Классы с аннотацией @Controller и @ControllerAdvice могут иметь методы, помеченные аннотацией @ExceptionHandler, для обработки исключений из методов контроллера, как показано в следующем примере:

Java
@Controller
public class SimpleController {
    // ...
    @ExceptionHandler
    public ResponseEntity<String> handle(IOException ex) {
        // ...
    }
}
Kotlin
@Controller
class SimpleController {
    // ...
    @ExceptionHandler
    fun handle(ex: IOException): ResponseEntity<String> {
        // ...
    }
}

Исключение может совпадать с распространяемым высокоуровневым исключением (например, прямо генерируемое IOException) или с вложенной причиной внутри исключения-обёртки (например, IOException, обернутое внутри IllegalStateException). Начиная с 5.3, это может совпадать на произвольных уровнях причин, тогда как ранее принималась во внимание только непосредственная причина.

Для совпадающих типов исключений предпочтительно объявлять целевое исключение в качестве аргумента метода, как показано в предыдущем примере. При совпадении нескольких методов исключения, как правило, предпочтительнее совпадение исключения корня, чем совпадение исключения причины. В частности, ExceptionDepthComparator используется для сортировки исключений на основе их глубины от типа сгенерированного исключения.

Кроме того, объявление аннотации может сужать типы исключений, которые должны совпадать, как показано в следующем примере:

Java
@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(IOException ex) {
    // ...
}
Kotlin
@ExceptionHandler(FileSystemException::class, RemoteException::class)
fun handle(ex: IOException): ResponseEntity<String> {
    // ...
}

Можно даже использовать список конкретных типов исключений с очень общей сигнатурой аргументов, как показано в следующем примере:

Java
@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(Exception ex) {
    // ...
}
Kotlin
@ExceptionHandler(FileSystemException::class, RemoteException::class)
fun handle(ex: Exception): ResponseEntity<String> {
    // ...
}

Различие между сопоставлением исключений корня и причины может удивить.

В варианте IOException, показанном ранее, метод обычно вызывается с использованием фактического экземпляра FileSystemException или RemoteException в качестве аргумента, поскольку оба они расширяются от IOException. Однако, если любое такое совпадающее исключение распространяется внутри исключения-обёртки, которое само является IOException, то переданный экземпляр исключения является этим исключением-обёрткой.

В варианте handle(Exception) логика работы еще проще. Её вызов происходит при помощи исключения-обёртки в сценарии обёртывания, при этом фактически соответствующее исключение можно найти через ex.getCause() в этом случае. Переданное исключение является фактическим экземпляром FileSystemException или RemoteException только в том случае, если они генерируются как высокоуровневые исключения.

Обычно рекомендуется давать как можно больше конкретики в сигнатуре аргументов, чтобы снизить вероятность несоответствия между типами исключений корня и причины. Рассмотрим возможность разбить метод с несколькими совпадениями на отдельные методы, аннотированные @ExceptionHandler, каждый из которых соответствует одному определенному типу исключения через свою сигнатуру.

В конфигурации с несколькими аннотациями @ControllerAdvice мы рекомендуем объявлять отображения основных исключений корня в аннотации @ControllerAdvice в соответствующем приоритетном порядке. Хотя сопоставление исключения корня предпочтительнее, чем сопоставление исключения причины, определение происходит среди методов данного контроллера или класса, аннотированного @ControllerAdvice. Это означает, что совпадение причины для бина, аннотированного @ControllerAdvice, с более высоким приоритетом предпочтительнее любого совпадения (например, корня) для бина, аннотированного @ControllerAdvice с более низким приоритетом.

И последнее, но не менее важное: реализация метода, помеченного аннотацией @ExceptionHandler, может отказаться работать с данным экземпляром исключения, повторно сгенерировав его в исходной форме. Это полезно в тех случаях, когда вас интересуют исключительно совпадения на корневом уровне или совпадения в определенном контексте, который не может быть определен статически. Повторная генерация исключения распространяется по оставшейся цепочке разрешения, как если бы данный метод с аннотацией @ExceptionHandler не совпал с первым.

Поддержка методов с аннотацией @ExceptionHandler в Spring MVC построена на уровне DispatcherServlet, механизме HandlerExceptionResolver.

Аргументы метода

Методы с аннотацией @ExceptionHandler поддерживают следующие аргументы:

Аргумент метода Описание

Тип исключения

Обеспечивает доступ к сгенерированному исключению.

HandlerMethod

Обеспечивает доступ к методу контроллера, который сгенерировал исключение.

WebRequest, NativeWebRequest

Типизированный доступ к параметрам запроса и атрибутам запроса и сессии без прямого использования Servlet API.

javax.servlet.ServletRequest, javax.servlet.ServletResponse

Выберите любой конкретный тип запроса или ответа (например, ServletRequest или HttpServletRequest, либо же MultipartRequest или MultipartHttpServletRequest из Spring).

