← Назад к вопросам

Что такое @ExceptionHandler?

1.7 Middle🔥 201 комментариев
#REST API и микросервисы#Spring Framework

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

@ExceptionHandler: обработка исключений в Spring

@ExceptionHandler — это аннотация в Spring Framework, которая определяет метод для обработки определённого типа исключений в контроллерах. Она позволяет централизованно обрабатывать ошибки и возвращать понятные ответы клиентам.

Основное назначение

@ExceptionHandler решает следующие задачи:

  • Централизованная обработка ошибок — вместо try-catch в каждом методе
  • Единообразный формат ошибок — все ошибки возвращаются в одинаковом формате
  • Правильные HTTP статусы — 400, 404, 500 в зависимости от типа ошибки
  • Логирование ошибок — все исключения логируются в одном месте
  • Безопасность — скрывает внутренние детали ошибок от клиента

Простой пример

// Кастомное исключение
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}

// DTO для ответа об ошибке
public class ErrorResponse {
    private int status;
    private String message;
    private LocalDateTime timestamp;

    public ErrorResponse(int status, String message) {
        this.status = status;
        this.message = message;
        this.timestamp = LocalDateTime.now();
    }
    // getters
}

// Контроллер с @ExceptionHandler
@RestController
@RequestMapping("/api/products")
public class ProductController {
    
    @GetMapping("/{id}")
    public ResponseEntity<ProductDTO> getProduct(@PathVariable Long id) {
        return productService.findById(id)
            .map(ResponseEntity::ok)
            .orElseThrow(() -> new ResourceNotFoundException(
                "Товар с ID " + id + " не найден"
            ));
    }
    
    // Обработчик для ResourceNotFoundException
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleResourceNotFound(
            ResourceNotFoundException ex) {
        ErrorResponse error = new ErrorResponse(404, ex.getMessage());
        return ResponseEntity.status(404).body(error);
    }
    
    // Обработчик для других исключений
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<ErrorResponse> handleIllegalArgument(
            IllegalArgumentException ex) {
        ErrorResponse error = new ErrorResponse(400, ex.getMessage());
        return ResponseEntity.status(400).body(error);
    }
}

Иерархия исключений

@ExceptionHandler автоматически выбирает обработчик на основе иерархии исключений:

@RestControllerAdvice // Глобальная обработка для всех контроллеров
public class GlobalExceptionHandler {
    
    // Самый специфичный обработчик
    @ExceptionHandler(EntityNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleEntityNotFound(
            EntityNotFoundException ex) {
        return ResponseEntity.status(404)
            .body(new ErrorResponse(404, ex.getMessage()));
    }
    
    // Более общий обработчик (родительский класс)
    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<ErrorResponse> handleRuntimeException(
            RuntimeException ex) {
        return ResponseEntity.status(500)
            .body(new ErrorResponse(500, "Внутренняя ошибка сервера"));
    }
    
    // Самый общий обработчик
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGenericException(
            Exception ex) {
        return ResponseEntity.status(500)
            .body(new ErrorResponse(500, "Неизвестная ошибка"));
    }
}

@RestControllerAdvice vs локальный @ExceptionHandler

Локальный @ExceptionHandler (в контроллере)

@RestController
public class ProductController {
    
    @GetMapping("/products/{id}")
    public ProductDTO getProduct(@PathVariable Long id) {
        return productService.findById(id);
    }
    
    // Обработчик только для этого контроллера
    @ExceptionHandler(ProductNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(ProductNotFoundException ex) {
        return ResponseEntity.status(404)
            .body(new ErrorResponse(404, ex.getMessage()));
    }
}

Плюсы: специфичная обработка для одного контроллера Минусы: дублирование кода, нет централизации

Глобальный @RestControllerAdvice

@RestControllerAdvice // Работает для всех контроллеров
public class GlobalExceptionHandler {
    
    @ExceptionHandler(ProductNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(ProductNotFoundException ex) {
        return ResponseEntity.status(404)
            .body(new ErrorResponse(404, ex.getMessage()));
    }
}

Плюсы: централизованная обработка, нет дублирования Минусы: менее специфична

Практический пример с валидацией

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @PostMapping
    public ResponseEntity<UserDTO> createUser(@Valid @RequestBody CreateUserRequest req) {
        return ResponseEntity.status(201)
            .body(userService.create(req));
    }
}

@RestControllerAdvice
public class GlobalExceptionHandler {
    
    // Обработка ошибок валидации
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ValidationErrorResponse> handleValidationException(
            MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(error ->
            errors.put(error.getField(), error.getDefaultMessage())
        );
        
        ValidationErrorResponse response = new ValidationErrorResponse(
            400,
            "Ошибка валидации",
            errors
        );
        return ResponseEntity.status(400).body(response);
    }
    
    // Обработка бизнес-ошибок
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) {
        return ResponseEntity.status(ex.getStatusCode())
            .body(new ErrorResponse(ex.getStatusCode(), ex.getMessage()));
    }
    
    // Обработка любых неожиданных ошибок
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleUnexpectedException(Exception ex) {
        // Логирование для отладки
        logger.error("Неожиданная ошибка", ex);
        
        return ResponseEntity.status(500)
            .body(new ErrorResponse(500, "Внутренняя ошибка сервера"));
    }
}

Важные моменты

  • Порядок выполнения — Spring выбирает самый специфичный обработчик
  • ResponseEntity — позволяет контролировать статус код и заголовки
  • Логирование — всегда логируй исключения для отладки
  • Безопасность — не отправляй стек-трейсы клиентам в продакшене
  • HTTP статусы — используй правильные коды (400, 404, 500 и т.д.)

Рекомендации

  1. Используй @RestControllerAdvice для централизованной обработки
  2. Создавай кастомные исключения для разных типов ошибок
  3. Возвращай единообразный формат ошибок
  4. Логируй все исключения
  5. Не передавай чувствительную информацию в ответах клиентам