Developing web applications with Javalin and Kotlin
This article will walk you through how to develop web applications in Kotlin using Javalin. Javalin and Kotlin work well together, as they are both very pragmatic and focus on getting things done quickly and with a small amount of code. We will start with a simple “Hello World” example, then look at server configuration, application structure, and finally deployment.
The code examples in this article are available on GitHub.
Hello World
To create a simple server which responds with Hello World
for a GET
request to /
, we need two lines of Javalin. One line to create and start the server, and one line to add a Handler
to a path.
import io.javalin.Javalin fun main(args: Array<String>) { val app = Javalin.start(7000) // create and start a server app.get("/") { ctx -> ctx.result("Hello World") } // add a handler to `/` }
This starts a basic Javalin server with the default configuration. Next we’ll look at how we can configure this server to fit our needs.
Configuring the server
Javalin’s configuration options are all available on the main Javalin
class through a fluent API. A full overview of all options is available in the docs.
In the following sections we will set a custom Jetty server and enable logging.
Custom EmbeddedServer
Javalin runs on an embedded Jetty server. You can configure this server in whatever way you want, and Javalin will add itself to it without ruining your configuration. As an example of a custom server we will use a custom ThreadPool
and Connector
-timeout settings:
val app = Javalin.create().apply { embeddedServer(EmbeddedJettyFactory { Server(QueuedThreadPool(200, 8, 120_000)).apply { connectors = arrayOf(ServerConnector(server).apply { port = 7070 idleTimeout = 120_000 }) } }) }.start()
Even if the Jetty Server is a Java API, we can use apply
to make the configuration elegant. Using a custom Jetty server is usually not necessary, but it’s required if you want to use Jetty for SSL or HTTP2.
Logging
To add logging we can use app.requestLogLevel(logLevel)
. The available levels are OFF
, STANDARD
and EXTENSIVE
. For development, the EXTENSIVE
level can be very useful, since it logs the full request and response. Keep in mind that this has a considerable performance impact, so don’t use it in production. Let’s add it to the snippet above:
val app = Javalin.create().apply { embeddedServer(EmbeddedJettyFactory { Server(QueuedThreadPool(200, 8, 120_000)).apply { connectors = arrayOf(ServerConnector(server).apply { port = 7070 idleTimeout = 120_000 }) } }) requestLogLevel(LogLevel.EXTENSIVE) // this has been added }.start()
Setting up routes and controllers
Javalin apps are built around Handler
lambdas. You have:
- before-handlers,
- endpoint-handlers,
- after-handlers,
- exception-handlers
- and error-handlers.
In other web frameworks an endpoint-handler is sometimes called a “route”, and before/after handlers are called “filters” or “middleware”.
Let’s take a closer look at the different handlers.
Endpoint handlers
The main handler-type in Javalin is the endpoint-handler. The endpoint-handler is attached to the Javalin server by calling the appropriate verb-method.
The Handler
interface looks like this:
@FunctionalInterface public interface Handler { void handle(Context ctx) throws Exception; }
And the signature of the verb-methods are verb(String path, Handler handler)
This allows you to use lambdas in trailing closures:
app.get("/") { ctx -> ctx.result("Hello World")) }
This is great for small services, but as your service grows you have to create controllers. You can structure your application by using the ApiBuilder
class and method references:
app.routes { path("users") { get(UserController::getAllUsers); post(UserController::createUser); path(":id") { get(UserController::getUser); patch(UserController::updateUser); delete(UserController::deleteUser); } } }
The example shows calling method references directly on the object/class, but you can also create an instance of userController
if you want to do dependency injection.
An implementation of one of these methods would look like this:
object UserController { function getAllUsers(ctx: Context) { ctx.json(DummyDao.findAllUsers()) } }
We are using ctx.json()
here to transform a list of users into a JSON object. Javalin relies on Jackson to perform this serialization, which needs to be added to your build.gradle
/pom.xml
.
The Context
object.
We’ve seen the Context
object (ctx) a few times so far, but we haven’t learned what it is yet. The Context
object is a wrapper for HttpServletRequest
and HttpServletResponse
. It contains everything you need to handle a HTTP-request.
- If you need to extract a query-param, you can call
ctx.queryParam("qp")
. - If you need to get all the headers, you can do
ctx.headerMap()
. - To set a result you can do
ctx.result("res")
.
A full list of all the available options can be found in the docs.
As a general rule, anything that “sets” something on the context operates on the response, and anything that “gets” something operates on the request.
Before/after handlers
Sometimes you need to do setup/cleanup before/after processing a request. You use before/after handlers for this in Javalin. They function like @Before
and @After
methods in JUnit, and are run before and after the endpoint handlers.
app.before { ctx -> // do something before every request } app.get("/test") { ctx -> // do something for GET to /test endpoint } app.after("/test") { ctx -> // do something after requests to /test }
Exception handlers
As you saw earlier, the Handler
interface throws Exception
. All exceptions are caught by the ExceptionMapper
. There is a special exception class called HaltException which is handled before other exceptions. To handle any sort of exception, you add an exception-handler similarily to the endpoint-handlers and before/after handlers:
app.exception(NullPointerException.class, (e, ctx) -> { // handle exception here });
If you have mulitple handlers capable of handling an exception (ex: NullPointerException
, RuntimeException
and Exception
), the most specific one will be used.
Error handlers
Often you’ll want to handle exceptions differently, but you want them all to result in the same error message. For example, the exceptions from the previous section should probably all turn into a 500 - Internal server error
message. This can be solved by making all the exception-handlers set the status to 500, and adding an error-handler. This will also allow you to point to the same error-message from your endpoint-handlers:
app.get("/error") { ctx -> if (somethingBad) { ctx.status(500) } ... } app.exception(NullPointerException::class.java, { e, ctx -> // do something ctx.status(500) }).exception(RuntimeException::class.java, { e, ctx -> // do something ctx.status(500) }) app.error(500) { ctx -> ctx.result("500 - Internal server error") // all 500s end up like this }
Going async
Javalin is intended to be very simple for beginners to get started with, but it also wants to offer advanced functionality for power users. To run your requests asynchronously, simply pass a CompletableFuture
to ctx.result()
:
app.get("/") { ctx -> ctx.result(getFuture()) } // hopefully your future is less pointless than this: private fun getFuture() = CompletableFuture().apply { Executors.newSingleThreadScheduledExecutor().schedule({ this.complete("Hello World!") }, 1, TimeUnit.SECONDS) }
Deployment
To deploy a Javalin app you just have to package it as a jar and launch it.
Building a jar
There is nothing Javalin specific about this, we just need to create a jar with all the dependencies and launch it.
Here one example for Gradle and one for Maven:
Gradle: https://github.com/johnrengelman/shadow
Maven: https://maven.apache.org/plugins/maven-shade-plugin
Then simply launch the jar with java -jar filename.jar
. That’s it!
Conclusion
Now you know a little about how to use Javalin in Kotlin. Some Kotlin features were particularly useful, for example apply
when working towards Jetty’s Java API, or the trailing closures which are used extensively with all Javalin handlers.
If you’re interested in learning more about web development using Kotlin and Javalin, I have written a lot of other tutorials on it. Here are five popular ones:
- Creating a WebSockets chat app
- Securing your endpoints
- Monitoring with prometheus and grafana
- Single page applications with Vue.js
- HTML forms and uploads
David Åse is the creator of Javalin, a JVM web-framework for Kotlin and Java. He currently works as a Software Engineer at Working Group Two, a startup looking to disrupt the telecom industry. He previously worked for Telenor, a major global telecom.
You can find him on LinkedIn. David doesn’t have twitter, but he would appreciate it if you followed Javalin’s Twitter.
Leave a Reply
Want to join the discussion?Feel free to contribute!