Опирается на правила: R-ERR-1..9 и R-ERR-X1..X4 из REST API Style Guide → раздел Ошибки RFC 9457.

Важно знать

  • Тело ошибки соответствует RFC 9457 Problem Details.
  • type — стабильный URI/URN категории ошибки (urn:problem:order-service:order-not-found).
  • Content-Type: application/problem+json (не application/json).
  • code — UPPER_SNAKE_CASE enum для программной логики на клиенте.
  • traceId — из traceparent для cross-system debugging.
  • violations для валидации: field (dot-notation), message, все ошибки за один запрос.
  • HTTP коды: 400/500 всегда, 401/403 при auth, 404 по ID, 409 conflict, 410 deprecated, 429 rate limit.
  • type: about:blank — запрещено (теряется машиночитаемая категория).

Ошибки — критическая часть API. UCP опирается на RFC 9457 (бывший RFC 7807) — стандарт Problem Details. Это даёт единый формат для всех 4xx/5xx, machine-readable categorization через type/code, human-readable detail, и связку с distributed tracing через traceId.

Структура Problem Details

R-ERR-1:

{
  "type": "urn:problem:order-service:validation-error",
  "status": 400,
  "title": "Validation Error",
  "detail": "Поле amount должно быть больше 0.",
  "instance": "urn:uuid:9f2d6c22-8e6d-4c2a-9b41-6b9a5e2f6c10",
  "traceId": "00-1f2a8b6c7d3e4f5a9b0c1d2e3f4a5b6c-7a8b9c0d1e2f3a4b-01",
  "code": "VALIDATION_ERROR"
}
ПолеНазначение
typeСтабильный URI/URN категории ошибки
statusHTTP-статус (дублирует HTTP-код для convenience)
titleКороткое описание (обычно = название HTTP-статуса)
detailДля пользователя (может быть на русском, динамический)
instanceУникальный URN/UUID конкретного инцидента
traceIdID трассировки из traceparent (см. Заголовки)
codeСимвольный enum для программной логики

type — стабильный URI/URN

R-ERR-2: одна категория всегда возвращает один type.

Две допустимые формы:

1. URL на резолвимую страницу документации

"type": "https://errors.example.com/order/not-found"
"type": "https://developer.example.com/errors/insufficient-stock"

Страница описывает: что за ошибка, почему возникает, как исправить. На developer-портале или внутреннем wiki.

2. URN — urn:problem:<service>:<code>

"type": "urn:problem:order-service:order-not-found"
"type": "urn:problem:catalog:product-archived"
"type": "urn:problem:payment-service:insufficient-balance"

Используется, если портала документации нет. URN сохраняет машиночитаемую категорию, не требует deployment портала.

Отношение к другим полям:

  • type — категория (стабильная).
  • code — enum для programming logic на клиенте (ORDER_NOT_FOUND). Удобнее парсить, чем URI.
  • instance — уникальный URN конкретного инцидента (меняется каждый раз).
  • detail — для пользователя.

Content-Type

R-ERR-3: application/problem+json.

HTTP/1.1 404 Not Found
Content-Type: application/problem+json

{
  "type": "urn:problem:order-service:order-not-found",
  "status": 404,
  ...
}

R-ERR-X1: application/json для error body — запрещено. Клиент должен различать success и error по Content-Type для proper handling.

В Spring:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(OrderNotFoundException.class)
    public ResponseEntity<ProblemDetail> handle(OrderNotFoundException ex) {
        var problem = ProblemDetail.forStatus(HttpStatus.NOT_FOUND);
        problem.setType(URI.create("urn:problem:order-service:order-not-found"));
        problem.setTitle("Order not found");
        problem.setDetail("Заказ с указанным id не найден");
        problem.setProperty("code", "ORDER_NOT_FOUND");
        problem.setProperty("traceId", MDC.get("traceId"));
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
            .contentType(MediaType.APPLICATION_PROBLEM_JSON)
            .body(problem);
    }
}

См. Error handling → global handler.

code — UPPER_SNAKE_CASE enum

R-ERR-4: machine-readable.

