Опирается на правила:
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 категории ошибки |
status | HTTP-статус (дублирует HTTP-код для convenience) |
title | Короткое описание (обычно = название HTTP-статуса) |
detail | Для пользователя (может быть на русском, динамический) |
instance | Уникальный URN/UUID конкретного инцидента |
traceId | ID трассировки из 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 мапятся через MethodArgumentNotValidException → BindException → violations.
HTTP-коды
R-ERR-9:
| Код | Когда |
|---|---|
400 Bad Request | всегда (validation, malformed body) |
401 Unauthorized | если есть аутентификация (no token, expired) |
403 Forbidden | если есть авторизация (RBAC/ABAC отказал) |
404 Not Found | обращение к конкретному объекту по ID |
409 Conflict | concurrent modification, duplicate resource |
410 Gone | удалённый deprecated-эндпоинт (см. Deprecation) |
429 Too Many Requests | rate 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-X1 | application/problem+json |
type: "about:blank" | R-ERR-X2 | URL или urn:problem:<service>:<code> |
| HTTP-код вне списка (418, 422, 451) | R-ERR-X3 | стандартный 400/404/409/500 |
Stack trace в detail | R-ERR-X4 | traceId для cross-ref |
| SQL-запросы в теле 500 | R-ERR-X4 | generic message |
| Внутренние пути файлов в error | R-ERR-X4 | без internal details |
| Одна ошибка валидации вместо всех | R-ERR-6 | все violations за один запрос |
PII в detail (см. Auth → PII) | AUTH-18 | error code, общее сообщение |
Куда дальше
- REST API → Ошибки (нормативно) — формулировки + полные OpenAPI examples.
- Error handling → global handler — Spring реализация.
- Error handling → exception hierarchy — Domain/Validation/Integration/Technical.
- Заголовки и трассировка —
traceparent→traceId. - Auth → PII и секреты — что не пишем в
detail. - Rate limiting, файлы, deprecation — 429, 410.
- JSON и формат ответов — success format vs error.