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

Как обрабатывать исключения в Spring MVC?

2.0 Middle🔥 121 комментариев
#Docker, Kubernetes и DevOps#JVM и управление памятью

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

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

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

Обработка исключений в Spring MVC

Правильная обработка исключений в Spring MVC критически важна для создания надёжных и user-friendly приложений. Spring предоставляет несколько мощных механизмов для централизованной обработки ошибок, избегая дублирования кода и создания согласованного API для ответов об ошибках.

1. @ExceptionHandler — обработчик исключений на уровне контроллера

Локальный @ExceptionHandler — обрабатывает исключения только в одном контроллере.

import org.springframework.web.bind.annotation.*;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
        // Выбрасываем исключение, если пользователь не найден
        User user = userService.findById(id)
            .orElseThrow(() -> new UserNotFoundException("Пользователь не найден: " + id));
        return ResponseEntity.ok(UserMapper.toDTO(user));
    }
    
    // Обработчик исключения в этом контроллере
    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException ex) {
        ErrorResponse errorResponse = new ErrorResponse(
            "USER_NOT_FOUND",
            ex.getMessage(),
            System.currentTimeMillis()
        );
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
    }
    
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<ErrorResponse> handleIllegalArgument(IllegalArgumentException ex) {
        ErrorResponse errorResponse = new ErrorResponse(
            "INVALID_ARGUMENT",
            ex.getMessage(),
            System.currentTimeMillis()
        );
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
    }
}

Пользовательские исключения:

public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(String message) {
        super(message);
    }
    
    public UserNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }
}

public class InvalidUserDataException extends RuntimeException {
    public InvalidUserDataException(String message) {
        super(message);
    }
}

Класс для ответа об ошибке:

import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDateTime;

public class ErrorResponse {
    private String errorCode;
    private String message;
    private long timestamp;
    private String path;
    
    public ErrorResponse(String errorCode, String message, long timestamp) {
        this.errorCode = errorCode;
        this.message = message;
        this.timestamp = timestamp;
    }
    
    // Getters and setters
    public String getErrorCode() { return errorCode; }
    public void setErrorCode(String errorCode) { this.errorCode = errorCode; }
    public String getMessage() { return message; }
    public void setMessage(String message) { this.message = message; }
    public long getTimestamp() { return timestamp; }
    public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
    public String getPath() { return path; }
    public void setPath(String path) { this.path = path; }
}

2. @ControllerAdvice — глобальная обработка исключений

@ControllerAdvice — позволяет определить глобальные обработчики исключений для всего приложения.

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.context.request.WebRequest;

@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
    
    // Обработка NotFoundException
    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleUserNotFound(
            UserNotFoundException ex,
            WebRequest request) {
        
        ErrorResponse errorResponse = new ErrorResponse(
            "USER_NOT_FOUND",
            ex.getMessage(),
            System.currentTimeMillis()
        );
        errorResponse.setPath(request.getDescription(false).replace("uri=", ""));
        
        return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
    }
    
    // Обработка IllegalArgumentException
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<ErrorResponse> handleIllegalArgument(
            IllegalArgumentException ex,
            WebRequest request) {
        
        ErrorResponse errorResponse = new ErrorResponse(
            "INVALID_ARGUMENT",
            ex.getMessage(),
            System.currentTimeMillis()
        );
        
        return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
    }
    
    // Обработка всех необработанных исключений
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGlobalException(
            Exception ex,
            WebRequest request) {
        
        ErrorResponse errorResponse = new ErrorResponse(
            "INTERNAL_SERVER_ERROR",
            "Произошла непредвиденная ошибка",
            System.currentTimeMillis()
        );
        
        // Логирование ошибки
        ex.printStackTrace();
        
        return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

3. Обработка ошибок валидации

import org.springframework.web.bind.annotation.*;
import org.springframework.validation.BindingResult;
import javax.validation.Valid;

@ControllerAdvice
public class ValidationExceptionHandler {
    
    @ExceptionHandler(org.springframework.web.bind.MethodArgumentNotValidException.class)
    public ResponseEntity<ValidationErrorResponse> handleValidationExceptions(
            org.springframework.web.bind.MethodArgumentNotValidException ex,
            WebRequest request) {
        
        ValidationErrorResponse errorResponse = new ValidationErrorResponse(
            "VALIDATION_ERROR",
            "Ошибка валидации входных данных",
            System.currentTimeMillis()
        );
        
        // Извлечение всех ошибок валидации
        ex.getBindingResult().getFieldErrors().forEach(error -> {
            errorResponse.addFieldError(
                error.getField(),
                error.getDefaultMessage()
            );
        });
        
        return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
    }
}

public class ValidationErrorResponse extends ErrorResponse {
    private Map<String, String> fieldErrors = new HashMap<>();
    
    public ValidationErrorResponse(String errorCode, String message, long timestamp) {
        super(errorCode, message, timestamp);
    }
    
    public void addFieldError(String field, String message) {
        fieldErrors.put(field, message);
    }
    
    public Map<String, String> getFieldErrors() {
        return fieldErrors;
    }
}

Использование в контроллере:

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @PostMapping
    public ResponseEntity<UserDTO> createUser(@Valid @RequestBody CreateUserRequest request) {
        User user = userService.create(request);
        return ResponseEntity.status(HttpStatus.CREATED).body(UserMapper.toDTO(user));
    }
}

// DTO с аннотациями валидации
import javax.validation.constraints.*;

