Fork me on GitHub

Javalin 4 to 5 migration guide

This page attempts to cover all the things you need to know in order to migrate from Javalin 4 to Javalin 5. If you find any errors, or if something is missing, please edit this page on GitHub.

Jetty 11 and Java 11

The most significant change is that Javalin no longer supports Java 8, with Java 11 being the new minimum version. This is because Jetty 9 (what Javalin has been running on since 2017) has been end-of-lifed, and later Jetty versions require Java 11. Jetty 11 is the latest stable version of Jetty.

Repository change

Javalin used to live as a personal project at, but it has now been moved into an organization at All old links/remotes redirect, everything works as before.

Package changes

The core package has been removed, flattening the package structure of Javalin. Some other things have also been moved around.

// core package
import io.javalin.core.compression -> import io.javalin.compression
import io.javalin.core.config -> import io.javalin.config
import io.javalin.core.event -> import io.javalin.event
import -> import
import io.javalin.core.util -> import io.javalin.util
import io.javalin.core.util.Header -> import io.javalin.http.Header

// plugin package
import io.javalin.plugin.rendering.vue -> import io.javalin.vue
import io.javalin.plugin.json -> import io.javalin.json

Configuration changes

Configuration has been changed significantly. All config options used to be available directly on the config consumer in Javalin.create { config }, but in Javalin 5 most of the old config options have been moved into subconfigs. You can find the full overview at /documentation#configuration.

Context changes

Future rework

In Javalin 4 we had Context#future(future, callback), which has been changed to Context#future(futureSupplier). The recommended way to use futures in Javalin 5 is to lean on the CompletableFuture API and call Javalin’s Context methods in those callbacks:

app.get("/", ctx ->
    ctx.future(() -> myFuture
        .thenAccept(result -> ctx.result(result))
        .exceptionally(error -> ctx.result("Error: " + error))
app.get("/") { ctx ->
    ctx.future {
        myFuture.thenAccept { ctx.result(it) }
        myFuture.exceptionally { ctx.result("Error: " + it) }

SSE changes

In Javalin 4, clients weren’t automatically closed, this allowed people to keep lists of clients outside of the handler. But in Javalin 5 we’re no longer blocking connections by default for SSE clients, so you have to explicitly enable it using SseClient#keepAlive() if you want to restore old behavior.

ArrayList<SseClient> clients = new ArrayList<>();

app.sse("/sse", client -> {
    client.onClose(() - > clients.remove(client));
val clients = mutableListOf<SseClient>()

app.sse("/sse") { client ->
    client.onClose { clients.remove(client) }

Semi private fields renamed

The _conf.inner field has been renamed to cfg.pvt (config private) to further discourage use. It’s still okay to use it (if you know what you are doing).

Modules and packages moving out of the core

The main javalin/javalin repo has been on a diet, and a lot of things have been moved out. A few things have been removed altogether.

New OpenAPI project

The OpenAPI DSL and annotation processor (javalin-openapi) has been replaced by a new project by a very talented new Javalin contributor, @dzikoysk, who is also the author of Reposilite (repo, website). Reposilite is currently running both Javalin v5 and Javalin OpenAPI v5 in production. This new OpenAPI plugin should have significantly fewer issues than the old module!

Template engines moved to separate artifact

All template engines have been moved to a separate javalin-rendering artifact. To use template engines in Javalin 5 you have to add this dependency, and call MyTemplateEngine.init(), which will make the template engine register itself on JavalinRenderer with the proper extension. You could also call JavalinRender.register(engine, extension...) manually.

Micrometer has been removed

Related to the Jetty 11 change, the MicrometerPlugin has been removed. This had to be done because Micrometer does not support Jetty 11:

It will hopefully return (to the core) soon!

Other modules


The JavalinVue singleton has been removed. Instead of JavalinVue.configOption = ..., you can now configure Vue through Javalin.create { config.vue.configOption = ... }.

CORS plugin

The CORS plugin has been completely rewritten to be more flexible. Instead of the two methods enableCorsForAllOrigins() and enableCorsForOrigin(@NotNull String... origins) on the config object you now pass a lambda to config.plugins.enableCors() to configure CORS.

Javalin.create(config -> {
config.plugins.enableCors(cors -> {
    cors.add(corsConfig -> {
        // replacement for enableCorsForOrigin(@NotNull String... origins)
        corsConfig.allowHost(/* add your origins here */);
        //replacement for enableCorsForAllOrigins()

Javalin.create { config ->
config.plugins.enableCors { cors ->
    cors.add { corsConfig ->
        //replacement for enableCorsForOrigin(@NotNull String... origins)
        corsConfig.allowHost(/* add your origins here */)
        //replacement for enableCorsForAllOrigins()

Check out the CORS plugin page for more details on the rewritten CORS plugin and its capabilities.

Additional changes

It’s hard to keep track of everything, but you can look at the full commit log between the last 4.x version and 5.0.

If you run into something not covered by this guide, please edit this page on GitHub!

You can also reach out to us on Discord or GitHub.

Like Javalin?
Star us 😊