INTERNAL_SERVER_ERROR
MISSING_DEFAULT_CARD
APPLICATION_ALREADY_SENT
ORDER_EMPTY
EXT_SYSTEM_UNAVAILABLE
ORDER_NOT_FOUND
RATE_LIMIT_EXCEEDED

Все возможные коды перечислены в OpenAPI как enum. Клиент пишет:

switch (error.code) {
  case 'ORDER_NOT_FOUND': showNotFoundPage(); break;
  case 'EXT_SYSTEM_UNAVAILABLE': showRetryButton(); break;
}

Парсить URI type — менее удобно. code — для logic.

Валидация — violations

R-ERR-5..6: per-field errors.

{
  "type": "urn:problem:order-service:validation-error",
  "status": 400,
  "title": "Bad Request",
  "detail": "Ошибка валидации входных данных",
  "instance": "urn:uuid:9f2d6c22-...",
  "traceId": "00-1f2a8b6c...",
  "code": "VALIDATION_ERROR",
  "violations": [
    { "field": "amount", "message": "Сумма должна быть больше 0" },
    { "field": "deliveryAddress.zipCode", "message": "Почтовый индекс обязателен" },
    { "field": "items[0].quantity", "message": "Количество должно быть от 1 до 99" }
  ]
}

Правила:

  • field — путь к полю в теле запроса. Dot-notation для вложенных (deliveryAddress.zipCode), индексы для массивов (items[0].quantity).
  • message — для отображения рядом с полем.
  • Все ошибки за один запрос — клиент подсветит все невалидные поля.
  • Ошибка относится к объекту целиком — field отсутствует или пустая строка.

В Spring @Valid errors мапятся через MethodArgumentNotValidExceptionBindExceptionviolations.

HTTP-коды

R-ERR-9:

КодКогда
400 Bad Requestвсегда (validation, malformed body)
401 Unauthorizedесли есть аутентификация (no token, expired)
403 Forbiddenесли есть авторизация (RBAC/ABAC отказал)
404 Not Foundобращение к конкретному объекту по ID
409 Conflictconcurrent modification, duplicate resource
410 Goneудалённый deprecated-эндпоинт (см. Deprecation)
429 Too Many Requestsrate limit exceeded
500 Internal Server Errorвсегда (unexpected exceptions)

Каждый код имеет examples в OpenAPI (готовые YAML — в нормативном разделе).

R-ERR-X3: HTTP-коды вне списка (418, 422, 451) — запрещены без обсуждения с архитектурным комитетом.

OpenAPI schema

R-ERR-7: единый ProblemDetails для контракта.

ErrorCode:
  type: string
  description: Символьный код ошибки
  enum:
    - INTERNAL_SERVER_ERROR
    - VALIDATION_ERROR
    - ORDER_NOT_FOUND
    - ORDER_EMPTY
    - EXT_SYSTEM_UNAVAILABLE

ProblemDetails:
  type: object
  description: Problem Details (RFC 9457)
  properties:
    type: { type: string, format: uri }
    status: { type: integer, format: int32 }
    title: { type: string }
    detail: { type: string }
    instance: { type: string, format: uri }
    traceId: { type: string }
    code: { $ref: '#/components/schemas/ErrorCode' }
    violations:
      type: array
      description: Только при code=VALIDATION_ERROR
      items:
        $ref: '#/components/schemas/Violation'

Violation:
  type: object
  required: [message]
  properties:
    field: { type: string, example: 'deliveryAddress.zipCode' }
    message: { type: string, example: 'Почтовый индекс обязателен' }

R-ERR-8: все доступные ошибки указаны как examples в response объектах каждого эндпоинта.

Что запрещено

АнтипаттернПравилоЧто взамен
Content-Type: application/json для ошибкиR-ERR-X1application/problem+json
type: "about:blank"R-ERR-X2URL или urn:problem:<service>:<code>
HTTP-код вне списка (418, 422, 451)R-ERR-X3стандартный 400/404/409/500
Stack trace в detailR-ERR-X4traceId для cross-ref
SQL-запросы в теле 500R-ERR-X4generic message
Внутренние пути файлов в errorR-ERR-X4без internal details
Одна ошибка валидации вместо всехR-ERR-6все violations за один запрос
PII в detail (см. Auth → PII)AUTH-18error code, общее сообщение

Куда дальше