https://javalin.io/news/javalin-5.0.0-stable.html Fork me on GitHub Javalin * Docs * Tutorials * Plugins * Community Javalin 5.0.0 released Javalin 5.0 stable is ready! (Oct 1, 2022) Foreword Javalin is a Java and Kotlin web framework which focuses on simplicity and Java/Kotlin interoperability. It supports WebSockets and HTTP3, and it uses Virtual Threads (from Project Loom) by default. Javalin aims to be very lightweight and has a codebase of around 7000 lines of Java/Kotlin code, as well as around 10 000 lines of test (629 tests). The project would not have been possible without the amazingly supportive JVM open-source community. Javalin has been around for five years now and has 161 contributors and 497 forks. 552 pull requests have been merged and 990 issues have been closed. The project has had three million downloads in the past 12 months. I'd like to extend my special thanks to one of our newer contributors, @dzikoysk, who has been very helpful in getting v5 ready. Thank you, your contributions have given me a lot of motivation! Okay, let's have a look at Javalin 5! Hello Javalin World Javalin's main goal is simplicity and developer productivity. The "Hello World" example reflects that: * Java * Kotlin public static void main(String[] args) { var app = Javalin.create(/*config*/) .get("/", ctx -> ctx.result("Hello World")) .start(7070); } fun main() { val app = Javalin.create(/*config*/) .get("/") { ctx -> ctx.result("Hello World") } .start(7070) } Sending data to clients The simplest way to send content to a client is through ctx.result ("My String"), which sends a text/plain result. Javalin has several options 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 makes it easy to extract and validate client data through dedicated methods: * 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 var bananaBox = ctx.bodyValidator(BananaBox.class) .check(box -> box.weight < 5, ValidationError("WEIGHT_TOO_HIGH", Map.of("MAX_WEIGHT", 5))) .check(box -> box.bananas.length > 20, ValidationError("NOT_ENOUGH_BANANAS", Map.of("MIN_BANANAS", 20))) .getOrDefault(defaultBananaBox) // uses default if body is null, runs validation rules otherwise 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() // deserialize ctx.body() to class ctx.bodyStreamAsClass() // consume input stream from request body and deserialize to class // validation val age = ctx.queryParamAsClass("age") // wraps parameter in Validator .check({ it > 18 }, "NOT_OLD_ENOUGH") // adds check with error message .get() // gets the validated value, or throws ValidationException val bananaBox = ctx.bodyValidator() .check({ it.weight < 5 }, ValidationError("WEIGHT_TOO_HIGH", mapOf("MAX_WEIGHT" to 5))) .check({ it.bananas.length > 20 }, ValidationError("NOT_ENOUGH_BANANAS", mapOf("MIN_BANANAS" to 20))) .getOrDefault(defaultBananaBox) // uses default if body is null, runs validation rules otherwise WebSockets and Server-Sent Events WebSockets and Server-Sent Events are handled with lambdas, similar to most of Javalin's other APIs: * Java * Kotlin app.ws("/websocket/{path}", ws -> { ws.onConnect(ctx -> System.out.println("Connected")); ws.onMessage(ctx -> { User user = ctx.messageAsClass(User.class); // convert from json ctx.send(user); // convert to json and send back }); ws.onBinaryMessage(ctx -> System.out.println("Message")) ws.onClose(ctx -> System.out.println("Closed")); ws.onError(ctx -> System.out.println("Errored")); }); app.sse("/sse", client -> client.sendEvent("connected", "Hello, SSE"); // can also send an object, which will be serialized client.onClose(() -> System.out.println("Client disconnected")); }); app.ws("/websocket/{path}") { ws -> ws.onConnect { ctx -> println("Connected") } ws.onMessage { ctx -> val user = ctx.messageAsClass(); // convert from json ctx.send(user); // convert to json and send back } ws.onBinaryMessage { ctx -> println("Message") } ws.onClose { ctx -> println("Closed") } ws.onError { ctx -> println("Errored") } } app.sse("/sse") { client -> client.sendEvent("connected", "Hello, SSE") // can also send an object, which will be serialized client.onClose { println("Client disconnected") } } Routing and request lifecycle Routing in Javalin can either happen directly on the Javalin instance (usually named app), or through a set of util-methods which improves readability. Please note that these util-method do not hold any global state, but function as normal util-methods (Util.method(app, ...)) with a fancy syntax. * Java * Kotlin import static io.javalin.apibuilder.ApiBuilder.* ... app.routes(() -> { before(GlobalController::globalAction) // handler that runs for every request to the app path("users", () -> { // push subpath /users on the router get(UserController::getAll); // get controller for /users/ post(UserController::create); // post controller for /users/ before("{userId}*", UserController:userIdCheck); // handler that runs for every request to /users/{userId} as well as al subpaths path("{userId}", (() -> { // new subpath /{userId} on the router get(UserController::getOne); // get controller for /users/{userId} patch(UserController::update); // patch controller for /users/{userId} path("subpath", (() -> { ... }); // push subpath /subpath on the router (and pop it immediately) }); // pop subpath /{userId} on the router ws("events", UserController::webSocketEvents); // websocket controller for /users/events }); // pop subpath /users on the router }).start(port); import static io.javalin.apibuilder.ApiBuilder.* ... app.routes { before(GlobalController::globalAction) // handler that runs for every request to the app path("users") { // push subpath /users on the router get(UserController::getAll) // get controller for /users/ post(UserController::create) // post controller for /users/ before("{userId}*", UserController:userIdCheck) // handler that runs for every request to /users/{userId} as well as al subpaths path("{userId}") { // new subpath /{userId} on the router get(UserController::getOne) // get controller for /users/{userId} patch(UserController::update) // patch controller for /users/{userId} path("subpath") { ... } // push subpath /subpath on the router (and pop it immediately) } // pop subpath /{userId} on the router ws("events", UserController::webSocketEvents) // websocket controller for /users/events } // pop subpath /users on the router }.start(port) Request lifecycle The Javalin request lifecycle is pretty straightforward. The following snippet covers every place you can hook into: Javalin#before // runs first, can throw exception (which will skip any endpoint handlers) Config#accessManager // can be configured to run before endpoints (get/post/patch/etc) Javalin#get/post/patch/etc // runs second, can throw exception Javalin#after // runs third, can throw exception Javalin#error // runs fourth, can throw exception Javalin#exception // runs any time a handler throws (cannot throw exception) Config#requestLogger // runs after response is written to client Configuring Javalin To configure Javalin, you can adjust the JavalinConfig using a Consumer in the Javalin#create method: * Java * Kotlin var app = Javalin.create(config -> { config.http.generateEtags = true; config.http.asyncTimeout = 10_000L; config.routing.ignoreTrailingSlashes = true; config.staticFiles.add("/public", Location.CLASSPATH); }); val app = Javalin.create { config -> config.http.generateEtags = true config.http.asyncTimeout = 10_000L config.routing.ignoreTrailingSlashes = true config.staticFiles.add("/public", Location.CLASSPATH) }; Configuring Jetty Javalin is built on top of Jetty, and unlike many other web frameworks it doesn't try to make this a loose coupling. This gives you access to many nice features that are only available in Jetty: * Java * Kotlin var app = Javalin.create(config -> { config.jetty.server(() -> Server()); // set the Jetty Server config.jetty.sessionHandler(() -> SessionHandler()); // set the Jetty SessionHandler config.jetty.contextHandlerConfig(handler -> {}); // configure the Jetty ServletContextHandler config.jetty.wsFactoryConfig((factory) -> {}); // configure the Jetty WebSocketServletFactory }); val app = Javalin.create { config -> config.jetty.server { Server() } // set the Jetty Server config.jetty.sessionHandler { SessionHandler() } // set the Jetty SessionHandler config.jetty.contextHandlerConfig { handler -> } // configure the Jetty ServletContextHandler config.jetty.wsFactoryConfig { factory -> } // configure the Jetty WebSocketServletFactory } Session handling is a particularly useful Jetty feature, as can be seen in /tutorials/jetty-session-handling. Plugins There are many third-party open-source plugins available for Javalin, and as of Javalin 5 we're launching a plugin "marketplace" on javalin.io/plugins. OpenAPI support One of the most popular Javalin plugins is its OpenAPI integration: @OpenApi( path = "/api/v1/users", methods = [HttpMethod.POST], summary = "Register a user", tags = ["Users"], requestBody = OpenApiRequestBody( content = [OpenApiContent(RegistrationRequest::class)], required = true, description = "Data about the user" ), responses = [ OpenApiResponse(status = "200", ...), OpenApiResponse(status = "401", ...), ] ) fun register(context: Context) { // handler code goes here } What's changed since Javalin 4 The biggest change is that Javalin no longer works with Java 8. We have moved to Jetty 11, which requires Java 11. We've also restructured a bit, reworked configuration and futures, split out some of the modules into separate Maven artifacts, and fixed one or two bugs. You can read more in the migration guide: / migration-guide-javalin-4-to-5. 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. [SrGd0fI3UJ]Share on Twitter [BiPTcEnBgR]Share on Facebook Javalin is a Java and Kotlin web framework, open sourced under the Apache 2 license | Download | About | News | Contact | For educators Like Javalin? Star us x