← Назад к вопросам
Какие знаешь способы обработки ошибок в RestController?
1.7 Middle🔥 171 комментариев
#Spring Boot и Spring Data#Spring Framework
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Способы обработки ошибок в RestController
Обзор подходов
В Spring есть несколько способов обработки ошибок в REST контроллерах:
- @ExceptionHandler — обработчики в контроллере
- @ControllerAdvice / @RestControllerAdvice — глобальная обработка
- ProblemDetail — стандартизированный формат ошибок (Spring 6.0+)
- ResponseEntity — явное возвращение HTTP ответов
- Встроенные исключения Spring — BadRequestException, NotFound и т.д.
1. @ExceptionHandler в контроллере
Обработчики для конкретного контроллера:
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
if (id <= 0) {
throw new IllegalArgumentException("ID must be positive");
}
// ... логика
return ResponseEntity.ok(user);
}
// Обработчик для IllegalArgumentException
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResponse> handleIllegalArgument(
IllegalArgumentException ex,
HttpServletRequest request
) {
ErrorResponse error = ErrorResponse.builder()
.timestamp(LocalDateTime.now())
.status(HttpStatus.BAD_REQUEST.value())
.error("Invalid Argument")
.message(ex.getMessage())
.path(request.getRequestURI())
.build();
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(error);
}
}
2. @RestControllerAdvice — глобальная обработка (рекомендуется)
Централизованная обработка всех ошибок приложения:
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
// Обработка NotFoundException
@ExceptionHandler(NotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(
NotFoundException ex,
HttpServletRequest request
) {
log.warn("Resource not found: {}", ex.getMessage());
ErrorResponse error = ErrorResponse.builder()
.timestamp(LocalDateTime.now())
.status(HttpStatus.NOT_FOUND.value())
.error("Not Found")
.message(ex.getMessage())
.path(request.getRequestURI())
.build();
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(error);
}
// Обработка IllegalArgumentException
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResponse> handleBadRequest(
IllegalArgumentException ex,
HttpServletRequest request
) {
ErrorResponse error = ErrorResponse.builder()
.timestamp(LocalDateTime.now())
.status(HttpStatus.BAD_REQUEST.value())
.error("Bad Request")
.message(ex.getMessage())
.path(request.getRequestURI())
.build();
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(error);
}
// Обработка MethodArgumentNotValidException (валидация)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidation(
MethodArgumentNotValidException ex,
HttpServletRequest request
) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(
error -> errors.put(error.getField(), error.getDefaultMessage())
);
ErrorResponse error = ErrorResponse.builder()
.timestamp(LocalDateTime.now())
.status(HttpStatus.BAD_REQUEST.value())
.error("Validation Failed")
.message("Input validation failed")
.validationErrors(errors)
.path(request.getRequestURI())
.build();
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(error);
}
// Обработка всех остальных исключений
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(
Exception ex,
HttpServletRequest request
) {
log.error("Unexpected error", ex);
ErrorResponse error = ErrorResponse.builder()
.timestamp(LocalDateTime.now())
.status(HttpStatus.INTERNAL_SERVER_ERROR.value())
.error("Internal Server Error")
.message("An unexpected error occurred")
.path(request.getRequestURI())
.build();
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(error);
}
}
3. Модель ответа об ошибке
@Data
@Builder
public class ErrorResponse {
private LocalDateTime timestamp;
private int status;
private String error;
private String message;
private String path;
private Map<String, String> validationErrors;
public static ErrorResponse of(
int status,
String error,
String message,
String path
) {
return ErrorResponse.builder()
.timestamp(LocalDateTime.now())
.status(status)
.error(error)
.message(message)
.path(path)
.build();
}
}
4. Пользовательские исключения
// Базовое приложением исключение
public abstract class ApplicationException extends RuntimeException {
private final HttpStatus status;
public ApplicationException(String message, HttpStatus status) {
super(message);
this.status = status;
}
public HttpStatus getStatus() {
return status;
}
}
// Не найдено
public class NotFoundException extends ApplicationException {
public NotFoundException(String message) {
super(message, HttpStatus.NOT_FOUND);
}
}
// Конфликт
public class ConflictException extends ApplicationException {
public ConflictException(String message) {
super(message, HttpStatus.CONFLICT);
}
}
// Использование
@GetMapping("/{id}")
public ResponseEntity<UserDto> getUser(@PathVariable Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new NotFoundException(
"User with id " + id + " not found"
));
return ResponseEntity.ok(UserDto.from(user));
}
5. ProblemDetail (Spring 6.0+)
Стандартизированный формат RFC 7807:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NotFoundException.class)
public ProblemDetail handleNotFound(NotFoundException ex) {
ProblemDetail problem = ProblemDetail.forStatusAndDetail(
HttpStatus.NOT_FOUND,
ex.getMessage()
);
problem.setTitle("Resource Not Found");
problem.setType(URI.create("https://api.example.com/errors/not-found"));
problem.setProperty("timestamp", LocalDateTime.now());
return problem;
}
}
6. ResponseStatusException
Простой способ для простых случаев:
@GetMapping("/{id}")
public ResponseEntity<UserDto> getUser(@PathVariable Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResponseStatusException(
HttpStatus.NOT_FOUND,
"User not found with id: " + id
));
return ResponseEntity.ok(UserDto.from(user));
}
7. Валидация с аннотациями
public class UserCreateRequest {
@NotBlank(message = "Name cannot be blank")
private String name;
@Email(message = "Email should be valid")
private String email;
@Min(value = 18, message = "Age must be at least 18")
private int age;
}
@PostMapping
public ResponseEntity<UserDto> createUser(
@Valid @RequestBody UserCreateRequest request
) {
// request.getName(), request.getEmail() гарантированно валидны
User user = userService.create(request);
return ResponseEntity.status(HttpStatus.CREATED).body(UserDto.from(user));
}
8. Логирование ошибок
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handle(Exception ex, HttpServletRequest request) {
String correlationId = UUID.randomUUID().toString();
log.error(
"Error [{}] at {} {}: {}",
correlationId,
request.getMethod(),
request.getRequestURI(),
ex.getMessage(),
ex
);
ErrorResponse error = ErrorResponse.builder()
.timestamp(LocalDateTime.now())
.status(HttpStatus.INTERNAL_SERVER_ERROR.value())
.error("Internal Server Error")
.message("Please contact support with ID: " + correlationId)
.build();
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(error);
}
}
9. Порядок приоритета обработчиков
- @ExceptionHandler в контроллере
- @RestControllerAdvice с наиболее конкретным типом исключения
- Spring встроенные обработчики (400, 404, 500 и т.д.)
- Сервер возвращает стандартный HTML ответ
Лучшие практики
- Используй @RestControllerAdvice для централизованной обработки
- Создавай пользовательские исключения с нужным HTTP статусом
- Логируй все ошибки с корреляционным ID
- Возвращай консистентный формат ошибок
- Не выдавай деталей реализации в ошибках (защита от инъекций)
- Используй правильные HTTP статусы (400, 404, 409, 500 и т.д.)
- Валидируй входные данные с @Valid
- Избегай множественных @ExceptionHandler в одном контроллере
Пример полного REST контроллера
@RestController
@RequestMapping("/api/v1/users")
@Validated
public class UserController {
@GetMapping("/{id}")
public ResponseEntity<UserDto> getUser(@PathVariable @Positive Long id) {
User user = userService.findById(id)
.orElseThrow(() -> new NotFoundException(
"User with id " + id + " not found"
));
return ResponseEntity.ok(UserDto.from(user));
}
@PostMapping
public ResponseEntity<UserDto> createUser(
@Valid @RequestBody UserCreateRequest request
) {
User user = userService.create(request);
return ResponseEntity
.status(HttpStatus.CREATED)
.body(UserDto.from(user));
}
@PutMapping("/{id}")
public ResponseEntity<UserDto> updateUser(
@PathVariable @Positive Long id,
@Valid @RequestBody UserUpdateRequest request
) {
User user = userService.update(id, request)
.orElseThrow(() -> new NotFoundException(
"User with id " + id + " not found"
));
return ResponseEntity.ok(UserDto.from(user));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable @Positive Long id) {
userService.delete(id);
return ResponseEntity.noContent().build();
}
}
Правильная обработка ошибок — ключ к надёжному и понятному REST API.