
Introducing Javalin 7 (TBD, 2026)
Javalin is a Java and Kotlin web framework focused on simplicity and seamless interoperability. Built as a thin layer on top of the Jetty web server, it concentrates on the web layer without unnecessary abstraction.
Much of Javalin’s success comes from the exceptionally supportive JVM open-source community. After nearly eight years, the project sees over 1 million monthly downloads, 8.1k GitHub stars, 199 contributors, and 12k dependent projects. The core module is just ~10k lines of code, backed by ~14k lines of tests, nearly 1k full integration tests, each starting and stopping a Javalin instance to validate real-world behavior.
Let’s have a look at Javalin 7!
Hello World
Javalin’s main goal is simplicity and developer productivity. The “Hello World” example in Javalin 7 reflects the new upfront configuration approach:
- Java
- Kotlin
void main() {
var app = Javalin.create(config -> {
config.routes.get("/", ctx -> ctx.result("Hello World"));
}).start(7070);
}
fun main() {
val app = Javalin.create { config ->
config.routes.get("/") { ctx -> ctx.result("Hello World") }
}.start(7070)
}
Building REST APIs with Javalin
Creating a REST API with Javalin is very straightforward. Here’s a complete server with a CRUD API:
- Java
- Kotlin
var app = Javalin.create(config -> {
config.routes.apiBuilder(() -> {
path("users", () -> {
get(UserController::getAll);
post(UserController::create);
path("{user-id}", () -> {
get(UserController::getOne);
patch(UserController::update);
delete(UserController::delete);
});
});
});
}).start(7070);
val app = Javalin.create { config ->
config.routes.apiBuilder {
path("users") {
get(UserController::getAll)
post(UserController::create)
path("{user-id}") {
get(UserController::getOne)
patch(UserController::update)
delete(UserController::delete)
}
}
}
}.start(7070)
Sending data to clients
Javalin 7 offers many convenient methods for sending responses:
- Java
- Kotlin
ctx.result(stringOrStream); // writes string or input stream to client (`text/plain` by default)
ctx.json(myJson); // serializes object to JSON string and writes to client (as `application/json`)
ctx.jsonStream(myJson); // serializes JSON directly to client (nothing buffered in memory)
ctx.writeSeekableStream(myMediaFile); // stream audio and video to client (supports seeking/skipping)
ctx.future(myFutureSupplier); // instructs Javalin to handle request asynchronously
ctx.render("/file.ext", model); // render template or markdown file (as `text/html`)
ctx.result(stringOrStream) // writes string or input stream to client (`text/plain` by default)
ctx.json(myJson) // serializes object to JSON string and writes to client (as `application/json`)
ctx.jsonStream(myJson) // serializes JSON directly to client (nothing buffered in memory)
ctx.writeSeekableStream(myMediaFile) // stream audio and video to client (supports seeking/skipping)
ctx.future(myFutureSupplier) // instructs Javalin to handle request asynchronously
ctx.render("/file.ext", model) // render template or markdown file (as `text/html`)
Handling input from clients
Javalin also makes it easy to extract and validate client data:
- Java
- Kotlin
ctx.body(); // get the request body as a string (caches the body)
ctx.formParam("name"); // get a form parameter
ctx.queryParam("name"); // get a query parameter
ctx.uploadedFile("name"); // get an uploaded file
// JSON methods
ctx.bodyAsClass(Clazz); // deserialize ctx.body() to class
ctx.bodyStreamAsClass(Clazz); // consume input stream from request body and deserialize to class
// validation
var age = ctx.queryParamAsClass("age", Integer.class) // wraps parameter in Validator
.check(age -> age > 18, "NOT_OLD_ENOUGH") // adds check with error message
.get(); // gets the validated value, or throws ValidationException
ctx.body() // get the request body as a string (caches the body)
ctx.formParam("name") // get a form parameter
ctx.queryParam("name") // get a query parameter
ctx.uploadedFile("name") // get an uploaded file
// JSON methods
ctx.bodyAsClass<Clazz>() // deserialize ctx.body() to class
ctx.bodyStreamAsClass<Clazz>() // consume input stream from request body and deserialize to class
// validation
val age = ctx.queryParamAsClass<Int>("age") // wraps parameter in Validator
.check({ it > 18 }, "NOT_OLD_ENOUGH") // adds check with error message
.get() // gets the validated value, or throws ValidationException
WebSockets and Server-Sent Events
WebSockets and Server-Sent Events are also easy to set up:
- Java
- Kotlin
config.routes.ws("/websocket/{path}", ws -> {
ws.onConnect(ctx -> System.out.println("Connected"));
ws.onMessage(ctx -> {
var user = ctx.messageAsClass(User.class);
ctx.send(user);
});
ws.onClose(ctx -> System.out.println("Closed"));
});
config.routes.sse("/sse", client -> {
client.sendEvent("connected", "Hello, SSE");
client.onClose(() -> System.out.println("Client disconnected"));
});
config.routes.ws("/websocket/{path}") { ws ->
ws.onConnect { ctx -> println("Connected") }
ws.onMessage { ctx ->
val user = ctx.messageAsClass<User>()
ctx.send(user)
}
ws.onClose { ctx -> println("Closed") }
}
config.routes.sse("/sse") { client ->
client.sendEvent("connected", "Hello, SSE")
client.onClose { println("Client disconnected") }
}
Configuring Javalin
Javalin 7 makes configuration explicit and organized. Everything is configured upfront in the create() block:
- Java
- Kotlin
var app = Javalin.create(config -> {
// HTTP configuration
config.http.asyncTimeout = 10_000L;
config.http.generateEtags = true;
// Router configuration
config.router.ignoreTrailingSlashes = true;
config.router.caseInsensitiveRoutes = true;
// Static files
config.staticFiles.add("/public", Location.CLASSPATH);
// Custom resource handler (optional, for Jetty-free static file serving)
config.resourceHandler(new JavalinStaticResourceHandler());
// Jetty configuration
config.jetty.defaultPort = 8080;
}).start();
val app = Javalin.create { config ->
// HTTP configuration
config.http.asyncTimeout = 10_000L
config.http.generateEtags = true
// Router configuration
config.router.ignoreTrailingSlashes = true
config.router.caseInsensitiveRoutes = true
// Static files
config.staticFiles.add("/public", Location.CLASSPATH)
// Custom resource handler (optional, for Jetty-free static file serving)
config.resourceHandler(JavalinStaticResourceHandler())
// Jetty configuration
config.jetty.defaultPort = 8080
}.start()
For a full list of configuration options, see the documentation.
Plugins
Javalin’s plugin system enforces plugin authors to provide a consistent API:
- Java
- Kotlin
Javalin.create(config -> {
config.registerPlugin(new ExamplePlugin(exampleConfig -> {
exampleConfig.exampleSetting = "example";
}));
});
Javalin.create { config ->
config.registerPlugin(ExamplePlugin { exampleConfig ->
exampleConfig.exampleSetting = "example"
})
}
For more information about the plugin system, see /plugins/how-to.
Upgrading from Javalin 6
Javalin 7 is a major release built on Jetty 12, with improved configuration and modularity. If you’re upgrading from Javalin 6, please follow the migration guide for detailed instructions.
Get involved
If you want to contribute to the project, please head over to GitHub or Discord.
If you want to stay up to date, please follow us on Twitter.