Обработка ошибок
Сейчас запрос несуществующего поста возвращает 404 с пустым телом. Клиенту API полезнее получить объяснение: что не так и с чем. Для этого код бросает своё исключение, а обработчик исключений (exception mapper) превращает его в HTTP-ответ с нужным статусом и телом. Так логика ошибок собрана в одном месте, а ресурсы остаются короткими.
Своё исключение
// src/main/java/com/example/blog/PostNotFoundException.java
package com.example.blog;
public class PostNotFoundException extends RuntimeException {
public PostNotFoundException(long id) {
super("Пост " + id + " не найден");
}
}Исключение наследует RuntimeException, поэтому его не нужно объявлять в throws. Для тела ответа заведём запись:
// src/main/java/com/example/blog/ErrorMessage.java
package com.example.blog;
public record ErrorMessage(String error) {
}Обработчик
В Quarkus REST обработчик — это метод с аннотацией @ServerExceptionMapper. Тип параметра говорит, какое исключение он ловит:
// src/main/java/com/example/blog/ExceptionMappers.java
package com.example.blog;
import jakarta.ws.rs.core.Response;
import org.jboss.resteasy.reactive.RestResponse;
import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
public class ExceptionMappers {
@ServerExceptionMapper
public RestResponse<ErrorMessage> mapNotFound(PostNotFoundException e) {
return RestResponse.status(Response.Status.NOT_FOUND, new ErrorMessage(e.getMessage()));
}
}RestResponse<ErrorMessage> — типизированный ответ Quarkus REST: статус плюс объект, который станет JSON. Класс с обработчиками может лежать где угодно в проекте — Quarkus найдёт его при сборке.
Бросаем исключение в ресурсе
В PostResource замените NotFoundException на своё исключение:
// src/main/java/com/example/blog/PostResource.java
@GET
@Path("/{id}")
public Post get(@PathParam("id") long id) {
return postService.findById(id)
.orElseThrow(() -> new PostNotFoundException(id));
}
@DELETE
@Path("/{id}")
public Response delete(@PathParam("id") long id) {
if (!postService.delete(id)) {
throw new PostNotFoundException(id);
}
return Response.noContent().build();
}curl -i http://localhost:8080/posts/99HTTP/1.1 404 Not Found
Content-Type: application/json;charset=UTF-8
{"error":"Пост 99 не найден"}
Стандартный вариант
В стандарте Jakarta REST обработчик — отдельный класс с аннотацией @Provider, реализующий ExceptionMapper. Quarkus поддерживает и его:
// src/main/java/com/example/blog/IllegalArgumentMapper.java
package com.example.blog;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
@Provider
public class IllegalArgumentMapper implements ExceptionMapper<IllegalArgumentException> {
@Override
public Response toResponse(IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(new ErrorMessage(e.getMessage()))
.build();
}
}Теперь любое IllegalArgumentException из ресурса станет ответом 400 с JSON {"error":"..."}. Вариант с @ServerExceptionMapper короче, поэтому в Quarkus используют в основном его.
Какой обработчик сработает
Если подходят несколько обработчиков, Quarkus выбирает тот, чей тип ближе всего к брошенному исключению. Ошибки проверки данных и стандартные исключения вроде NotFoundException уже имеют встроенные обработчики.
Частая ошибка
Хочется написать один обработчик на всё — для Exception:
// src/main/java/com/example/blog/ExceptionMappers.java
@ServerExceptionMapper
public RestResponse<ErrorMessage> mapAll(Exception e) {
return RestResponse.status(Response.Status.INTERNAL_SERVER_ERROR, new ErrorMessage("Что-то пошло не так"));
}Но он перехватит и встроенные исключения Jakarta REST:
curl -i http://localhost:8080/nothing-here
curl -i -X PUT http://localhost:8080/posts/1HTTP/1.1 500 Internal Server Error
Content-Type: application/json;charset=UTF-8
{"error":"Что-то пошло не так"}
Оба запроса должны были вернуть 404 и 405, а получили 500. Делайте обработчики для своих конкретных исключений. Если общий обработчик всё же нужен, пропускайте стандартные ошибки с их собственным статусом:
// src/main/java/com/example/blog/ExceptionMappers.java
@ServerExceptionMapper
public RestResponse<ErrorMessage> mapAll(Exception e) {
if (e instanceof WebApplicationException w) {
return RestResponse.status(w.getResponse().getStatusInfo());
}
return RestResponse.status(Response.Status.INTERNAL_SERVER_ERROR, new ErrorMessage("Что-то пошло не так"));
}С такой проверкой (импорт jakarta.ws.rs.WebApplicationException) неизвестный адрес снова получает 404, а неверный метод — 405.