Stop generating duplicate ServiceResponseXxxDto classes when your Spring Boot APIs use generic envelopes.
A drop-in OpenAPI Generator specialization for Java/Spring that preserves ServiceResponse<Page<T>> end-to-end — no model explosion, no manual templates, no fork.
- The problem in 30 seconds
- Get started
- Key features in 1.0.0 (GA)
- How it works
- Compatibility
- Relationship to OpenAPI Generator
- Modules
- References
- Contributing
- License
You return a generic envelope from a Spring Boot controller:
ResponseEntity<ServiceResponse<Page<CustomerDto>>> getCustomers() { ... }OpenAPI Generator gives your clients this:
// ❌ Generated by default — one of these per endpoint
class ServiceResponsePageCustomerDto {
PageCustomerDto data;
Meta meta;
}The envelope is duplicated per endpoint, generics are flattened,
and getData() returns a flattened type that needs casting. Multiply by every
endpoint and every service — the model graph quietly explodes.
With openapi-generics, the same client looks like this:
// ✅ Generated with openapi-generics
public class ServiceResponsePageCustomerDto
extends ServiceResponse<Page<CustomerDto>> {}One envelope. Generics preserved. Same contract on the server, in the OpenAPI spec, and in every generated client.
| Before default OpenAPI Generator |
After with openapi-generics |
![]() |
![]() |
Define your contract once in Java — reuse it everywhere without drift.
Run a sample producer (Spring Boot 3; equivalent pipeline under samples/spring-boot-4/):
cd samples/spring-boot-3/customer-service
mvn clean package
java -jar target/customer-service-*.jarVerify it's running:
- Swagger UI — http://localhost:8084/customer-service/swagger-ui/index.html
- OpenAPI — http://localhost:8084/customer-service/v3/api-docs.yaml
Generate the client from the same pipeline:
cd samples/spring-boot-3/customer-service-client
mvn clean installInspect the generated wrapper:
public class ServiceResponsePageCustomerDto
extends ServiceResponse<Page<CustomerDto>> {}No duplicated envelope. Generics preserved. Contract reused end-to-end.
You don't copy code from this repo — you add two building blocks.
Server (producer):
<dependency>
<groupId>io.github.blueprint-platform</groupId>
<artifactId>openapi-generics-server-starter</artifactId>
<version>1.0.1</version>
</dependency>Client (consumer):
<parent>
<groupId>io.github.blueprint-platform</groupId>
<artifactId>openapi-generics-java-codegen-parent</artifactId>
<version>1.0.1</version>
</parent>That's it. Run your service, generate the client, get contract-aligned wrappers.
For BYOE, BYOC, and progressive adoption flags, see Key features.
| Feature | What it does | Default |
|---|---|---|
| BYOE — Bring Your Own Envelope | Use your existing response envelope (e.g. ApiResponse<T>) instead of ServiceResponse<T>. No migration required. |
ServiceResponse<T> |
| BYOC — Bring Your Own Contract | Reuse your own domain DTOs instead of generating new copies. | Generate from spec |
| Progressive adoption | Toggle contract-aware generation per client module via a single Maven property. Mix old and new clients in the same repo. | Enabled |
| Deterministic generation | Upstream OpenAPI Generator templates are extracted fresh every build, patched with a single generic-aware branch, and the build fails fast if upstream drifts. | — |
| End-to-end samples | Producer + client pipelines for both Spring Boot 3 and Spring Boot 4. | See samples/ |
Already have an ApiResponse<T> (or any other envelope) across your services?
Point the platform at it — no rewrites:
<additionalProperties>
<additionalProperty>
openapi-generics.envelope=io.example.contract.ApiResponse
</additionalProperty>
</additionalProperties>- If unset →
ServiceResponse<T>is used as the default envelope. - If set → your envelope becomes the base of every generated wrapper.
- Server side picks it up automatically when using Springdoc; spec-first
pipelines can declare it via
x-api-wrapperextensions in OpenAPI directly.
Scope: BYOE supports envelopes with a single direct generic payload (
YourEnvelope<T>). Nested forms likeYourEnvelope<Page<T>>are out of scope and fail fast at startup — see the BYOE guide for rationale.
Stop regenerating DTOs you already own. Map them once:
<additionalProperties>
<additionalProperty>
openapi-generics.response-contract.CustomerDto=io.example.contract.CustomerDto
</additionalProperty>
</additionalProperties>The generated client references your existing CustomerDto directly
instead of producing a near-duplicate model.
Generate some clients with contract-aware behavior, others with stock OpenAPI Generator — same repo, same parent, no fork:
<openapi.generics.skip>true</openapi.generics.skip>openapi.generics.skip |
Behavior |
|---|---|
false (default) |
Contract-aware generation |
true |
Standard OpenAPI generation |
openapi-generics is built on one principle: the Java contract is the source of truth, OpenAPI is a projection of it.
Java Contract (SSOT)
↓
OpenAPI (projection — not authority)
↓
Generator (deterministic reconstruction)
↓
Client (contract-aligned types)
In practice this means:
- the response envelope is a shared contract, not a generated artifact
- generated client classes extend that contract instead of redefining it
- OpenAPI carries metadata (
x-api-wrapper,x-data-container), not authority - clients and servers stay aligned even as the spec evolves
The diagram shows two parallel phases — projection (server → spec) and enforcement (spec → client) — both rooted in a single shared authority layer. The adapter boundary keeps generated code isolated from application logic.
For internal architecture and design decisions: docs/architecture/platform.md
- ✔ Contract identity is preserved across server, spec, and client
- ✔ Contract ownership stays with you (envelope and DTOs are reusable, not duplicated)
- ✔ Generics are preserved within the supported scope
- ✔ Client generation is deterministic — same spec, same output, every build
- ✔ External models are reused, not regenerated
- ✔ Upstream OpenAPI Generator drift is detected at build time, not at runtime
OpenAPI Generics is currently verified with:
- Java: 17+
- Spring Boot: 3.4.x, 3.5.x, 4.x
- springdoc-openapi: 2.8.x (Spring Boot 3.x), 3.x (Spring Boot 4.x)
- OpenAPI Generator: 7.x
- Server scope: Spring WebMvc (
springdoc-openapi-starter-webmvc-ui)
See the full compatibility matrix and support policy: Compatibility & Support Policy
This is not a fork of OpenAPI Generator. It uses the upstream tool as a Maven dependency and adds a Java/Spring Boot specialization layer on top.
What stays upstream:
- OpenAPI Generator (used as-is, fresh extraction on every build)
- OpenAPI 3.x spec (only
x-vendor extensions added) - The full upstream template chain
What this project adds:
- A custom generator extending
JavaClientCodegen - A surgical patch to upstream
model.mustachethat injects a single generic-aware branch — the rest is untouched - Vendor extensions (
x-api-wrapper,x-data-container) carrying generic semantics through the spec - Server-side
OpenApiCustomizerfor contract introspection
Why not just drop a custom model.mustache into templateDirectory?
That approach freezes a snapshot of the upstream template and quietly
falls behind as upstream evolves. This project keeps upstream as the
source of structure, injects only the generic-aware branch, and fails
the build fast if upstream changes invalidate the patch.
Cross-language parity is an explicit non-goal. Java generics deserve a generics-aware solution; other languages may benefit from different specializations on top of the same upstream.
- openapi-generics-contract
- openapi-generics-server-starter
- openapi-generics-java-codegen
- openapi-generics-java-codegen-parent
- openapi-generics-platform-bom
-
Adoption Guide (GitHub Pages)
Spring Boot OpenAPI Generics — Adoption Guide -
Medium Article
We Made OpenAPI Generator Think in Generics -
RFC 9457
Problem Details for HTTP APIs
The project is in early adoption and v1.0.1 just shipped. The most useful thing right now is hearing from people who are actually pulling it into a build.
If you've tried it — even briefly, even just evaluated — there's a pinned discussion with the questions I'd most like answered:
👉 v1.0.1 is out — and I'd like to hear how you're using openapi-generics
It covers BYOE friction, BYOC patterns, error model choice (RFC 9457 vs envelope-based), and Spring Boot 3 vs 4 experience. Two sentences are enough. Anonymous or company-redacted feedback is fine.
For everything else:
- 🐛 Concrete bugs → Issues
- 💡 Design suggestions → Ideas
- 🔗 Private feedback → LinkedIn DM
A 👍 reaction on Discussion #20 is itself a useful signal — it tells me someone real is using this without requiring you to share details publicly.
MIT — see LICENSE


