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

Introducing Javalin 2.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(args: Array<String>) {
    val app = Javalin.create().start(7000)
    app.get("/") { ctx -> ctx.result("Hello World") }
}
public static void main(String[] args) {
    Javalin app = Javalin.create().start(7000);
    app.get("/", ctx -> ctx.result("Hello World"));
}

API structure and server config

  • Kotlin
  • Java
val app = Javalin.create().apply {
    enableCorsForAllOrigins()
    enableStaticFiles("/public")
    enableStaticFiles("uploads", Location.EXTERNAL)
}.start(port)

app.routes {
    path("users") {
        get(UserController::getAll)
        post(UserController::create)
        path(":user-id") {
            get(UserController::getOne)
            patch(UserController::update)
            delete(UserController::delete)
        }
    }
}
Javalin app = Javalin.create()
    .enableCorsForAllOrigins()
    .enableStaticFiles("/public")
    .enableStaticFiles("uploads", Location.EXTERNAL)
    .start(port);

app.routes(() -> {
    path("users", () -> {
        get(UserController::getAll);
        post(UserController::create);
        path(":user-id"(() -> {
            get(UserController::getOne);
            patch(UserController::update);
            delete(UserController::delete);
        });
    });
});

WebSockets

  • Kotlin
  • Java
app.ws("/websocket") { ws ->
    ws.onConnect { session -> println("Connected") }
    ws.onMessage { session, message ->
        println("Received: " + message)
        session.send("Echo: " + message)
    }
    ws.onClose { session, statusCode, reason -> println("Closed") }
    ws.onError { session, throwable -> println("Errored") }
}
app.ws("/websocket", ws -> {
    ws.onConnect(session -> System.out.println("Connected"));
    ws.onMessage((session, message) -> {
        System.out.println("Received: " + message);
        session.send("Echo: " + message);
    });
    ws.onClose((session, statusCode, reason) -> System.out.println("Closed"));
    ws.onError((session, throwable) -> System.out.println("Errored"));
});

Object mapping

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

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.

Uploads

  • Kotlin
  • Java
app.post("/upload") { ctx ->
    ctx.uploadedFiles("files").forEach { (contentType, content, name, extension) ->
        FileUtil.streamToFile(content, "upload/$name")
    }
}
app.post("/upload", ctx -> {
    ctx.uploadedFiles("files").forEach(file -> {
        FileUtil.streamToFile(file.getContent(), "upload/" + file.getName())
    });
});

Filters and mappers

  • Kotlin
  • Java
app.before("/some-path/*") { ctx ->  ... } // runs before requests to /some-path/*
app.before { ctx -> ... } // runs before all requests
app.after { ctx -> ... } // runs after all requests
app.exception(Exception.class) { e, ctx -> ... } // runs if uncaught Exception
app.error(404) { ctx -> ... } // runs if status is 404 (after all other handlers)
app.before("/some-path/*", ctx -> { ... }); // runs before requests to /some-path/*
app.before(ctx -> { ... }); // runs before all requests
app.after(ctx -> { ... }); // runs after all requests
app.exception(Exception.class, (e, ctx) -> { ... }); // runs if uncaught Exception
app.error(404, ctx -> { ... }); // runs if status is 404 (after all other handlers)

What’s changed since Javalin 1.7 ?

According to the gitlog, 180+ files have changed, with ~5000 additions and ~5500 deletions (which is more or less the entire code base). Most of the changes are internal; some abstraction layers have been removed and the WebSocket implementation and the test-suite have been completely rewritten. Some of the major changes are:

  • We’ve added ETag support and a method for auto-generating ETags
  • We’ve added a RequestLogger interface ({ ctx, executionTime -> ...})
  • We’ve added the option to return 405 instead of 404, listing available methods for the path
  • We’ve added a set of default responses, so you can throw BadRequestResponse() (optional message)
  • We’ve added a CrudHandler to remove some boilerplate from creating standard CRUD APIs
  • We’ve added support for WebJars (https://www.webjars.org)
  • We’ve improved support for Single Page Applications
  • We’ve improved exception handling for async requests
  • All JSON and Template functionality has been modularized so you can easily plug in your own mappers/rendering engines.
  • All Template functionality has been moved into a single ctx.render() function which uses the correct engine based on the file extension
  • All requests run through an AccessManager now (the default implementation is NOOP)
  • URL matching is now case-insensitive by default (call app.enableCaseSensitiveUrls() if you must)
  • Javalin now has a pac4j implementation (https://github.com/pac4j/javalin-pac4j)
  • Some default values have been changed

There are also a lot quality of life improvements, such as functions returning List instead of Array, returning empty collections instead of null, better default values and package structure, fixing visibility from Java, etc. Multiple bugs were hurt in the making of the new version.

We’ve created a migration guide for users upgrading from 1.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.