Before and after handlers
Sometimes the same code is needed for many routes: log the request, add a header, check the login. Copying it into every handler is inconvenient. For this Javalin has before and after handlers: the first run before the main handler, the second after it. Other frameworks call them filters or middleware.
Execution order
Every request goes through the same chain:
- All matching
beforehandlers, in the order they were declared. - One main route handler (the endpoint).
- Exception and error handlers, if something went wrong.
- All matching
afterhandlers — they run even after an error.
Logging every request
Add two handlers to the configuration block. before stores the start time in a request attribute, after prints the method, path, code and duration:
// src/main/java/com/example/blog/Main.java
config.routes.before(ctx -> ctx.attribute("start", System.nanoTime()));
config.routes.after(ctx -> {
long start = ctx.attribute("start");
long ms = (System.nanoTime() - start) / 1_000_000;
System.out.println(ctx.method() + " " + ctx.path() + " -> " + ctx.statusCode() + " (" + ms + " ms)");
});An attribute is a value that lives only during one request: ctx.attribute("name", value) stores it, ctx.attribute("name") reads it. Call a few addresses and look at the server console:
GET /posts -> 200 (5 ms)
GET /posts/99 -> 404 (2 ms)
POST /posts -> 201 (45 ms)
DELETE /posts/3 -> 204 (0 ms)
The first JSON request is always slower: on its first call Jackson prepares its internal structures.
Handlers for some addresses
You can pass a path as the first argument. The star * means "anything after this":
// src/main/java/com/example/blog/Main.java
config.routes.before("/posts/*", ctx -> System.out.println("Post request: " + ctx.path()));
config.routes.after(ctx -> ctx.header("X-Blog", "Javalin"));The first handler runs for /posts/1 and /posts/new, the second adds the X-Blog: Javalin header to all responses, including 404 responses.
before and beforeMatched
A before handler runs for any request, even when there is no route and the response will be 404. If the code is needed only for existing routes, use beforeMatched and afterMatched. In chapter 4 we build a permission check on beforeMatched.
| Method | When it runs |
|---|---|
before, after | For all requests |
beforeMatched, afterMatched | Only if a route or a static file was found |
Common mistake
The path /posts in before matches only the address /posts, not everything that starts with it:
// src/main/java/com/example/blog/Main.java
config.routes.before("/posts", ctx -> System.out.println("before /posts: " + ctx.path()));The request /posts prints the line, while the request /posts/1 does not. To cover nested addresses, add a second handler with the path /posts/*.