Какие знаешь способы возврата статуса 201 No Content на фронтенд при возникновении ошибки orderNotFound в Spring?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Способы возврата статуса при ошибке в Spring
Вопрос содержит небольшую путаницу в терминологии (201 No Content — такого кода не существует, скорее всего имеется в виду 404 Not Found или 204 No Content), но разберём все способы обработки ошибок в Spring с возвратом правильных HTTP статусов.
1. @ExceptionHandler в Controller
Это самый простой способ — обработать исключение прямо в контроллере:
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/{id}")
public ResponseEntity<OrderResponse> getOrder(@PathVariable Long id) {
Order order = orderService.findById(id);
return ResponseEntity.ok(new OrderResponse(order));
}
// Обработка ошибки прямо в контроллере
@ExceptionHandler(OrderNotFoundException.class)
public ResponseEntity<ErrorResponse> handleOrderNotFound(
OrderNotFoundException ex) {
ErrorResponse error = new ErrorResponse(
"ORDER_NOT_FOUND",
"Заказ не найден",
404
);
return ResponseEntity
.status(HttpStatus.NOT_FOUND) // 404 Not Found
.body(error);
}
}
При возникновении OrderNotFoundException контроллер вернёт 404 со своим телом ответа.
2. ControllerAdvice (Глобальная обработка ошибок)
Для обработки исключений из всех контроллеров:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(OrderNotFoundException.class)
public ResponseEntity<ErrorResponse> handleOrderNotFound(
OrderNotFoundException ex,
HttpServletRequest request) {
ErrorResponse error = ErrorResponse.builder()
.code("ORDER_NOT_FOUND")
.message("Заказ с ID " + ex.getOrderId() + " не найден")
.status(HttpStatus.NOT_FOUND.value())
.timestamp(LocalDateTime.now())
.path(request.getRequestURI())
.build();
return ResponseEntity
.status(HttpStatus.NOT_FOUND) // 404
.body(error);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(
Exception ex,
HttpServletRequest request) {
ErrorResponse error = ErrorResponse.builder()
.code("INTERNAL_SERVER_ERROR")
.message("Внутренняя ошибка сервера")
.status(HttpStatus.INTERNAL_SERVER_ERROR.value())
.timestamp(LocalDateTime.now())
.path(request.getRequestURI())
.build();
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR) // 500
.body(error);
}
}
Это центральное место для обработки всех ошибок приложения.
3. Custom Exception с информацией о статусе
Создаём своё исключение, которое знает о HTTP статусе:
public class OrderNotFoundException extends RuntimeException {
private final Long orderId;
private final HttpStatus httpStatus;
public OrderNotFoundException(Long orderId) {
super("Заказ с ID " + orderId + " не найден");
this.orderId = orderId;
this.httpStatus = HttpStatus.NOT_FOUND; // 404
}
public Long getOrderId() {
return orderId;
}
public HttpStatus getHttpStatus() {
return httpStatus;
}
}
// В сервисе
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
public Order findById(Long id) {
return orderRepository.findById(id)
.orElseThrow(() -> new OrderNotFoundException(id));
}
}
// Обработчик использует информацию из исключения
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(OrderNotFoundException.class)
public ResponseEntity<ErrorResponse> handleOrderNotFound(
OrderNotFoundException ex) {
ErrorResponse error = new ErrorResponse(
"ORDER_NOT_FOUND",
ex.getMessage(),
ex.getHttpStatus().value()
);
return ResponseEntity
.status(ex.getHttpStatus()) // Берём статус из исключения
.body(error);
}
}
4. ResponseEntity в контроллере (явная проверка)
Проверяем результат и возвращаем нужный статус:
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/{id}")
public ResponseEntity<?> getOrder(@PathVariable Long id) {
try {
Order order = orderService.findById(id);
return ResponseEntity.ok(order); // 200 OK
} catch (OrderNotFoundException e) {
ErrorResponse error = new ErrorResponse(
"ORDER_NOT_FOUND",
e.getMessage(),
404
);
return ResponseEntity
.status(HttpStatus.NOT_FOUND) // 404
.body(error);
}
}
}
НО это не рекомендуется — лучше использовать @ExceptionHandler.
5. HttpStatus аннотация (устарелая версия Spring)
В старых версиях Spring можно было использовать аннотацию:
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Заказ не найден")
public class OrderNotFoundException extends RuntimeException {
// ...
}
@RestController
public class OrderController {
@GetMapping("/orders/{id}")
public Order getOrder(@PathVariable Long id) {
// Если выбросить OrderNotFoundException,
// Spring вернёт 404 автоматически
return orderService.findById(id);
}
}
Это работает, но не позволяет вернуть кастомное тело ответа.
6. Problem Detail (RFC 7231 - новый стандарт)
В Spring 6+ рекомендуется использовать стандартный формат application/problem+json:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(OrderNotFoundException.class)
public ProblemDetail handleOrderNotFound(
OrderNotFoundException ex) {
ProblemDetail problem = ProblemDetail.forStatusAndDetail(
HttpStatus.NOT_FOUND,
ex.getMessage()
);
problem.setTitle("Order Not Found");
problem.setType(URI.create("https://api.example.com/errors/order-not-found"));
problem.setProperty("orderId", ex.getOrderId());
return problem; // Content-Type: application/problem+json
}
}
Ответ:
{
"type": "https://api.example.com/errors/order-not-found",
"title": "Order Not Found",
"status": 404,
"detail": "Заказ с ID 123 не найден",
"orderId": 123
}
7. HttpMessageConverter для кастомного формата
Если нужен специфичный формат ответа:
public class ErrorResponse {
private String code; // "ORDER_NOT_FOUND"
private String message; // "Заказ не найден"
private int status; // 404
private LocalDateTime timestamp;
private String path;
// getters/setters
}
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(OrderNotFoundException.class)
public ResponseEntity<ErrorResponse> handleOrderNotFound(
OrderNotFoundException ex,
HttpServletRequest request) {
ErrorResponse error = new ErrorResponse();
error.setCode("ORDER_NOT_FOUND");
error.setMessage("Заказ не найден: " + ex.getOrderId());
error.setStatus(404);
error.setTimestamp(LocalDateTime.now());
error.setPath(request.getRequestURI());
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.contentType(MediaType.APPLICATION_JSON)
.body(error);
}
}
8. Правильная аннотация @ExceptionHandler с multiple exceptions
Обработка нескольких исключений одним методом:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler({OrderNotFoundException.class,
PaymentNotFoundException.class})
public ResponseEntity<ErrorResponse> handleNotFound(
RuntimeException ex) {
ErrorResponse error = new ErrorResponse(
"RESOURCE_NOT_FOUND",
ex.getMessage(),
404
);
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(error);
}
}
9. Правильные HTTP статусы для разных сценариев
| Сценарий | Статус | Пример |
|---|---|---|
| Ресурс не найден | 404 Not Found | OrderNotFoundException |
| Ошибка в данных | 400 Bad Request | ValidationException |
| Нет прав доступа | 403 Forbidden | AccessDeniedException |
| Не аутентифицирован | 401 Unauthorized | AuthenticationException |
| Конфликт данных | 409 Conflict | DuplicateOrderException |
| Внутренняя ошибка | 500 Internal Server Error | DatabaseException |
| Не реализовано | 501 Not Implemented | FeatureNotAvailableException |
10. Полный пример (Best Practice)
// 1. Custom Exception
public class OrderNotFoundException extends RuntimeException {
private final Long orderId;
public OrderNotFoundException(Long orderId) {
super("Order not found with ID: " + orderId);
this.orderId = orderId;
}
public Long getOrderId() { return orderId; }
}
// 2. Service бросает исключение
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
public Order findById(Long id) {
return orderRepository.findById(id)
.orElseThrow(() -> new OrderNotFoundException(id));
}
}
// 3. Controller
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/{id}")
public ResponseEntity<OrderDTO> getOrder(@PathVariable Long id) {
Order order = orderService.findById(id); // Может выбросить исключение
return ResponseEntity.ok(OrderDTO.from(order));
}
}
// 4. Global Exception Handler
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(OrderNotFoundException.class)
public ResponseEntity<ErrorResponse> handleOrderNotFound(
OrderNotFoundException ex,
HttpServletRequest request) {
ErrorResponse error = ErrorResponse.builder()
.code("ORDER_NOT_FOUND")
.message(ex.getMessage())
.status(404)
.timestamp(LocalDateTime.now(UTC))
.path(request.getRequestURI())
.build();
return ResponseEntity
.status(HttpStatus.NOT_FOUND) // 404
.body(error);
}
}
Итоговые рекомендации
- Всегда использовать @RestControllerAdvice для централизованной обработки
- Вовращать правильный HTTP статус (404, 400, 500 и т.д.)
- Вернуть структурированное тело ошибки с кодом и сообщением
- Не выставлять деревню стек трейсов фронтенду в продакшене
- Логировать ошибки на серверной стороне
- Использовать custom exceptions для разных типов ошибок