Какие способы централизованной обработки исключений предоставляет Spring MVC?

«Какие способы централизованной обработки исключений предоставляет Spring MVC?» — вопрос из категории Spring, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Spring MVC предлагает несколько механизмов для глобальной и контроллер-специфичной обработки исключений:

  1. Аннотация @ExceptionHandler на уровне контроллера.

    • Что делает: Позволяет обрабатывать исключения, выброшенные в методах одного контроллера.
    • Пример:
      @RestController
      @RequestMapping("/api/users")
      public class UserController {
      @ExceptionHandler(UserNotFoundException.class)
      public ResponseEntity<String> handleUserNotFound(UserNotFoundException ex) {
          return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
      }
      @GetMapping("/{id}")
      public User getUser(@PathVariable Long id) {
          // Может выбросить UserNotFoundException
          return userService.findById(id);
      }
      }
  2. @ControllerAdvice (или @RestControllerAdvice) — глобальный обработчик.

    • Что делает: Класс, методы которого (@ExceptionHandler, @InitBinder, @ModelAttribute) применяются ко всем контроллерам. Это основной способ глобальной обработки.
    • Пример:
      @RestControllerAdvice
      public class GlobalExceptionHandler {
      @ExceptionHandler(ResourceNotFoundException.class)
      @ResponseStatus(HttpStatus.NOT_FOUND)
      public ErrorResponse handleNotFound(ResourceNotFoundException ex) {
          return new ErrorResponse("NOT_FOUND", ex.getMessage());
      }
      @ExceptionHandler(MethodArgumentNotValidException.class)
      @ResponseStatus(HttpStatus.BAD_REQUEST)
      public ErrorResponse handleValidation(MethodArgumentNotValidException ex) {
          // Обработка ошибок валидации @Valid
          List<String> errors = ex.getBindingResult()
                                  .getFieldErrors()
                                  .stream()
                                  .map(FieldError::getDefaultMessage)
                                  .collect(Collectors.toList());
          return new ErrorResponse("VALIDATION_ERROR", errors.toString());
      }
      @ExceptionHandler(Exception.class)
      @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
      public ErrorResponse handleAll(Exception ex) {
          return new ErrorResponse("INTERNAL_ERROR", "Произошла внутренняя ошибка");
      }
      }
  3. ResponseStatusException (с Spring 5).

    • Что делает: Программный способ выброса исключения с нужным HTTP-статусом и сообщением без создания кастомных классов исключений.
    • Пример:
      @GetMapping("/{id}")
      public User getUser(@PathVariable Long id) {
      return userRepository.findById(id)
              .orElseThrow(() -> new ResponseStatusException(
                      HttpStatus.NOT_FOUND, "User not found"
              ));
      }

Best Practice: Используйте @RestControllerAdvice для единой точки обработки всех бизнес- и системных исключений в REST API, возвращая структурированные объекты ошибок (ErrorResponse).