HTTP Server


This port's purpose is to develop HTTP servers (REST services or Web applications). It defines a DSL to declare HTTP request handlers.

Adapters implementing this port are responsible of transforming the DSL into a runtime. And allows you to switch implementations without changing the service.

The hexagon_web module provides utilities on top of this port for Web application development (like templates helpers).


A server is a process listening to HTTP requests on a TCP port.

You can run multiple ones on different ports at the same time (this can be useful to test many microservices at the same time).

The server can be configured with different properties. If you do not provide a value for them, they are searched inside the application settings and lastly, a default value is picked. This is the parameters list:

  • serviceName: name of this service, it is only informative and it is displayed on the logs. If not set <undefined> is used.
  • bindAddress: address to which this process is bound. If none is provided, is taken.
  • bindPort: the port that the process listens to. By default it is 2010
  • contextPath: initial path used for the rest of the routes, by default it is empty.

You can inject an adapter for the Server port using the InjectionManager object: InjectionManager.bindObject<ServerPort>(JettyServletAdapter())

To create a server, you need to provide a router (check the next section for more information), and after creating a server you can run it or stop it with start and stop methods.

 * All settings are optional, you can supply any combination
 * Parameters not set will fall back to the defaults
val settings = ServerSettings(
    serverName = "name",
    bindAddress = InetAddress.getByName("0.0.0"),
    bindPort = 2020,
    contextPath = "/context"

val router = Router {
    get("/hello") { ok("Hello World!") }

val customServer = Server(adapter, router, settings)


val customClient = Client("http://localhost:${customServer.runtimePort}")
assert(customClient.get("/context/hello").responseBody == "Hello World!")


 * You can skip the adapter is you previously bound one
 * You may also skip the settings an the defaults will be used
val defaultServer = Server(router = router)


val defaultClient = Client("http://localhost:${defaultServer.runtimePort}")
assert(defaultClient.get("/hello").responseBody == "Hello World!")

Servlet Web server

There is an special server adapter for running inside Servlet Containers. To use it you should import the Servlet HTTP Server Adapter into your project. Check the http_server_servlet module for more information.


The main building block of a Hexagon HTTP service is a set of routes. A route is made up of three simple pieces:

  • A verb (get, post, put, delete, head, trace, connect, options). It can also be any.
  • A path (/hello, /users/{name}). Paths must start with '/' and trailing slash is ignored.
  • A callback code block.

The callback has a void return type. You should use Call.send() to set the response which will be returned to the user.

Routes are matched in the order they are defined. The first route that matches the request is invoked and the following ones are ignored.

Check the next snippet for usage examples:

get("/hello") { ok("Get greeting")}
put("/hello") { ok("Put greeting")}
post("/hello") { ok("Post greeting")}

any("/hello") { ok("Fallback if HTTP verb was not used before")}

get { ok("Get at '/' if no route matched before") }

HTTP clients will be able to reuse the routes to create REST services clients.

Route groups

Routes can be nested by calling the path() method, which takes a String prefix and gives you a scope to declare routes and filters (or more nested paths). Ie:

path("/nested") {
    get("/hello") { ok("Greeting")}

    path("/secondLevel") {
        get("/hello") { ok("Second level greeting")}

    get { ok("Get at '/nested'") }

If you have a lot of routes, it can be helpful to group them into routers. You can create routers to mount a group of routes in different paths (allowing you to reuse them). Check this snippet:

fun personRouter(kind: String) = Router {
    get { ok("Get $kind") }
    put { ok("Put $kind") }
    post { ok("Post $kind") }

val server = Server(adapter) {
    path("/clients", personRouter("client"))
    path("/customers", personRouter("customer"))


Callbacks are request's handling blocks that are bound to routes or filters. They make the request, response and session objects available to the handling code.


The Call object provides you with everything you need to handle a http-request.

It contains the underlying request and response, and a bunch of utility methods to return results, read parameters or pass attributes among filters/routes.

The methods are available directly from the callback (Call is the callback receiver). You can check the API documentation for the full list of methods.

This sample code illustrates the usage:

get("/call") {
    attributes                   // the attributes list
    attributes["foo"]            // value of foo attribute
    attributes["A"] = "V"        // sets value of attribute A to V

    ok("Response body")          // returns a 200 status
    send(400, "Invalid request") // returns any status

Request functionality is provided by the request field:

get("/request") {
    request.method                   // the HTTP method (GET, ..etc)
    request.scheme                   // http or https                   // true if scheme is https                     // the host, e.g. ""
    request.ip                       // client IP address
    request.port                     // the server port
    request.path                     // the request path, e.g. /result.jsp
    request.body                     // request body sent by the client
    request.url                      // the url. e.g. ""
    request.contentLength            // length of request body
    request.contentType              // content type of request.body
    request.accept                   // Client accepted content types
    request.headers                  // the HTTP header list
    request.headers["BAR"]           // value of BAR header
    request.userAgent                // user agent (browser requests)
    request.origin                   // origin (browser requests)
    request.body(Type::class)        // Object passed in the body as a typed object
    request.body<Type>()             // Syntactic sugar for the previous statement
    request.bodyObjects(Type::class) // Object(s) passed in the body as a typed list
    request.bodyObjects<Type>()      // Syntactic sugar for the previous statement
    request.body(Map::class)         // Object passed in the body as a field map
    request.body<Map<*, *>>()        // Syntactic sugar for the previous statement
    request.bodyObjects(Map::class)  // Object(s) passed in the body as a list of maps
    request.bodyObjects<Map<*, *>>() // Syntactic sugar for the previous statement

Response information is provided by the response field:

get("/response") {
    response.body                           // get response content
    response.body = "Hello"                 // sets content to Hello
    response.headers["FOO"] = listOf("bar") // sets header FOO with value bar
    response.status                         // get the response status
    response.status = 401                   // set status code to 401
    response.contentType                    // get the content type
    response.contentType = "text/xml"       // set content type to text/xml
Path Parameters

Route patterns can include named parameters, accessible via the pathParameters map on the request object:

get("/pathParam/{foo}") {
    request.pathParameters["foo"] // value of foo path parameter
    request.pathParameters        // map with all parameters
Query Parameters

It is possible to access the whole query string or only an specific query parameter using the parameters map on the request object:

get("/queryParam") {
    request.queryParameters                 // the query param list
    request.queryParameters["FOO"]?.first() // value of FOO query param
    request.queryParameters["FOO"]          // all values of FOO query param

You can redirect requests (returning 30x codes) by using Call utility methods:

get("/redirect") {
    redirect("/call") // browser redirect to /call

The request and response cookie functions provide a convenient way for sharing information between handlers, requests, or even servers.

You can read client sent cookies from the request's cookies read only map. To change cookies or add new ones you have to use response.addCookie() and response.removeCookie() methods.

Check the following sample code for details:

get("/cookie") {
    request.cookies                       // get map of all request cookies
    request.cookies["foo"]                // access request cookie by name

    val cookie = HttpCookie("new_foo", "bar")
    response.addCookie(cookie)            // set cookie with a value

    cookie.maxAge = 3600
    response.addCookie(cookie)            // set cookie with a max-age = true
    response.addCookie(cookie)            // secure cookie

    response.removeCookie("foo")          // remove cookie

Every request has access to the session created on the server side, the session object provides the following methods:

get("/session") {
    session                         // create and return session
    session.attributes["user"]      // Get session attribute 'user'
    session.set("user", "foo")      // Set session attribute 'user'
    session.removeAttribute("user") // Remove session attribute 'user'
    session.attributes              // Get all session attributes                      // Get session id
    session.isNew()                 // Check if session is new

To immediately stop a request within a filter or route use halt(). halt() is not intended to be used inside exception-mappers. Check the following snippet for an example:

get("/halt") {
    halt()                // halt with status 500 and stop route processing

     * These are just examples the following code will never be reached
    halt(401)             // halt with status
    halt("Body Message")  // halt with message (status 500)
    halt(401, "Go away!") // halt with status and message


You might know filters as interceptors, or middleware from other libraries. Filters are blocks of code executed before or after one or more routes. They can read the request and read/modify the response.

All filters that match a route are executed in the order they are declared.

Filters optionally take a pattern, causing them to be executed only if the request path matches that pattern.

Before and after filters are always executed (if the route is matched). But any of them may stop the execution chain if halted.

If halt() is called in one filter, filter processing is stopped for that kind of filter (before or after). In the case of before filters, this also prevent the route from being executed (but after filters are executed anyway).

The following code details filters usage:

before { response.headers["b_all"] = listOf("true") }

before("/filters/*") { response.headers["b_filters"] = listOf("true") }
get("/filters/route") { ok("filters route") }
after("/filters/*") { response.headers["a_filters"] = listOf("true") }

get("/filters") { ok("filters") }

path("/nested") {
    before { response.headers["b_nested"] = listOf("true") }
    before("/") { response.headers["b_nested_2"] = listOf("true") }
    get("/filters") { ok("nested filters") }
    get("/halted") { halt(499, "halted") }
    get { ok("nested also") }
    after { response.headers["a_nested"] = listOf("true") }

after { response.headers["a_all"] = listOf("true") }

Error Handling

You can provide handlers for runtime errors. Errors are unhandled thrown exceptions in the callbacks, or handlers halted with an error code.

Error handlers for a given code or exception are unique, and the first one defined is the one that will be used.

HTTP Errors Handlers

Allows to handle routes halted with a given code. These handlers are only applied if the route is halted, if the error code is returned with send it won't be handled as an error. Example:

// Register handler for routes halted with 512 code
error(512) { send(500, "Ouch")}

// If status code (512) is returned with `send` error won't be triggered
get("/errors") { halt(512) }
Exception Mapping

You can handle exceptions of a given type for all routes and filters. The handler allows you to refer to the thrown exception. Look at the following code for a detailed example:

// Register handler for routes which callbacks throw exceptions
error(CodedException::class) { send(599, it.message ?: "empty") }
error(IllegalStateException::class) { send(505, it.message ?: "empty") }
get("/exceptions") { error("Message") }
get("/codedExceptions") { halt(509, "code") }

Static Files

You can use a folder in the classpath for serving static files with the get() methods. Note that the public directory name is not included in the URL.

Asset mapping is handled like any other route, so if an asset mapping is matched, no other route will be checked (assets or other routes). And also, if a previous route is matched, the asset mapping will never be checked.

Being get(resource) a shortcut of get("/*", resource) it should be placed as the last route. Check the next example for details:

get("/web/file.txt") { ok("It matches this route and won't search for the file") }

// Expose resources on the '/public' resource folder over the '/web' HTTP path
get("/web/*", Resource("public"))

// Maps resources on 'assets' on the server root (assets/f.css -> /f.css)
// '/public/css/style.css' resource would be: 'http://{host}:{port}/css/style.css'
MIME types

The MIME types of static files are computed from the file extension using the SerializationManager.contentTypeOf method.


CORS behaviour can be different depending on the path. You can attach different CorsSettings to different routers. Check CorsSettings class for more details.

val server: Server by lazy {
    Server(adapter) {
        corsPath("/default", CorsSettings())
        corsPath("/example/org", CorsSettings(""))
        corsPath("/no/credentials", CorsSettings(supportCredentials = false))
        corsPath("/only/post", CorsSettings(allowedMethods = setOf(POST)))
        corsPath("/cache", CorsSettings(preFlightMaxAge = 10))
        corsPath("/exposed/headers", CorsSettings(exposedHeaders = setOf("head")))
        corsPath("/allowed/headers", CorsSettings(allowedHeaders = setOf("head")))

private fun Router.corsPath(path: String, settings: CorsSettings) {
    path(path) {
        // CORS settings can change for different routes

        get("/path") { ok(request.method) }
        post("/path") { ok(request.method) }
        put("/path") { ok(request.method) }
        delete("/path") { ok(request.method) }
        get { ok(request.method) }
        post { ok(request.method) }
        put { ok(request.method) }
        delete { ok(request.method) }


It is possible to start a secure server enabling HTTPS. For this, you have to provide a server certificate and its key in the server's SslSettings. Once you use a server certificate, it is also possible to serve content using HTTP/2, for this to work, ALPN is required (however, this is already handled if you use Java 11).

The certificate common name should match the host that will serve the content in order to be accepted by an HTTP client without a security error. There is a Gradle helper to create sample certificates for development purposes.

HTTP clients can also be configured to use a certificate. This is required to implement a double ended authorization (mutual TLS). This is also done by passing a SslSettings object the the HTTP client.

If you want to implement mutual trust, you must enforce client certificate in the server configuration (check SslSettings.clientAuth). If this is done, you can access the certificate the client used to connect (assuming it is valid, if not the connection will end with an error) with the Request.certificateChain property.

Below you can find a simple example to set up an HTTPS server and client with mutual TLS:

// Key store files
val identity = "hexagonkt.p12"
val trust = "trust.p12"

// Key stores can be set as URIs to classpath resources (password is file name reversed)
val keyStore = URI("resource://${identity.reversed()}/ssl/$identity")
val trustStore = URI("resource://${trust.reversed()}/ssl/$trust")

val sslSettings = SslSettings(
    keyStore = keyStore,
    trustStore = trustStore,
    clientAuth = true // Requires a valid certificate from the client (mutual TLS)

val serverSettings = ServerSettings(
    bindPort = 0,
    protocol = HTTPS, // You can also use HTTP2
    sslSettings = sslSettings

val server = serve(serverSettings, serverAdapter) {
    get("/hello") {
        // We can access the certificate used by the client from the request
        val subjectDn = request.certificate?.subjectDN?.name
        response.setHeader("cert", subjectDn)
        ok("Hello World!")

// We'll use the same certificate for the client (in a real scenario it would be different)
val clientSettings = ClientSettings(sslSettings = sslSettings)

// Create a HTTP client and make a HTTPS request
Client("https://localhost:${server.runtimePort}", clientSettings).get("/hello") {
    logger.debug { responseBody }
    // Assure the certificate received (and returned) by the server is correct
    assert(responseBody == "Hello World!")


Integration tests

To test HTTP servers from outside using a real Adapter, you can create a server setting 0 as port. This will pick a random free port that you can check later:

val router = Router {
    get("/hello") { ok("Hi!") }

val serverSettings = ServerSettings("name", InetAddress.getLoopbackAddress(), 0)
val server = Server(adapter, router, serverSettings)

val client = Client("http://localhost:${server.runtimePort}")
assert(client.get("/hello").responseBody == "Hi!")

To do this kind of tests without creating a custom server (using the real production code). Check the tests of the starter projects.

Mocking calls

To unit test callbacks you can create test calls with hardcoded requests, responses and sessions.

To use it in your project you'll have to include a dependency (with test scope):


Check testCall, TestRequest, TestResponse and TestSession for more details.

For a quick sample, check the snipped below:

private fun Call.testedCallback() {
    if (request.body == "weird")
        send(999, "Weird error")

@Test fun `Test call behaves as expected`() {
    val testCall = testCall(TestRequest(body = "weird"))


    assert(testCall.response.status == 999)
    assert(testCall.response.body == "Weird error")


Name Summary
com.hexagonkt.http.server This package defines the classes used in the HTTP DSL.


All Types