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

Какие знаешь способы возврата статуса 201 No Content на фронтенд при возникновении ошибки orderNotFound в Spring?

2.0 Middle🔥 271 комментариев
#Spring Boot и Spring Data

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

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

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

Способы возврата статуса при ошибке в 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 FoundOrderNotFoundException
Ошибка в данных400 Bad RequestValidationException
Нет прав доступа403 ForbiddenAccessDeniedException
Не аутентифицирован401 UnauthorizedAuthenticationException
Конфликт данных409 ConflictDuplicateOrderException
Внутренняя ошибка500 Internal Server ErrorDatabaseException
Не реализовано501 Not ImplementedFeatureNotAvailableException

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);
    }
}

Итоговые рекомендации

  1. Всегда использовать @RestControllerAdvice для централизованной обработки
  2. Вовращать правильный HTTP статус (404, 400, 500 и т.д.)
  3. Вернуть структурированное тело ошибки с кодом и сообщением
  4. Не выставлять деревню стек трейсов фронтенду в продакшене
  5. Логировать ошибки на серверной стороне
  6. Использовать custom exceptions для разных типов ошибок