This is a release candidate - There shouldn’t be breaking changes, but there has been a lot of refactoring. Exercise caution, and please report any bugs at

Improvements to request lifecyle

There exists an excellent description of some of the issues with the previous implementation, written by @dzikoysk.

To provide a short summary, Javalin used to store the result the user wished to return to the client either as a CompletableFuture or an InputStream. This depended on if you called Context#future or Context#result. Calling one of these methods would clear any value set by the other one, and when it was time to finish the request lifecycle, Javalin would branch into two ways of writing a response (async or sync). This could lead to race conditions in certain cases.

The issue was solved by consolidating the two backing fields into a single CompletableFuture, and wrapping it in an AtomicReference. This also allowed us to rework the request lifecycle into a chain of CompletableFuture results. This makes previously impossible things possible, such as calling Context#future in all types of handlers (before, after, exception, error).

Javalin still runs synchronously if you don’t pass it a CompletableFuture. If you pass Javalin a String result by calling ctx.result("My String"), Javalin will transform this into an already completed future, and there will be no need to start an async context. There should be no changes to the existing behavior of Javalin apps. Calling Context#result or Context#future will now always close previous streams and cancel previous futures.

It’s also worth noting that when you pass a non-completed CompletableFuture in, for example, a before, then the request will be lifted out of Jetty’s ThreadPool, and the rest of handlers (http, after, exception, error) will be appended to this CompletableFuture as a next stage. This means it will be executed in the ThreadPool that was responsible for completion of the original non-completed CompletableFuture.

Improvements to response writing

The JavalinResponseWrapper class, which handled compression and response writing has been completely rewritten. This should fix potential bugs with streams not being closed properly.

New functionality

  • You can now call Context#future in any type of handler
  • You can now configure the default future callback via ContextResolver#defaultFutureCallback
  • The JavalinJackson implementation now registers a KtormModule (if it finds it)