Server-Side Adoption β Contract-First OpenAPI Publication
Publish a deterministic, generics-aware OpenAPI from Spring Boot with one contract and zero duplication.
A practical, minimal, and deterministic guide for exposing a contract-aligned OpenAPI from a Spring Boot (WebMVC) service.
This guide is intentionally action-oriented: you implement a small set of rules, and the platform guarantees the rest.
π Table of Contents
- β‘ 60-second quick start
- π― What the server is responsible for
- π§© The only rule that matters
- π¦ Minimal dependencies
- βοΈ What you actually write
- π§ What gets published to OpenAPI
- β οΈ Rules (do NOT break these)
- π Quick verification
- π§ Mental model
- π« What this guide does NOT cover
- π§Ύ Summary
β‘ 60-second quick start
You want:
- deterministic OpenAPI output
- no envelope duplication
- generics preserved in generated clients
Do this:
1) Add dependency
<dependency>
<groupId>io.github.blueprintplatform</groupId>
<artifactId>openapi-generics-server-starter</artifactId>
</dependency>
2) Return contract types
ServiceResponse<CustomerDto>
ServiceResponse<Page<CustomerDto>>
3) Expose OpenAPI
/v3/api-docs.yaml
Done.
π― What the server is responsible for
The server has exactly one responsibility:
Publish a correct, deterministic projection of the runtime contract.
It does not:
- generate clients
- define alternative response models
- adapt for specific generators
It only performs:
Contract β OpenAPI (projection)
Everything else (generation, typing, reuse) happens downstream.
π§© The only rule that matters
There is one canonical success envelope:
ServiceResponse<T>
Supported shapes:
ServiceResponse<T>
ServiceResponse<Page<T>>
This constraint is what enables:
- deterministic schema generation
- stable naming
- type-safe client reconstruction
π¦ Minimal dependencies
No custom configuration is required.
<dependency>
<groupId>io.github.blueprintplatform</groupId>
<artifactId>openapi-generics-server-starter</artifactId>
</dependency>
Assumes:
- Spring Boot (WebMVC)
- Springdoc enabled (default
/v3/api-docs)
βοΈ What you actually write
You write only your domain contract.
Controller example
@GetMapping("/{id}")
public ResponseEntity<ServiceResponse<CustomerDto>> getCustomer(...) {
return ResponseEntity.ok(ServiceResponse.of(dto));
}
Pagination example
@GetMapping
public ResponseEntity<ServiceResponse<Page<CustomerDto>>> getCustomers(...) {
return ResponseEntity.ok(ServiceResponse.of(page));
}
Thatβs it.
No annotations. No schema configuration. No wrapper DTOs.
π§ What gets published to OpenAPI
From this runtime type:
ServiceResponse<CustomerDto>
The system produces a deterministic schema:
ServiceResponseCustomerDto
Characteristics:
- stable, predictable naming
allOf-based composition- vendor extensions for downstream generation (e.g.
x-api-wrapper)
Important:
OpenAPI is a projection artifact β not the source of truth.
β οΈ Rules (do NOT break these)
These are architectural constraints, not conventions.
1. Only constrain the envelope
ServiceResponse<T>
ServiceResponse<Page<T>>
2. Do NOT replace the envelope
β Wrong:
CustomerResponse
ApiResponse
PagedResult
Replacing the envelope breaks cross-layer consistency and determinism.
3. Payload is completely free
β Valid:
ServiceResponse<CustomerDto>
ServiceResponse<CustomerDeleteResponse>
ServiceResponse<Anything>
The system constrains structure β not domain models.
4. Errors are NOT wrapped
ProblemDetail (RFC 9457)
Errors are handled as a protocol, separate from success responses.
5. Do NOT customize OpenAPI
No:
- manual schemas
- custom annotations
- overrides
The starter owns the projection.
π Quick verification
Run a request:
curl http://localhost:8084/.../v1/.../1
Expected shape:
{
"data": { ... },
"meta": { ... }
}
If this is correct, then:
Server β OpenAPI β Client will remain consistent
π§ Mental model
Think of the server as:
A deterministic compiler from runtime contract β OpenAPI
Not:
- a schema designer
- a generator configuration layer
π« What this guide does NOT cover
This guide intentionally excludes:
- client generation
- template customization
- generator internals
These belong to the client-side adoption guide.
π§Ύ Summary
If you remember only this:
Return ServiceResponse<T>
Add the starter
Do nothing else
The platform handles:
- OpenAPI projection
- schema stability
- downstream compatibility
π‘ MIT License