Deterministic OpenAPI projection for contract-first Spring Boot services
openapi-generics-server-starter is a Spring Boot starter that projects your Java API contract into a stable, generics-aware OpenAPI document.
It does not redefine the contract. It publishes it as OpenAPI — deterministically and without schema drift.
The role of this module is precise:
Take the canonical runtime contract and project it into OpenAPI without changing its meaning.
- What Problem It Solves
- What It Does
- How It Works
- Usage
- Bring Your Own Envelope (BYOE)
- Supported Contract Shapes
- What It Does NOT Do
- Compatibility
- Determinism Guarantees
- Failure Philosophy
- When To Use
- Mental Model
In typical OpenAPI workflows:
- response envelopes are regenerated per endpoint
- generic structure is flattened or lost
- shared models are duplicated across layers
- generated clients drift from the actual server contract
Over time, this creates unstable OpenAPI output and fragile client generation.
This starter solves that by enforcing one rule:
Java contract is the source of truth. OpenAPI is only its projection.
Once added, the starter:
- discovers supported response envelopes from controller return types
- projects deterministic wrapper schemas into OpenAPI
- preserves supported generic shapes
- adds vendor extensions required by the client codegen layer
- marks contract-owned infrastructure schemas to avoid duplicate generation downstream
No schema annotations. No manual wrapper DTOs. No patching OpenAPI by hand.
Java Contract
↓
Runtime Projection Pipeline
↓
OpenAPI Document
For the default contract:
ServiceResponse<T>
ServiceResponse<Page<T>>
the starter publishes deterministic wrapper schemas such as:
ServiceResponseCustomerDto
ServiceResponsePageCustomerDto
If a custom envelope is configured, the same projection model applies, but the wrapper name is derived from that envelope type instead.
<dependency>
<groupId>io.github.blueprint-platform</groupId>
<artifactId>openapi-generics-server-starter</artifactId>
<version>1.0.0</version>
</dependency>Default envelope:
@GetMapping("/{id}")
public ResponseEntity<ServiceResponse<CustomerDto>> getCustomer(Long id) {
return ResponseEntity.ok(ServiceResponse.of(customer));
}Pagination:
@GetMapping
public ResponseEntity<ServiceResponse<Page<CustomerDto>>> getCustomers() {
return ResponseEntity.ok(ServiceResponse.of(page));
}/v3/api-docs
/v3/api-docs.yaml
That is enough.
The starter also supports custom success envelopes.
Example configuration:
openapi-generics:
envelope:
type: io.example.ApiResponseExample controller signature:
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<CustomerDto>> getCustomer(Long id) {
return ResponseEntity.ok(ApiResponse.ok(customer));
}Custom envelopes must satisfy these rules:
- must be a concrete class
- must declare exactly one type parameter
- must expose exactly one direct payload field of type
T - nested generic payloads are not supported
This means the starter supports:
YourEnvelope<T>
but not:
YourEnvelope<Page<T>>
The default platform envelope remains the only built-in path with container support.
Supported:
ServiceResponse<T>ServiceResponse<Page<T>>
Supported:
YourEnvelope<T>
Not supported:
- nested generic payloads in custom envelopes
- arbitrary collection wrappers
- map-based generic shapes
- multi-parameter envelope types
The scope is intentionally narrow to keep projection deterministic.
This module does not:
- define the canonical contract library
- generate clients
- own DTOs
- change runtime HTTP behavior
- define service-specific error semantics
It only projects success-envelope contract structure into OpenAPI.
| Component | Supported Versions |
|---|---|
| Java | 17+ |
| Spring Boot | 3.4.x, 3.5.x, 4.x |
| springdoc-openapi | 2.8.x, 3.x (WebMvc) |
The starter is designed to provide:
- same contract → same OpenAPI output
- stable wrapper naming
- fixed projection behavior
- no scattered customizer ordering issues
- no contract reinterpretation
OpenAPI output is generated through a single runtime pipeline, then validated before completion.
If the contract shape is unsupported or the projected schema becomes inconsistent, the starter fails fast.
Typical failure mode:
IllegalStateException
Principle:
Incorrect projection is worse than no projection.
Use this module if:
- your service is Spring Boot based
- you publish OpenAPI with springdoc
- you want contract-first OpenAPI output
- you generate clients from published OpenAPI
- you want to avoid wrapper duplication and generic type loss
Think of this module as:
a deterministic projector from Java contract to OpenAPI
Not:
- a documentation helper
- a schema customization toolkit
- a model generator
MIT — see LICENSE