| layout | default |
|---|---|
| title | Server-Side Adoption |
| parent | Adoption Guides |
| nav_order | 1 |
| has_toc | false |
Publish a deterministic, generics-aware OpenAPI from Spring Boot with one contract and zero duplication.
This is a contract-first projection system, not a documentation tool.
You define your contract in Java. The platform guarantees a stable, generator-ready OpenAPI projection.
- 60-second quick start
- What the server actually does
- The only rule that matters
- Minimal dependencies
- What you actually write
- What gets published to OpenAPI
- Projection pipeline (what really happens)
- Rules (do NOT break these)
- Quick verification
- Samples
You want:
- deterministic OpenAPI output
- no envelope duplication
- generics preserved in generated clients
Do this:
<dependency>
<groupId>io.github.blueprint-platform</groupId>
<artifactId>openapi-generics-server-starter</artifactId>
<version>1.0.0</version>
</dependency>Default envelope:
ServiceResponse<CustomerDto>
ServiceResponse<Page<CustomerDto>>Or bring your own envelope (BYOE):
ApiResponse<CustomerDto>(Optional configuration for BYOE)
openapi-generics:
envelope:
type: io.example.contract.ApiResponse/v3/api-docs.yaml
Done.
The server has one responsibility:
Project the runtime Java contract into a deterministic OpenAPI representation.
It does not:
- generate clients
- define alternative models
- adapt output for specific generators
It only performs:
Java Contract → OpenAPI (projection)
Everything else happens downstream.
There is one canonical success model — but not a single fixed type.
YourEnvelope<T>
Supported shapes (deterministic scope):
ServiceResponse<T>
ServiceResponse<Page<T>>
YourEnvelope<T>
ServiceResponse<T> is the default envelope provided by the platform. BYOE (Bring Your Own Envelope) lets you substitute any envelope that meets the constraints.
Detailed constraints and violation examples are defined in Rules (do NOT break these).
No configuration required.
<dependency>
<groupId>io.github.blueprint-platform</groupId>
<artifactId>openapi-generics-server-starter</artifactId>
</dependency>Requirements:
- Spring Boot (WebMVC)
- OpenAPI endpoint (
/v3/api-docs) via springdoc
You write only your domain contract.
@GetMapping("/{id}")
public ResponseEntity<ServiceResponse<CustomerDto>> getCustomer(...) {
return ResponseEntity.ok(ServiceResponse.of(dto));
}@GetMapping
public ResponseEntity<ServiceResponse<Page<CustomerDto>>> getCustomers(...) {
return ResponseEntity.ok(ServiceResponse.of(page));
}@GetMapping("/{id}")
public ResponseEntity<ApiResponse<CustomerDto>> getCustomer(...) {
return ResponseEntity.ok(ApiResponse.success(dto));
}Configuration:
openapi-generics:
envelope:
type: io.example.contract.ApiResponseNo annotations. No schema config. No wrapper DTOs. The platform detects the envelope and projects it into OpenAPI deterministically.
The system constrains envelope structure — not payload content. You can place any domain type inside your envelope:
ServiceResponse<CustomerDto>
YourEnvelope<Anything>
From:
ServiceResponse<CustomerDto>or (BYOE):
ApiResponse<CustomerDto>The system produces a deterministic wrapper schema:
ServiceResponseCustomerDto
ApiResponseCustomerDto
Characteristics:
- deterministic naming (based on envelope + payload type)
allOfcomposition- vendor extensions for generation
Example extensions:
x-api-wrapper
x-api-wrapper-datatype
x-data-container
x-data-item
x-ignore-model
Notes:
- The envelope itself is NOT generated — it is treated as an external contract
- Wrapper schemas exist only to bind OpenAPI responses to the generic contract shape
- The same mechanism applies to both default and custom envelopes
OpenAPI is a projection artifact, not the source of truth.
The server is not "configurable". It is a fixed execution pipeline.
Controller return types
↓
Response type discovery
↓
Contract-aware introspection (envelope + payload)
↓
Base schema registration
↓
Wrapper schema generation
↓
Container enrichment
↓
Duplicate model marking
↓
Contract validation (fail-fast)
↓
OpenAPI output
The pipeline is envelope-agnostic — the same stages run for the default ServiceResponse<T> and for BYOE-configured external envelopes. The only difference is envelope resolution at the introspection stage; all downstream logic remains identical.
- single orchestrator (no ordering issues)
- no patching / no overrides
- deterministic output
- envelope-agnostic (default or BYOE uses the same pipeline)
These are hard architectural constraints.
ServiceResponse<T>
ServiceResponse<Page<T>>
YourEnvelope<T>
Constraints:
- exactly one direct generic payload must exist
- nested generic payloads in custom envelopes are not supported
Valid:
ApiResponse<T>
CustomEnvelope<T>
Conditions:
- must be a concrete class
- must declare exactly one type parameter
- must contain exactly one direct payload field of type T
Violations — these fail fast at application startup with a clear error message:
ApiResponse<Page<T>> ❌ nested generic payload
ApiResponse<T, M> ❌ multiple type parameters
WrapperWithoutPayload ❌ no payload field
InterfaceEnvelope<T> ❌ not a concrete class
RecordEnvelope<T> ❌ records are not supported
AbstractEnvelope<T> ❌ not a concrete class
Error handling is not dictated by the starter. It depends entirely on the contract model defined by the service.
Two common patterns:
Pattern A — Separate error protocol (recommended default):
Success → ServiceResponse<T>
Error → ProblemDetail (RFC 9457)
Pattern B — Envelope-based error model (custom / BYOE):
Success → YourEnvelope<T>
Error → YourEnvelope<T> (e.g. errors field)
Key point: The platform enforces success envelope structure, but does not define or restrict error semantics.
No:
- manual schemas (when using Springdoc mode)
- annotations for schema shaping
- overrides of generated structure
The starter owns projection.
curl http://localhost:8084/.../v1/.../1Default envelope (ServiceResponse):
{
"data": { ... },
"meta": { ... }
}Custom envelope (BYOE):
{
"data": { ... },
"status": 200,
"message": "OK"
}Key check:
- exactly one payload field carries the response data
- envelope structure matches your contract (default or custom)
If correct:
Server → OpenAPI → Client is aligned
Full end-to-end examples are provided:
- Spring Boot 3 samples
- Spring Boot 4 samples
- client generation examples
- consumer services
These demonstrate:
- how the contract is produced
- how the client is generated
- how it is consumed safely
If anything is unclear, inspect samples instead of guessing.