ExceptionHandler
@ExceptionHandler перехватывает исключения и превращает их в понятный HTTP-ответ, вместо того чтобы клиент получал страшный стектрейс или просто 500 Internal Server Error без объяснений.
@RestControllerAdvice // применяется ко всем контроллерам сразу
class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
ResponseEntity<String> handleNotFound(UserNotFoundException e) {
return ResponseEntity.status(404).body(e.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class) // ошибка валидации @Valid
ResponseEntity<String> handleValidation(MethodArgumentNotValidException e) {
return ResponseEntity.status(400).body("Некорректные данные: " + e.getMessage());
}
}
Когда любой контроллер бросает UserNotFoundException — выполнение переходит в этот обработчик, и клиент получает чистый 404 с понятным сообщением, а не голый 500.
Копнуть глубже
@RestControllerAdvice против @ExceptionHandler прямо в контроллере. Можно поставить @ExceptionHandler в самом контроллере — он сработает только для исключений из методов этого контроллера. @RestControllerAdvice — глобальный, перехватывает исключения из любого контроллера приложения, что обычно и нужно: единая, согласованная обработка ошибок по всему API.
Хороший формат ошибки — не просто текст, а структурированный объект, который клиент (фронтенд) может разобрать программно:
record ErrorResponse(String code, String message, Instant timestamp) {}
@ExceptionHandler(UserNotFoundException.class)
ResponseEntity<ErrorResponse> handleNotFound(UserNotFoundException e) {
return ResponseEntity.status(404)
.body(new ErrorResponse("USER_NOT_FOUND", e.getMessage(), Instant.now()));
}
Без глобального обработчика любое незапланированное исключение приводит к 500 с полным стектрейсом в ответе — это и неинформативно для клиента, и потенциально раскрывает внутренние детали реализации (риск безопасности).
• разницу между обработчиком в контроллере и `@RestControllerAdvice` (если дошёл до 2-го слоя).