Skip to main content
ElastiCORE 3.0 is a release candidate: 3.0.261022-rc1 on Maven Central. Projects on the 2.2 line: see the 2.2 documentation.
Version: 3.0

The generated API and error answers

The API of an @expose entity​

Base path /api/<domain>/<entity> - the entity name with a small first letter (PurchaseOrder is /api/shop/purchaseOrder); apiPath in env.yml sets /api.

RequestDoes
POST / with the fieldscreate; answers the row with its key
GET /{key}read one; 404 when there is none
PUT / with the key in the bodyupdate (an @editable root or field)
DELETE /{key}delete; answers true
POST /search {field: value, "pageNumber": 0, "pageSize": 20, "sortCode": "title+"}a page: content, totalElements, totalPages, number
PUT /batch with a listupdate several in one transaction - each row goes through update(dto)
POST /delete with a list of keysdelete several; a list of true/false
GET /schema/infothe entity's field descriptions, for a generic screen
POST /{key}/<command>a lifecycle step or command (DDD bundles)
POST /signin, POST /signout, GET /mewith @signin

There is no GET for a list. The controller documents every operation for springdoc (/swagger-ui.html, /v3/api-docs).

@restExclude(...)​

Leaves methods out of the service, controller, use case and TypeScript client. Names: the service's save, update, batchUpdate, delete (a list), deleteById, updateBySpecification, and the controller's create, read, search, update, batchUpdate, deleteBatch (POST /delete), deleteById, schemaInfo. On v3 what calls a removed method goes with it, so it still compiles: update takes batchUpdate, deleteById takes delete and deleteBatch, delete takes deleteBatch, save takes create. A Kotlin service keeps its methods; its controller and use case follow. The 2.x-compatible output removes exactly what is written.

What the v3 write path checks​

The core services of jpa-springboot-v3, ddd-springboot, hexagonal-springboot and the Kotlin editions check before writing (the 2.x-compatible jpa-springboot does not change):

CaseBehaviourAnswer
a create with a key that existsrefused, not overwritten409
an update whose version differs from the row'srefused (optimistic lock)409
an update value that breaks a constraint (@Size ...)refused - only the fields sent are checked400
an update of a row that does not existEntityNotFoundException404
a key of another aggregate that names no row (DDD)reference.<Entity>.<relation>404 Member 99 does not exist
a command or delete the state does not allow (states:)state....409
another invariant brokenInvariantViolationException422
a rule you wrote refusesthe runtime's exceptions, below400 / 401 / 403 / 404 / 409 / 502

The DDD mappers also never write @jpa:Version or the Spring Data audit fields from a request: they are answered, so a client sends the version back, but a request cannot forge the audit record.

Error answers: application/problem+json​

Every v3 bundle generates, per domain with exposed entities, one XxxApiErrors (@RestControllerAdvice, RFC 9457 ProblemDetail) in the controller package - generate-once, so the answer's shape is yours. It also imports the runtime's RequestShapeErrors, which answers what Spring MVC refuses before a controller is called, in the same shape, for the whole application.

HTTP/1.1 409
Content-Type: application/problem+json

{ "title": "Not allowed in this state",
"detail": "PurchaseOrder cannot cancel when status is SHIPPED; it must be [PLACED]",
"code": "state.PurchaseOrder.status.cancel", "status": 409 }

A 400 for fields also has errors ("field: message") and fieldErrors:

{ "status": 400, "code": "invalid",
"fieldErrors": [{ "field": "lines", "code": "Size", "message": "the cart is empty" }] }

Before the controller (RequestShapeErrors)​

RequestStatuscode
no body, or not JSON400unreadable
a value its type refuses - letters for a number, an unknown enum code, a negative quantity400invalid, fieldErrors[].code typeMismatch (lines[0].quantity: a quantity cannot be negative: -1)
a required query parameter missing400invalid, NotNull
a path that does not exist404not-found
a method the path does not have (PUT on a root that is not @editable)405 (with Allow)method-not-allowed
an answer format it cannot give (Accept: image/png)406not-acceptable
a body format it cannot read (Content-Type: text/plain)415unsupported-media-type

A browser asking for a page (Accept: text/html) still gets Spring Boot's error page for 404 and 405. Do not set spring.mvc.problemdetails.enabled=true: Spring Boot's handler would take the field validation answers. It needs Spring Framework 6.1 (Spring Boot 3.2) or later.

Codes​

CodeWhen
not-found, already-exists, stale, required, unique, referenced, conflict, invalid, refused, state, not-signed-in, forbidden, bad-request, no-such-propertyshared by all domains
state.<Entity>.<field>.<command>a lifecycle step from the wrong state (409)
invariant.<Entity>.<field>.min / .max / .minsize / .maxsizea field's range invariant
invariant.<Entity>.<condition>an @invariant (items.size > 0 gives items-size-gt-0)
reference.<Entity>.<relation>a referenced row that does not exist (404)
upstreamanother system failed (PortCallException, 502)
unreadable, method-not-allowed, unsupported-media-type, not-acceptablerefused before the controller

The TypeScript client's ApiError reads code and fieldErrors (TypeScript).

Refusals your code throws​

Exception (io.elasticore.springboot3.)Statuscode without a code of yours
validation.BadRequestException(code, message, args...)400bad-request
security.NotSignedInException(code, message, args...)401not-signed-in
security.ForbiddenException(code, message, args...)403forbidden
validation.NotFoundException(code, message, args...)404not-found
validation.ConflictException(code, message, args...)409conflict
http.PortCallException(code, message, args...)502upstream
validation.InvariantViolationException(code, message, args...)state. 409, reference. 404, otherwise 422

The message is the detail; with a message code it is translated into the request's language (see Messages and languages). new ConflictException("...") without a code works too.

throw new ConflictException("loan.no-copy-left", "No copy of {0} is left to lend", isbn);
HTTP/1.1 409
{ "title": "Conflicts with stored data", "detail": "No copy of 978-0-00 is left to lend",
"code": "loan.no-copy-left", "status": 409 }
An XxxApiErrors you edited

It is generate-once: one you never edited is regenerated with each change (REGENERATED(owned, never edited)); one you edited stays as it is. From 3.0.261022-rc1 a refused lifecycle step is 409 and a missing reference 404 (before: 422 and a foreign-key 409). An edited file still answers the old way - change its refused(...) as a new one does (state. 409, reference. 404), add @Import(io.elasticore.springboot3.validation.RequestShapeErrors.class), or delete it and build again. Upgrade the generator and the runtime library to the same version.