public class CreateUserRequest {
    
    @NotBlank(message = "Имя не должно быть пустым")
    @Size(min = 2, max = 100, message = "Имя должно быть от 2 до 100 символов")
    private String name;
    
    @NotBlank(message = "Email не должен быть пустым")
    @Email(message = "Email должен быть корректным")
    private String email;
    
    @NotNull(message = "Возраст не должен быть null")
    @Min(value = 18, message = "Возраст должен быть не менее 18 лет")
    @Max(value = 120, message = "Возраст не может быть более 120 лет")
    private Integer age;
    
    // Getters and setters
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }
}

4. Обработка исключений базы данных

@ControllerAdvice
public class DataAccessExceptionHandler {
    
    @ExceptionHandler(DataIntegrityViolationException.class)
    public ResponseEntity<ErrorResponse> handleDataIntegrityViolation(
            DataIntegrityViolationException ex,
            WebRequest request) {
        
        ErrorResponse errorResponse = new ErrorResponse(
            "CONSTRAINT_VIOLATION",
            "Нарушение ограничений целостности данных",
            System.currentTimeMillis()
        );
        
        return new ResponseEntity<>(errorResponse, HttpStatus.CONFLICT);
    }
    
    @ExceptionHandler(org.hibernate.exception.ConstraintViolationException.class)
    public ResponseEntity<ErrorResponse> handleHibernateConstraintViolation(
            org.hibernate.exception.ConstraintViolationException ex) {
        
        ErrorResponse errorResponse = new ErrorResponse(
            "DATABASE_CONSTRAINT_ERROR",
            "Ошибка ограничения базы данных: " + ex.getMessage(),
            System.currentTimeMillis()
        );
        
        return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
    }
}

5. Пример со статусными кодами HTTP

@ControllerAdvice
public class CustomExceptionHandler {
    
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
        return buildResponse("NOT_FOUND", ex.getMessage(), HttpStatus.NOT_FOUND);
    }
    
    @ExceptionHandler(UnauthorizedException.class)
    public ResponseEntity<ErrorResponse> handleUnauthorized(UnauthorizedException ex) {
        return buildResponse("UNAUTHORIZED", ex.getMessage(), HttpStatus.UNAUTHORIZED);
    }
    
    @ExceptionHandler(ForbiddenException.class)
    public ResponseEntity<ErrorResponse> handleForbidden(ForbiddenException ex) {
        return buildResponse("FORBIDDEN", ex.getMessage(), HttpStatus.FORBIDDEN);
    }
    
    @ExceptionHandler(ConflictException.class)
    public ResponseEntity<ErrorResponse> handleConflict(ConflictException ex) {
        return buildResponse("CONFLICT", ex.getMessage(), HttpStatus.CONFLICT);
    }
    
    private ResponseEntity<ErrorResponse> buildResponse(
            String errorCode,
            String message,
            HttpStatus status) {
        ErrorResponse errorResponse = new ErrorResponse(errorCode, message, System.currentTimeMillis());
        return new ResponseEntity<>(errorResponse, status);
    }
}

6. Логирование исключений

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@ControllerAdvice
public class LoggingExceptionHandler {
    private static final Logger logger = LoggerFactory.getLogger(LoggingExceptionHandler.class);
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleException(Exception ex, WebRequest request) {
        String path = request.getDescription(false).replace("uri=", "");
        
        // Логирование в зависимости от типа исключения
        if (ex instanceof RuntimeException) {
            logger.error("RuntimeException при {} : {}", path, ex.getMessage(), ex);
        } else {
            logger.warn("Exception при {}: {}", path, ex.getMessage());
        }
        
        ErrorResponse errorResponse = new ErrorResponse(
            "ERROR",
            "Произошла ошибка при обработке запроса",
            System.currentTimeMillis()
        );
        
        return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

7. Лучшие практики

// ✅ ПРАВИЛЬНЫЙ ПОДХОД
@ControllerAdvice
@Slf4j // Использование Lombok для логирования
public class GlobalExceptionHandler {
    
    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<ApiError> handleUserNotFound(
            UserNotFoundException ex,
            HttpServletRequest request) {
        
        ApiError apiError = ApiError.builder()
            .timestamp(LocalDateTime.now())
            .status(HttpStatus.NOT_FOUND.value())
            .error("Not Found")
            .message(ex.getMessage())
            .path(request.getRequestURI())
            .build();
        
        log.warn("User not found: {}", ex.getMessage());
        return new ResponseEntity<>(apiError, HttpStatus.NOT_FOUND);
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ApiError> handleGenericException(
            Exception ex,
            HttpServletRequest request) {
        
        ApiError apiError = ApiError.builder()
            .timestamp(LocalDateTime.now())
            .status(HttpStatus.INTERNAL_SERVER_ERROR.value())
            .error("Internal Server Error")
            .message("Произошла непредвиденная ошибка")
            .path(request.getRequestURI())
            .build();
        
        log.error("Unexpected exception: ", ex);
        return new ResponseEntity<>(apiError, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

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

  1. @ExceptionHandler в контроллере — специфичны для контроллера
  2. @ControllerAdvice — глобальные, применяются ко всем контроллерам
  3. Встроенные обработчики Spring — обработка стандартных ошибок (404, 405 и т.д.)

Использование правильной иерархии гарантирует согласованное и предсказуемое обращение с ошибками по всему приложению.

Как обрабатывать исключения в Spring MVC? | PrepBro