Documentation

Web and Problem Details

krizaka-web: one error format (RFC 9457), request correlation, Jackson defaults, cursor pagination, CORS.

<dependency>
  <groupId>com.krizaka</groupId>
  <artifactId>krizaka-spring-boot-starter-web</artifactId>
</dependency>

The error contract

Every error a service answers is application/problem+json:

{
  "type": "https://krizaka.com/problems/auction-closed",
  "title": "auction closed",
  "status": 409,
  "detail": "The auction closed at 18:00.",
  "code": "auction-closed",
  "requestId": "0b6c3f1e-6a52-4c4e-9a43-61f0f1f2a1d7"
}
FieldMeaning
typekrizaka.web.problems.base-type + code — where the code is documented
titlethe code, words separated by spaces
statusthe HTTP status
detailthe exception's message, written for the caller. Absent on a 500.
codethe stable slug a client branches on; never changes once published
requestIdthe request's X-Request-Id, also on every log line of the request

A bean validation failure is a 422 with code: validation-failed and an errors array ({ field, code, message } per violation). Spring Security's 401/403 stay Spring Security's; anything unexpected is a 500 with code: internal, logged with its requestId, never sent.

Exceptions

A DomainException is the only exception that leaves a service as a 4xx:

ExceptionStatusDefault code
NotFoundException404not-found
ConflictException409conflict
ForbiddenException403forbidden
ValidationException422validation-failed
InvalidCursorException400invalid-cursor
throw new ConflictException("auction-closed", "The auction closed at 18:00.");

Request correlation

CorrelationIdFilter runs first: it keeps a well-formed caller X-Request-Id (or creates a UUID), sends it back, and puts it in the MDC as requestId for the whole request. Outside a request, open a scope:

try (CorrelationId.Scope scope = CorrelationId.open(message.getMessageProperties().getCorrelationId())) {
  handle(message);
}

Cursor pagination

@GetMapping
CursorPage<Item> list(@RequestParam(required = false) String cursor) {
  Cursor page = Cursor.fromRequest(cursor, 20);
  List<Item> rows = items.after(page.afterAsLong(), page.limit() + 1); // one more than the page
  return CursorPage.of(rows, page, Item::id);
}

The extra row proves there is a next page without a COUNT. nextCursor is absent on the last page; the page size is capped at 100.

JSON and CORS

Jackson 3 writes ISO-8601 dates, omits nulls and ignores unknown fields (the tolerant reader). CORS opens nothing unless declared — krizaka.web.cors.allowed-origins lists exact origins, a wildcard is refused at startup.

PropertyDefault
krizaka.web.problems.base-typehttps://krizaka.com/problems/
krizaka.web.cors.allowed-origins(none)

On this page