Gymterview
middle

Как обрабатывать ошибки в REST API?

Правильная обработка ошибок строится на двух принципах: использование корректных HTTP-кодов (4xx/5xx) и возврат структурированного тела ответа с описанием ошибки.

Стандартный формат ответа об ошибке (RFC 7807 — Problem Details):

Пример
{
  "type": "https://api.example.com/errors/validation-error",
  "title": "Ошибка валидации",
  "status": 400,
  "detail": "Поле 'email' содержит некорректное значение",
  "instance": "/api/users",
  "timestamp": "2025-01-15T10:30:00Z",
  "errors": [
    {
      "field": "email",
      "message": "Некорректный формат email",
      "rejectedValue": "not-an-email"
    }
  ]
}
Реализация в Spring с @ControllerAdvice
// DTO для ответа об ошибке
public record ErrorResponse(
    String type,
    String title,
    int status,
    String detail,
    String instance,
    LocalDateTime timestamp,
    List<FieldError> errors
) {
    public record FieldError(String field, String message, Object rejectedValue) {}
}
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(EntityNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(
            EntityNotFoundException ex, HttpServletRequest request) {
        ErrorResponse error = new ErrorResponse(
            "https://api.example.com/errors/not-found",
            "Ресурс не найден",
            404,
            ex.getMessage(),
            request.getRequestURI(),
            LocalDateTime.now(),
            null
        );
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidation(
            MethodArgumentNotValidException ex, HttpServletRequest request) {
        List<ErrorResponse.FieldError> fieldErrors = ex.getBindingResult()
            .getFieldErrors().stream()
            .map(fe -> new ErrorResponse.FieldError(
                fe.getField(), fe.getDefaultMessage(), fe.getRejectedValue()))
            .toList();

        ErrorResponse error = new ErrorResponse(
            "https://api.example.com/errors/validation-error",
            "Ошибка валидации",
            400,
            "Переданные данные не прошли валидацию",
            request.getRequestURI(),
            LocalDateTime.now(),
            fieldErrors
        );
        return ResponseEntity.badRequest().body(error);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGeneral(
            Exception ex, HttpServletRequest request) {
        ErrorResponse error = new ErrorResponse(
            "https://api.example.com/errors/internal-error",
            "Внутренняя ошибка сервера",
            500,
            "Произошла непредвиденная ошибка",
            request.getRequestURI(),
            LocalDateTime.now(),
            null
        );
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
    }
}

Spring Boot 3 имеет встроенную поддержку RFC 7807 через ProblemDetail:

Пример
@ExceptionHandler(EntityNotFoundException.class)
public ProblemDetail handleNotFound(EntityNotFoundException ex) {
    ProblemDetail problem = ProblemDetail.forStatusAndDetail(
        HttpStatus.NOT_FOUND, ex.getMessage());
    problem.setTitle("Ресурс не найден");
    problem.setType(URI.create("https://api.example.com/errors/not-found"));
    return problem;
}

На собеседовании: ключевое — упомянуть RFC 7807 (Problem Details) и @ControllerAdvice как стандартные подходы. Частая ошибка — возвращать 200 OK с телом {"success": false} вместо правильных HTTP-кодов ошибок.