Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Status codes and errors

The response code is the first thing a client sees: 200 means all good, 404 means no such thing, 500 means the server broke. A good API answers with the right code and a clear error text. Javalin has three tools for this: the ctx.status method, ready-made response exceptions and exception handlers in the configuration.

Response codes

A code is set with a number or a constant from the HttpStatus enum (package io.javalin.http):

// src/main/java/com/example/blog/Main.java
ctx.status(201);
ctx.status(HttpStatus.CREATED);   // the same, but reads better
CodeConstantWhen
200OKAn ordinary success, set automatically
201CREATEDA new object was created
204NO_CONTENTSuccess without a body, e.g. after deleting
400BAD_REQUESTThe client sent wrong data
401 / 403UNAUTHORIZED / FORBIDDENNot logged in / no rights
404NOT_FOUNDThe object does not exist
500INTERNAL_SERVER_ERRORAn error on the server

Ready-made exceptions

To stop a handler with an error, throw a response exception: NotFoundResponse, BadRequestResponse, ForbiddenResponse and others. Let us add deleting a post:

// src/main/java/com/example/blog/Main.java
config.routes.delete("/posts/{id}", ctx -> {
    long id = ctx.pathParamAsClass("id", Long.class).get();
    if (!store.delete(id)) {
        throw new NotFoundResponse("Post " + id + " not found");
    }
    ctx.status(HttpStatus.NO_CONTENT);
});
curl -i -X DELETE http://localhost:8080/posts/2
curl -i -X DELETE http://localhost:8080/posts/2

The first request returns 204 No Content, the second 404 Not Found with the text Post 2 not found. If the client asks for JSON with the Accept: application/json header, Javalin answers with an object:

{
    "title": "Post 2 not found",
    "status": 404,
    "type": "https://javalin.io/documentation#notfoundresponse",
    "details": {}
}

Your own exception handlers

Javalin turns any other exception into 500 Server Error. To answer your own way, register a config.routes.exception handler. Add a logger and a handler to the Main class:

// src/main/java/com/example/blog/Main.java
private static final Logger log = LoggerFactory.getLogger(Main.class);   // org.slf4j
 
// inside Javalin.create(config -> { ... })
config.routes.exception(RuntimeException.class, (e, ctx) -> {
    log.error("Request failed: {}", ctx.path(), e);
    ctx.status(500).json(Map.of("error", "Something went wrong"));
});

The handler catches RuntimeException and its subclasses, except response exceptions like NotFoundResponse: those still give their own code. Let us check it with a temporary route:

// src/main/java/com/example/blog/Main.java
config.routes.get("/boom", ctx -> {
    throw new IllegalStateException("Database is down");
});
curl -i http://localhost:8080/boom
HTTP/1.1 500 Server Error
Content-Type: application/json

{"error":"Something went wrong"}

The server log shows the reason: ERROR com.example.blog.Main - Request failed: /boom and the java.lang.IllegalStateException stack trace.

Handlers by code

config.routes.error(404, ...) runs for any response with code 404:

// src/main/java/com/example/blog/Main.java
config.routes.error(404, ctx -> ctx.json(Map.of("error", "Not found: " + ctx.path())));

The request curl http://localhost:8080/nothing returns {"error":"Not found: /nothing"}. Keep in mind that this handler also replaces the text from NotFoundResponse, so we do not keep it in the project.

Common mistake

An exception handler that does not log hides the error. The client sees {"error":"Something went wrong"}, the server console is empty, and the cause cannot be found. Always pass the exception to the logger as the last argument, like log.error(..., e) above: then SLF4J prints the full stack trace.

Thanks for reading!

Donate to the site — we work hard for you.