Какие аннотации используются для обработки исключений в Spring
Что проверяют
Умеете ли вы централизовать обработку ошибок и понимаете ли, где кончается зона ответственности Spring MVC. Уточняющий вопрос почти неизбежен: какой обработчик победит, если подходят несколько, и почему ваш @RestControllerAdvice не поймал ошибку из фильтра Spring Security.
Как отвечать
@ExceptionHandler: помечает метод, который вызовется при указанном исключении. Внутри контроллера ловит только его ошибки, один метод может принимать несколько классов через@ExceptionHandler({A.class, B.class});@ControllerAdvice: выносит обработчики сразу на все контроллеры. Область сужается параметрамиbasePackages,assignableTypes,annotations, порядок между несколькими advice задаёт@Order;@RestControllerAdvice: то же плюс@ResponseBody, для REST вариант по умолчанию;@ResponseStatus: вешается на класс исключения или на метод обработчика и задаёт код ответа. Программный аналог этоResponseStatusException;ResponseEntityExceptionHandler: базовый класс для advice, от него наследуются, чтобы переопределить обработку встроенных исключений вродеMethodArgumentNotValidException.
Про формат ошибки. Начиная со Spring Framework 6 есть поддержка Problem Details по RFC 9457: тип ProblemDetail, включение флагом spring.mvc.problemdetails.enabled=true.
Про выбор обработчика: побеждает метод с ближайшим по иерархии типом исключения, локальный обработчик контроллера важнее глобального. Граница: исключения из сервлет-фильтров до DispatcherServlet в advice не попадают, у Spring Security за это отвечают AuthenticationEntryPoint и AccessDeniedHandler, непойманное уходит на /error.
Где спотыкаются
Ловят Exception.class одним методом и отдают клиенту e.getMessage(). В ответ утекают фрагменты SQL и внутренние пути, а в мониторинге всё выглядит одинаковыми пятисотками.
Ставят @ResponseStatus на исключение, а в обработчике возвращают ResponseEntity с другим кодом, и ищут, почему клиент видит не тот статус. Побеждает статус возвращённого ответа.
Заводят несколько advice с пересекающимися обработчиками и без @Order. Срабатывает не тот, что ожидали, и порядок меняется от сборки к сборке.