Коды ответа и ошибки
Код ответа — первое, что видит клиент: 200 — всё хорошо, 404 — такого нет, 500 — сломался сервер. Хороший API отвечает правильным кодом и понятным текстом ошибки. В Javalin для этого есть три инструмента: метод ctx.status, готовые исключения-ответы и обработчики исключений в настройках.
Коды ответа
Код задаётся числом или константой из перечисления HttpStatus (пакет io.javalin.http):
// src/main/java/com/example/blog/Main.java
ctx.status(201);
ctx.status(HttpStatus.CREATED); // то же самое, но читается лучше| Код | Константа | Когда |
|---|---|---|
200 | OK | Обычный успешный ответ, ставится сам |
201 | CREATED | Создан новый объект |
204 | NO_CONTENT | Успех без тела, например после удаления |
400 | BAD_REQUEST | Клиент прислал неверные данные |
401 / 403 | UNAUTHORIZED / FORBIDDEN | Нет входа / нет прав |
404 | NOT_FOUND | Объекта нет |
500 | INTERNAL_SERVER_ERROR | Ошибка на сервере |
Готовые исключения
Чтобы прервать обработчик с ошибкой, бросьте исключение-ответ: NotFoundResponse, BadRequestResponse, ForbiddenResponse и другие. Добавим удаление поста:
// 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("Пост " + id + " не найден");
}
ctx.status(HttpStatus.NO_CONTENT);
});curl -i -X DELETE http://localhost:8080/posts/2
curl -i -X DELETE http://localhost:8080/posts/2Первый запрос вернёт 204 No Content, второй — 404 Not Found с текстом Пост 2 не найден. Если клиент просит JSON заголовком Accept: application/json, Javalin отвечает объектом:
{
"title": "Пост 2 не найден",
"status": 404,
"type": "https://javalin.io/documentation#notfoundresponse",
"details": {}
}
Свои обработчики исключений
Любое другое исключение Javalin превращает в 500 Server Error. Чтобы ответить по-своему, зарегистрируйте обработчик config.routes.exception. Добавьте в класс Main логгер и обработчик:
// src/main/java/com/example/blog/Main.java
private static final Logger log = LoggerFactory.getLogger(Main.class); // org.slf4j
// внутри Javalin.create(config -> { ... })
config.routes.exception(RuntimeException.class, (e, ctx) -> {
log.error("Ошибка запроса: {}", ctx.path(), e);
ctx.status(500).json(Map.of("error", "Что-то пошло не так"));
});Обработчик ловит RuntimeException и его наследников, кроме исключений-ответов вроде NotFoundResponse: те по-прежнему дают свой код. Проверим временным маршрутом:
// src/main/java/com/example/blog/Main.java
config.routes.get("/boom", ctx -> {
throw new IllegalStateException("База данных недоступна");
});curl -i http://localhost:8080/boomHTTP/1.1 500 Server Error
Content-Type: application/json
{"error":"Что-то пошло не так"}
В логе сервера при этом видно причину: ERROR com.example.blog.Main - Ошибка запроса: /boom и стек java.lang.IllegalStateException.
Обработчики по коду
config.routes.error(404, ...) срабатывает для любого ответа с кодом 404:
// src/main/java/com/example/blog/Main.java
config.routes.error(404, ctx -> ctx.json(Map.of("error", "Не найдено: " + ctx.path())));Запрос curl http://localhost:8080/nothing вернёт {"error":"Не найдено: /nothing"}. Учтите: этот обработчик заменяет и текст из NotFoundResponse, поэтому в проекте мы его не оставим.
Частая ошибка
Обработчик исключений без записи в лог прячет ошибку. Клиент видит {"error":"Что-то пошло не так"}, а в консоли сервера пусто — найти причину невозможно. Всегда передавайте исключение в логгер последним аргументом, как log.error(..., e) выше: тогда SLF4J напечатает полный стек.