Обработка ошибок и статус-кодов
Статус-коды ошибок: Возвращайте корректные коды HTTP для ошибок (400 – неверный запрос, 404 – не найдено, 500 – ошибка сервера и т.д.).
Исключения в Spring: Spring MVC сам устанавливает код ответа для ряда исключений (например, отсутствие ресурса дает 404, неправильный метод – 405).
ResponseStatusException: можно выбросить new ResponseStatusException(HttpStatus.NOT_FOUND, "Message") в методе, чтобы вернуть нужный статус и сообщение без дополнительного кода.
@ExceptionHandler/@ControllerAdvice: глобальная обработка ошибок – позволяет перехватывать исключения и формировать единообразный JSON-ответ с ошибкой.
Современный подход: поддержка Problem Details (RFC 7807) – стандартный формат ошибки JSON в Spring 6 (включается опцией spring.mvc.problemdetails.enabled=true).
Пояснение:
Корректная обработка ошибок – существенная часть разработки API. Клиенту важно получать понятные и стандартизированные ответы об ошибках. В REST принято использовать комбинацию HTTP-кода и тела ответа с описанием ошибки.
Spring MVC многое делает автоматически. Например, если в контроллере не найден подходящий маршрут для запроса, Spring выбросит исключение NoSuchRequestHandlingMethodException – по умолчанию это вернется как 404 Not Found. Если HTTP метод не поддерживается контроллером – HttpRequestMethodNotSupportedException – получим 405 Method Not Allowed, и так далее. Встроенный резолвер исключений (DefaultHandlerExceptionResolver) покрывает много стандартных случаев и выставляет соответствующие коды. Однако по умолчанию тело ответа при таких ошибках пустое или содержит минимальную информацию, что не всегда удобно.
При разработке API мы хотим не только правильный код, но и информативное сообщение об ошибке в формате JSON. Рассмотрим подходы, которые предоставляет Spring:
Бросать специальные исключения с аннотацией @ResponseStatus. Если вы определили собственное исключение и пометили класс аннотацией @ResponseStatus(code = HttpStatus.NOT_FOUND), то при выбросе этого исключения Spring автоматически вернет ответ с кодом. Пример:
@ResponseStatus(HttpStatus.NOT_FOUND)
class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
Если внутри метода контроллера сделать throw new ResourceNotFoundException("User not found"), то клиент получит 404. Однако тело ответа по умолчанию будет содержать только стандартную Spring-ошибку (в Spring Boot до версии 3 – это JSON с полями timestamp, status, error, message…). Этот подход удобен для простых случаев, но не позволяет динамически менять текст ошибки или передавать дополнительные данные без написания обработчика.
Использовать ResponseStatusException (появился в Spring 5) для быстрого проброса ошибки из тела метода контроллера. Это класс исключения, который можно сконструировать с конкретным HttpStatus и сообщением. Например:
import org.springframework.web.server.ResponseStatusException;
@GetMapping("/items/{id}")
public Item getItem(@PathVariable Long id) {
return itemRepo.findById(id)
.orElseThrow(() ->
new ResponseStatusException(HttpStatus.NOT_FOUND, "Item not found")
);
}
Здесь, если элемент не найден, выбрасывается ResponseStatusException: Spring перехватит его и отправит ответ с кодом 404. Сообщение "Item not found" также будет включено в ответ (в старых версиях Spring Boot – в поле "message" JSON-ошибки, если включить подробности ошибок). Этот способ очень лаконичен для типичных REST-ситуаций (не нашли ресурс – вернуть 404, нет доступа – 403 и т.д.). На практике, Spring Boot по умолчанию для ResponseStatusException возвращает JSON примерно такого вида:
{
"timestamp": "2025-10-22T13:30:45.123+00:00",
"status": 404,
"error": "Not Found",
"message": "Item not found",
"path": "/api/items/999"
}
Поля могут немного различаться в зависимости от версии и настроек (начиная с Spring 3.2+ можно настраивать показ сообщения через server.error.include-message). Тем не менее, разработчик может предоставить кастомное сообщение, а Spring сформирует остальное.
Локальные обработчики исключений (@ExceptionHandler). Внутри контроллера можно создать метод, помеченный @ExceptionHandler(SomeException.class), который будет вызываться, если во время выполнения методов этого контроллера выброшено указанное исключениеhabr.com
. Этот метод может вернуть ResponseEntity или объект ошибки. Например:
@RestController
public class UserController {
@GetMapping("/users/{id}")
public User getUser(...) throws UserNotFoundException { ... }
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException ex) {
ErrorResponse error = new ErrorResponse("User not found", ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
}
Здесь при выбросе UserNotFoundException в любом методе этого контроллера управление перейдет в handleUserNotFound. Мы сформировали свою структуру ошибки (ErrorResponse – DTO с полями, например, error и details) и вернули 404. Недостаток подхода в том, что такой обработчик привязан к конкретному контроллеру. В больших приложениях дублировать это в каждом контроллере неудобно.
Глобальные обработчики (@ControllerAdvice). Это специализированный класс, помеченный @ControllerAdvice, который может содержать методы с @ExceptionHandler для всего приложения сразу. Такой глобальный обработчик перехватит исключения из любых контроллеров, что удобно для единообразного оформления ошибок. Например:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationError(MethodArgumentNotValidException ex) {
// собираем информацию о всех ошибках валидации
String message = "Validation failed";
List<String> details = ex.getBindingResult().getFieldErrors()
.stream()
.map(err -> err.getField() + " " + err.getDefaultMessage())
.toList();
ErrorResponse error = new ErrorResponse(message, details);
return ResponseEntity.badRequest().body(error); // 400
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleOtherErrors(Exception ex) {
ErrorResponse error = new ErrorResponse("Server error", ex.getMessage());
return ResponseEntity.status(500).body(error);
}
}
В этом примере первый метод ловит ошибки валидации (превращая их в 400 с перечислением полей), а второй – любой другой Exception, возвращая 500. С глобальным обработчиком мы можем централизованно задать формат всех ошибок (например, поля error, details, timestamp и т.п.). Spring гарантирует, что если исключение перехвачено нашим @ExceptionHandler, то стандартный механизм уже не будет трогать его. Поэтому можно полностью переопределить ответ.
Начиная с Spring Boot 3 / Spring Framework 6 (2022) появился встроенный механизм для единообразных ошибок – формат Problem Details по RFC 7807. Spring предлагает класс ProblemDetail и интерфейс ErrorResponse для формирования стандартизированного ответа об ошибке. Этот формат включает поля: type (URI с типом проблемы), title (краткое название ошибки), status (HTTP-код), detail (детальное описание) и instance (URI запроса или контекста). При включении настройки spring.mvc.problemdetails.enabled=true, Spring автоматически начинает возвращать ошибки в формате ProblemDetails там, где это возможно. Например, 404 ошибка может выглядеть как:
{
"type": "https://errors.example.com/not-found",
"title": "Not Found",
"status": 404,
"detail": "User with ID 123 was not found",
"instance": "/api/users/123"
}
Использование Problem Details – современная практика, упрощающая клиентам разбор ошибок. Вы сами можете вернуть ProblemDetail из своего @ExceptionHandler, или бросить ResponseStatusException – Spring преобразует ее к ProblemDetail автоматически, если включен данный режим. На 2025 год это считается бест практис для новых API, так как соответствует открытым стандартам и легко расширяется (можно добавлять свои поля, например, errors со списком ошибок валидации).
В заключение, при обработке ошибок REST API важно:
Отправлять понятный HTTP статус-код, соответствующий ситуации.
Давать в теле ответа читаемое описание проблемы (на английском для программных ошибок, либо локализованное сообщение, если API ориентировано на прямое взаимодействие с пользователем).
Не раскрывать лишнего – подробный stack trace или внутренние детали ошибки лучше не выдавать в продакшене (Spring Boot по умолчанию их скрывает).
Логировать ошибки на сервере для отладки, а клиенту возвращать аккуратное сообщение.
Spring MVC + Boot предоставляет все инструменты для этого: либо конфигурацией по умолчанию, либо через расширение ControllerAdvice. Следуя современным практикам, вы сможете сделать ответы ошибок в вашем API предсказуемыми и информативными, что улучшит взаимодействие с клиентами и облегчит интеграцию.
Это был обзор REST API на Spring MVC: мы рассмотрели принципы REST, создание контроллеров, привязку параметров, возврат JSON-данных и обработку ошибок со статус-кодами. Далее вы можете углубиться в темы безопасности (Spring Security для защиты REST API), документации (Swagger/OpenAPI) и тестирования (например, с помощью Spring Boot Test и REST-assured) для полного владения стеком разработки REST-сервисов.
