Как обрабатывать ошибки в контроллере
Что проверяют
Знаете ли вы, как в Spring вынести обработку ошибок из контроллеров в одно место и какой ответ при этом получает клиент. Следом обычно спрашивают про ошибки валидации и про то, что показывать пользователю при 500.
Как отвечать
Основа: контроллер не ловит исключения сам, а бросает доменные (OrderNotFoundException, InsufficientFundsException), и в HTTP-ответ их переводит глобальный обработчик.
@RestControllerAdvice: это@ControllerAdviceплюс@ResponseBody. Внутри методы с@ExceptionHandler, каждый отвечает за свой тип. Если подходят несколько, Spring выбирает самый близкий по иерархии исключений;- формат ответа: начиная со Spring 6 и Boot 3 есть
ProblemDetail, реализация RFC 9457, преемника RFC 7807: поляtype,title,status,detail,instance. Базовый классResponseEntityExceptionHandlerуже отдаёт так стандартные ошибки Spring MVC, а в Boot это включается свойствомspring.mvc.problemdetails.enabled; - валидация:
@Validна теле запроса бросаетMethodArgumentNotValidException, из него достают список полей с ошибками и отдают400; - коды: не найдено
404, конфликт состояния409, нарушение бизнес-правила422или400, всё непредвиденное500; - точечные случаи:
ResponseStatusExceptionили@ResponseStatusна классе исключения, когда полноценный обработчик избыточен.
При 500 клиенту уходит общий текст и идентификатор трассировки, а стек и детали идут в лог через SLF4J. Логировать ошибку стоит один раз, в обработчике, а не на каждом слое.
Где спотыкаются
Ловят Exception в каждом методе контроллера и возвращают 200 с полем error. Клиент и мониторинг видят успех, хотя запрос упал.
Отдают наружу ex.getMessage() для непредвиденных ошибок. Так в ответ попадают текст SQL, имена таблиц и внутренние пути.
Не знают, что исключения из фильтров, например из цепочки Spring Security, до @ControllerAdvice не доходят: они возникают раньше DispatcherServlet. Для них нужны свои AuthenticationEntryPoint и AccessDeniedHandler.