javax.servlet.http.HttpSession

Обеспечивает наличие сессии. Как следствие, такой аргумент никогда не является null.
Обратите внимание, что доступ к сессии не является потокобезопасным. Рассмотрите возможность установки флага synchronizeOnSession экземпляра RequestMappingHandlerAdapter в true, если нескольким запросам разрешен одновременный доступ к сессии.

java.security.Principal

Текущий аутентифицированный пользователь – возможно, конкретный класс реализации Principal, если он известен.

HttpMethod

HTTP-метод запроса.

java.util.Locale

Текущие региональные настройки запроса, определяемые наиболее конкретным доступным LocaleResolver – по сути, сконфигурированным LocaleResolver или LocaleContextResolver.

java.util.TimeZone, java.time.ZoneId

Часовой пояс, связанный с текущим запросом, определенный LocaleContextResolver.

java.io.OutputStream, java.io.Writer

Обеспечивает доступ к сырому телу ответа, как это предусмотрено Servlet API.

java.util.Map, org.springframework.ui.Model, org.springframework.ui.ModelMap

Обеспечивает доступ к модели для выдачи ответа на ошибку. Всегда пустой.

RedirectAttributes

Задайте атрибуты, используемые в случае перенаправления - (которые будут добавлены к строке запроса) и flash атрибуты для временного хранения до поступления запроса после перенаправления.

@SessionAttribute

Предназначен для обеспечения доступа к любому атрибуту сессии, в отличие от атрибутов модели, хранящихся в сессии в результате объявления аннотации @SessionAttributes на уровне класса.

@RequestAttribute

Предназначен для обеспечения доступа к атрибутам запроса.

Возвращаемые значения

Методы с аннотацией @ExceptionHandler поддерживают следующие возвращаемые значения:

Return value Description

@ResponseBody

Возвращаемое значение преобразуется через экземпляры HttpMessageConverter и записывается в ответе.

HttpEntity<B>, ResponseEntity<B>

Возвращаемое значение задает, что полный ответ (включая HTTP-заголовки и тело) будет преобразован через экземпляры HttpMessageConverter и записан в ответ.

String

Имя представления, которое должно быть распознано с помощью реализаций ViewResolver и использоваться вместе с неявной моделью – определяется через объекты команд и методы с аннотацией @ModelAttribute. Метод обработчика также может программно доработать модель, объявив аргумент Model (описанный ранее).

View

Экземпляр View, предназначенный для визуализации вместе с неявной моделью – определяется через объекты команд и методы с аннотацией @ModelAttribute. Метод обработчика может также программно доработать модель, объявив аргумент Model (описанный ранее).

java.util.Map, org.springframework.ui.Model

Атрибуты для добавления в неявную модель с именем представления, неявно определенным через RequestToViewNameTranslator.

@ModelAttribute

Атрибут, добавляемый к модели с именем представления, неявно определенным через RequestToViewNameTranslator.

Обратите внимание, что аннотация @ModelAttribute является необязательной. См. раздел "Любое другое возвращаемое значение" в конце этой таблицы.

ModelAndView object

Используемые атрибуты представления и модели и, при необходимости, статус ответа.

void

Считается, что метод с возвращаемым типом void (или возвращаемым значениемnull) полностью обработал ответ, если он также имеет аргумент ServletResponse или OutputStream, или аннотацию @ResponseStatus. То же самое верно, если контроллер выполнил положительную проверку ETag или временной метки lastModified.

Если ни одно из вышеперечисленных значений не верно, возвращаемый тип void может также указывать на "отсутствие тела ответа" для REST-контроллеров или выбор имени представления по умолчанию для HTML-контроллеров.

Любое другое возвращаемое значение

Если возвращаемое значение не соответствует ни одному из вышеперечисленных и не является простым типом (как определено BeanUtils#isSimpleProperty), по умолчанию оно считается атрибутом модели, который должен быть добавлен в модель. Если это простой тип, то он остается неразрешенным.

Работа с исключениями в REST API

Общим требованием для служб REST является включение информации об ошибке в тело ответа. Spring Framework не автоматически этого не делает, поскольку представление информации об ошибке в теле ответа зависит от конкретного приложения. Однако аннотация @RestController может использовать методы, помеченные аннотацией @ExceptionHandler, с возвращаемым значением ResponseEntity для установки статуса и тела ответа. Такие методы также могут быть объявлены в классах, аннотированных @ControllerAdvice, чтобы можно было применять их глобально.

Приложения, реализующие глобальную обработку исключений с подробным описанием ошибки в теле ответа, должны учитывать возможность расширения ResponseEntityExceptionHandler, который обеспечивает обработку исключений, возникающих в Spring MVC, и предоставляет перехватчики для настройки тела ответа. Чтобы воспользоваться этим, создайте подкласс ResponseEntityExceptionHandler, аннотируйте его при помощи @ControllerAdvice, переопределите необходимые методы и объявите его как бин Spring.