← Назад к вопросам
Как обрабатывать исключения в 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);
}
}
Иерархия обработки исключений
- @ExceptionHandler в контроллере — специфичны для контроллера
- @ControllerAdvice — глобальные, применяются ко всем контроллерам
- Встроенные обработчики Spring — обработка стандартных ошибок (404, 405 и т.д.)
Использование правильной иерархии гарантирует согласованное и предсказуемое обращение с ошибками по всему приложению.