Important: This news article covers an old version of Javalin (v3.0.0). The current version is v6.1.3.
See the documentation page for up-to-date information.

Introducing Javalin 3.0

Javalin is a very lightweight web framework for Kotlin and Java which supports WebSockets, HTTP2 and async requests. Javalin’s main goals are simplicity, a great developer experience, and first class interoperability between Kotlin and Java.

Javalin is more library than framework; you don’t need to extend anything, there are no @Annotations, no reflection, no other magic; just code. Let’s look at some examples. You can switch between Kotlin and Java to see what we mean by first class interoperability.

Hello World

  • Kotlin
  • Java
fun main() {
    val app = Javalin.create().start(7000)
    app.get("/") { it.result("Hello World") }
public static void main(String[] args) {
    var app = Javalin.create().start(7000);
    app.get("/", ctx -> ctx.result("Hello World"));

Server config and API structure

  • Kotlin
  • Java
val app = Javalin.create { config ->
    config.defaultContentType = "application/json"
    config.autogenerateEtags = true
    config.asyncRequestTimeout = 10_000L
    config.dynamicGzip = true
    config.enforceSsl = true
}.routes {
    path("users") {
        path(":user-id") {
        ws("events", userController::webSocketEvents)
var app = Javalin.create(config -> {
    config.defaultContentType = "application/json";
    config.autogenerateEtags = true;
    config.asyncRequestTimeout = 10_000L;
    config.dynamicGzip = true;
    config.enforceSsl = true;
}).routes(() -> {
    path("users", () -> {
        path(":user-id"(() -> {
        ws("events", userController::webSocketEvents);


  • Kotlin
  • Java"/websocket/:path") { ws ->
    ws.onConnect { ctx -> println("Connected") }
    ws.onMessage { ctx ->
        val user = ctx.message<User>(); // convert from json string to object
        ctx.send(user); // convert to json string and send back
    ws.onClose { ctx -> println("Closed") }
    ws.onError { ctx -> println("Errored") }
}"/websocket/:path", ws -> {
    ws.onConnect(ctx -> System.out.println("Connected"));
    ws.onMessage(ctx -> {
        var user = ctx.message(User.class); // convert from json string to object
        ctx.send(user); // convert to json string and send back
    ws.onClose(ctx -> System.out.println("Closed"));
    ws.onError(ctx -> System.out.println("Errored"));


Javalin has built validation for parameters (path params, query params, and form params). This validation can be activated by providing a type to the parameter call:

  • Kotlin
  • Java
val myQpStr = ctx.queryParam("my-qp") // no validation, returns String or null
val myQpInt = ctx.pathParam<Int>("my-qp").get() // returns an Int or throws
val myQpInt = ctx.formParam<Int>("my-qp").check{ it > 4 }.get() // returns an Int > 4 or throws
val instant = ctx.pathParam<Instant>("my-qp").get() // returns an Instant or throws
var myQpStr = ctx.queryParam("my-qp"); // no validation, returns String or null
var myQpInt = ctx.pathParam("my-qp", Integer.class).get(); // returns an Integer or throws
var myQpInt = ctx.formParam("my-qp", Integer.class).check(i -> i > 4).get(); // Integer > 4
var instant = ctx.pathParam("my-qp", Instant.class).get(); // returns an Instant or throws

You can also write more complicated check predicates:

  • Kotlin
  • Java
// validate two dependent query parameters:
val fromDate = ctx.queryParam<Instant>("from").get()
val toDate = ctx.queryParam<Instant>("to")
        .check({ it.isAfter(fromDate) }, "'to' has to be after 'from'")

// validate a json body:
val myObject = ctx.bodyValidator<MyObject>()
        .check({ it.myObjectProperty == someValue })
// validate two dependent query parameters:
var fromDate = ctx.queryParam("from", Instant.class).get();
var toDate = ctx.queryParam("to", Instant.class)
        .check(it -> it.isAfter(fromDate), "'to' has to be after 'from'")

// validate a json body:
var myObject = ctx.bodyValidator(MyObject.class)
        .check(obj -> obj.myObjectProperty == someValue)

If any of the validators fail, a BadRequestResponse is thrown:

"Query parameter 'to' with value '1262347000000' invalid - 'to' has to be after 'from'"
"Request body as MyObject invalid - Check failed" // can set custom error message in check()

Object mapping

  • Kotlin
  • Java
var todos = arrayOf<Todo>()
app.get("/todos") { ctx -> // map array of Todos to json-string
app.put("/todos") { ctx -> // map request-body (json) to array of Todos
    todos = ctx.body<Array<Todo>>()
var todos = new Todo[];
app.get("/todos", ctx -> { // map array of Todos to json-string
app.put("/todos", ctx -> { // map request-body (json) to array of Todos
    todos = ctx.body(Todo[].class);

The JSON mapper is pluggable, so Javalin supports any and all JSON-mapping libraries. There is an optional Jackson implementation included, but you can create your own mapper easily.

OpenAPI plugin

One of the biggest features of Javalin 3.0 is the OpenAPI (Swagger) support. This has been heavily requested since Javalin was first released, but coming up with a good integration has been difficult. Tobias Walle (GitHub and LinkedIn) did a fantastic job and contributed a full implementation of the OpenAPI 3.0 specification, available both as a DSL and as annotations.


When using the OpenAPI DSL you define an OpenApiDocumentation object to pair with your Handler:

val addUserDocs = document()

fun addUserHandler(ctx: Context) {
    val user = ctx.body<User>()

You then combine these when you add your routes:

post("/users", documented(addUserDocs, ::addUserHandler))

OpenAPI annotations

If you prefer to keep your documentation separate from your code, you can use annotations instead:

    requestBody = OpenApiRequestBody(User::class),
    responses = [
        OpenApiResponse("400", Unit::class),
        OpenApiResponse("201", Unit::class)
fun addUserHandler(ctx: Context) {
    val user = ctx.body<User>()

If you use the annotation API you don’t need to connect the documentation and the handler manually, you just reference your handler as normal:

post("/users", ::addUserHandler)

Javalin will then extract the information from the annotation and build the documentation automatically.

To enable hosted docs you have to specify some paths in your Javalin config:

val app = Javalin.create {
            OpenApiOptions(Info().version("1.0").description("My Application"))
                    .swagger(SwaggerOptions("/swagger").title("My Swagger Documentation"))
                    .reDoc(ReDocOptions("/redoc").title("My ReDoc Documentation"))

Javalin supports both Swagger and ReDoc for rendering the documentation.

What’s changed since Javalin 2.8 ?

According to the gitlog, 200+ files have changed, with ~6000 additions and ~2500 deletions.
The OpenAPI integration is about ~3000 additions, and some of other the major changes are:

  • WebSockets have been completely reworked
    • WsContext has been introduced
    • wsBefore / wsAfter / wsException have been introduced
    • AccessManager can now handle WebSocket upgrade requests
  • Configuration has been moved from the Javalin class to JavalinConfig, which is an argument to app.create()
  • Event-setup has been completely reworked, and more events have been added
  • Validation has been improved and simplified
  • Extension has been renamed to Plugin and has been reworked
  • Standalone mode (running without Jetty) has been improved
  • A very small Vue (JavaScript frontend library) integration has been added
  • The internals have been completely refactored to make development easier
  • A lot of new config options have been added

We’ve created a migration guide for users upgrading from 2.X.

Get involved

If you want to contribute to the project, please head over to GitHub or Gitter.

If you want to stay up to date, please follow us on Twitter.