Prevent OpenAPI Generator from redefining your Java contract.
A contract-preserving OpenAPI Generator specialization for Java/Spring that keeps shared envelopes and DTOs reusable across service boundaries — no model explosion, no manual templates, no fork.
- The problem in 30 seconds
- Get started
- Real-World Example
- What's New in 1.2
- Key Features
- How it works
- Compatibility
- Relationship to OpenAPI Generator
- Modules
- References
- Contributing
- License
You return a typed generic contract from a Spring Boot controller:
ResponseEntity<ServiceResponse<Page<CustomerDto>>> getCustomers() { ... }This is not just a JSON shape.
It is a contract boundary.
ServiceResponse<T> defines the response language.
Page<T> defines the pagination contract.
CustomerDto defines the payload.
A standard OpenAPI client generation flow often turns that boundary into a generated model:
// ❌ Generated by default
class ServiceResponsePageCustomerDto {
PageCustomerDto data;
Meta meta;
}That class may look equivalent, but it is not the same contract anymore.
The envelope has been copied into generated code. The client now owns a generated reinterpretation of a contract that already existed.
With openapi-generics, the generated wrapper keeps the original contract identity:
// ✅ Generated with openapi-generics
public class ServiceResponsePageCustomerDto
extends ServiceResponse<Page<CustomerDto>> {}The generated class does not redefine the envelope. It only binds concrete generic parameters to the shared contract type.
| Before default OpenAPI Generator |
After with openapi-generics |
![]() |
![]() |
This is the core architectural difference:
Default generation:
OpenAPI schema becomes the source for a new envelope model.
OpenAPI Generics:
The Java contract remains the source of truth.
OpenAPI carries enough metadata to reconstruct it.
For one endpoint, the difference is easy to ignore.
Across many generated clients, it becomes expensive.
A BFF, aggregator, or downstream service may consume dozens of APIs. If every generated client brings its own envelope and container copies, each service boundary adds more duplicated models, more mapping code, and more places for contract drift.
OpenAPI Generics takes the opposite position:
The response envelope is not generated output.
It is shared contract authority.
Payload models may be generated.
Endpoint clients may be generated.
Transport code may be generated.
But the envelope should remain owned by the contract.
The result is a generated client that behaves as a thin transport adapter instead of an alternative contract definition.
One envelope. One contract identity. Generics preserved from producer to consumer.
Define your contract once in Java, project it through OpenAPI, and reconstruct it deterministically in the generated client.
Runnable end-to-end sample stacks are available under samples, including Spring Boot 3, Spring Boot 4, and dedicated type-coverage samples covering supported payload types, wrapper shapes, and generic response patterns.
samples
├── domain-contracts
├── spring-boot-3
│ ├── customer-service
│ ├── customer-service-client
│ └── customer-service-consumer
├── spring-boot-4
│ ├── customer-service
│ ├── customer-service-client
│ └── customer-service-consumer
└── type-coverage
├── service-response
└── byoe-response
See samples/README.md for the full topology, Docker-based setup, and stack overview.
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.2.0</version>
</dependency>Important
openapi-generics-server-starter does not intercept application requests or change endpoint runtime behavior.
It is invoked only when Springdoc generates the OpenAPI document, for example when /v3/api-docs or /v3/api-docs.yaml is requested, or when the document is generated in CI.
If the OpenAPI document is never generated, this component does nothing.
Client (consumer):
<parent>
<groupId>io.github.blueprint-platform</groupId>
<artifactId>openapi-generics-java-codegen-parent</artifactId>
<version>1.2.0</version>
</parent>The client generation flow uses the java-generics-contract generator
instead of the standard java generator to preserve generic wrapper semantics.
That's it. Run your service, generate the OpenAPI document, generate the client, and get contract-aligned wrappers.
For BYOE, BYOC, and fallback-to-standard-generation options, see the Key features section below.
See the Licensing Project for a complete end-to-end BYOE example using a shared ApiResponse<T> contract.
The project demonstrates:
- Spring Boot server integration with
openapi-generics-server-starter - Contract-first OpenAPI projection
- Generated Java client using
openapi-generics-java-codegen-parent - Shared
ApiResponse<T>reuse across service, client, SDK, and CLI - Docker-based end-to-end verification
Version 1.2 builds on the container-aware infrastructure introduced in 1.1 by making the container model extensible and improving generated artifact quality.
Applications can now register their own generic container contracts while continuing to use the same contract-first projection and deterministic client reconstruction pipeline.
Built-in response shapes continue to work unchanged:
ServiceResponse<T>
ServiceResponse<List<T>>
ServiceResponse<Set<T>>
ServiceResponse<Page<T>>The same support is available when using your own shared response envelope:
ApiResponse<T>
ApiResponse<List<T>>
ApiResponse<Set<T>>
ApiResponse<Page<T>>In addition, applications can now register their own generic container contracts:
openapi-generics:
envelope:
type: io.example.contract.ApiResponse
containers:
- type: io.example.contract.Paging
item-property: content
- type: io.example.contract.Window
item-property: itemsConfigured containers participate in the same projection, metadata generation, and reconstruction pipeline as the built-in container types.
Additional improvements in 1.2 include:
- application-defined generic container support
- preservation of Java container identity through
x-data-container-type - deterministic generated-source hygiene for Java clients
- cleaner generated Java imports and formatting
- expanded regression and metadata validation
- full backward compatibility with existing 1.1 contracts
No migration is required for existing users.
| Feature | What it does | Default |
|---|---|---|
| BYOE — Bring Your Own Envelope | Reuse your existing response envelope (for example ApiResponse<T>) instead of ServiceResponse<T>. No migration required. |
ServiceResponse<T> |
| BYOC — Bring Your Own Contract | Reuse your existing domain DTOs instead of generating duplicate models. | Generate from spec |
| Application-defined containers | Register your own generic container contracts (for example Paging<T> or Window<T>) and have them participate in the same projection, metadata, and reconstruction pipeline as built-in containers. |
Built-in containers only |
| Container-aware reconstruction | Deterministically reconstruct built-in and configured generic container types from OpenAPI metadata instead of using container-specific generation logic. | Enabled |
| Fallback to standard generation | Disable the generics-aware template patching with a single Maven property. To fully revert to stock OpenAPI Generator behavior, switch the client module to generatorName=java. |
Generics-aware generation enabled |
| Deterministic generation | Apply deterministic template patching, generated-source hygiene, and build-time validation to produce stable, reproducible Java clients. | Enabled |
| End-to-end samples | Complete producer, client, and consumer pipelines for Spring Boot 3, Spring Boot 4, ServiceResponse, and BYOE scenarios. |
See samples |
Already have an ApiResponse<T> or another response envelope shared across your services?
Use it as the contract source of truth without migrating to a platform-specific wrapper.
On the server/producer side:
openapi-generics:
envelope:
type: io.example.contract.ApiResponse
# Optional: register application-defined generic containers
containers:
- type: io.example.contract.Paging
item-property: content
- type: io.example.contract.Window
item-property: itemsOn the client/codegen side:
<additionalProperties>
<additionalProperty>
openapi-generics.envelope=io.example.contract.ApiResponse
</additionalProperty>
</additionalProperties>Key characteristics:
- Your envelope remains the contract owner.
- Generated wrappers extend your envelope instead of redefining it.
- The envelope type must be available on the client classpath.
- Springdoc-based projection is automatic.
- Application-defined containers are optional and participate in the same projection and reconstruction model as built-in containers when configured.
- Spec-first pipelines can publish equivalent semantics through OpenAPI vendor extensions.
Reuse DTOs you already own instead of generating duplicate models.
Map OpenAPI model names to existing Java types:
<additionalProperties>
<additionalProperty>
openapi-generics.response-contract.CustomerDto=io.example.contract.CustomerDto
</additionalProperty>
<additionalProperty>
openapi-generics.response-contract.AddressDto=io.example.contract.AddressDto
</additionalProperty>
<additionalProperty>
openapi-generics.response-contract.OrderDto=io.example.contract.OrderDto
</additionalProperty>
</additionalProperties>Each mapping follows:
openapi-generics.response-contract.<OpenAPI model name>=<fully-qualified Java type>
The generated client imports and reuses those contract types directly instead of producing duplicate DTO definitions.
Disable the generics-aware template patching with a single Maven property:
<openapi.generics.skip>true</openapi.generics.skip>This skips the template extraction, patching, and overlay steps provided by openapi-generics-java-codegen-parent.
To fully revert to stock OpenAPI Generator behavior:
<generatorName>java</generatorName>openapi.generics.skip |
Behavior |
|---|---|
false (default) |
Apply generics-aware template patching |
true |
Skip generics-aware template patching |
Use this mode for output comparison, troubleshooting, or temporary opt-out scenarios.
OpenAPI Generics preserves Java generic contract semantics across the OpenAPI lifecycle.
It keeps generic response envelopes, container payloads, and shared DTO contracts aligned from Spring Boot producers to generated Java clients.
The project is built on one principle:
The Java contract is the source of truth.
OpenAPI is a projection of that contract.
Client generation deterministically reconstructs the original contract.
Java Contract (SSOT)
↓
OpenAPI Projection
↓
Deterministic Client Reconstruction
↓
Contract-Aligned Client
In practice this means:
- the response envelope remains a shared contract, not a generated artifact
- generated wrapper classes extend existing contracts instead of redefining them
- OpenAPI carries contract metadata, not contract ownership
- container semantics are preserved through projection metadata
- clients and servers remain aligned as contracts evolve
Wrapper semantics can be published in two ways.
The server starter discovers generic response contracts, projects wrapper schemas, enriches them with contract metadata, and marks infrastructure models so generated clients reconstruct the original Java contract instead of regenerating it.
Teams that own their OpenAPI documents can publish the same contract semantics directly through the OpenAPI Generics vendor extensions:
x-api-wrapperx-api-wrapper-datatypex-data-containerx-data-container-typex-data-itemx-ignore-model
Together, these extensions describe wrapper semantics, payload type, container identity, item type, and generation behavior, allowing generated clients to reconstruct the original Java contract deterministically without requiring Spring-based projection.
The architecture consists of two complementary phases:
- Projection — derives deterministic OpenAPI metadata from Java contracts.
- Reconstruction — restores contract-aligned Java client types during generation using the projected metadata.
Both phases share the same contract authority while keeping generated code isolated from application code.
For internal architecture and design decisions, see the architecture documentation.
- ✔ Contract identity is preserved across server, OpenAPI, and generated client
- ✔ Contract ownership remains with your application, not generated code
- ✔ Built-in and application-defined generic containers share the same projection and reconstruction pipeline
- ✔ Container identity is preserved through deterministic projection metadata
- ✔ Generated clients reconstruct contract semantics instead of redefining them
- ✔ External models can be reused without duplication (BYOC)
- ✔ Generated source hygiene produces cleaner, deterministic Java artifacts
- ✔ Upstream OpenAPI Generator changes are detected during the build rather than 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
OpenAPI Generics is not a fork of OpenAPI Generator.
It builds on top of the upstream project and adds a Java/Spring Boot specialization layer focused on preserving contract-owned generic structures across the OpenAPI lifecycle.
The generated OpenAPI document remains valid OpenAPI and can be consumed by standard OpenAPI tooling without modification.
What OpenAPI Generics adds:
- Generics-aware Java client generation through an OpenAPI Generator specialization
- Contract metadata via vendor extensions such as
x-api-wrapper,x-data-container,x-data-container-type, andx-data-item - Server-side OpenAPI enrichment through Springdoc integration
- Container-aware reconstruction for both built-in and application-defined generic container contracts
- Deterministic generated-source hygiene for cleaner Java client output
The project keeps OpenAPI Generator as the source of template structure and applies a minimal generics-aware extension layer rather than maintaining a forked template set.
In short:
Java Contract
↓
OpenAPI Projection
↓
Deterministic Client Reconstruction
OpenAPI Generics preserves contract semantics while remaining fully compatible with the OpenAPI ecosystem.
OpenAPI Generics does not lock consumers to a specific OpenAPI Generator version.
The provided parent configuration includes a tested default, but consumers may override the openapi-generator.version property within the supported 7.x range.
OpenAPI Generics owns contract semantics. Consumers own the OpenAPI Generator version they choose to run.
| Module | Responsibility |
|---|---|
openapi-generics-contract |
Shared response contracts and platform-owned generic types. |
openapi-generics-server-starter |
Spring Boot integration that projects generic contract metadata into OpenAPI. |
openapi-generics-java-codegen |
OpenAPI Generator specialization that reconstructs contract-aligned Java clients. |
openapi-generics-java-codegen-parent |
Maven parent for client generation, template patching, and generated-source hygiene. |
openapi-generics-platform-bom |
Dependency alignment for OpenAPI Generics modules. |
-
Rationale: Why OpenAPI Generics Exists
Explains the architectural motivation, design decisions, engineering trade-offs, and ecosystem constraints behind the project. -
Documentation Site
Official GitHub Pages documentation for adoption guides, architecture, compatibility, and project usage.
- We Made OpenAPI Generator Think in Generics
Background, motivation, and technical overview of the project.
-
OpenAPI Specification
Official specification for describing HTTP APIs. -
JSON Schema Draft 2020-12
Specification for describing and validating JSON documents. -
RFC 9457 – Problem Details for HTTP APIs
Standard format for error responses in HTTP APIs.
OpenAPI Generics is still evolving, and real-world feedback is the most valuable input for future improvements.
Whether you're evaluating the project, running it in a prototype, or using it in production, feedback is welcome.
Questions, bug reports, design discussions, and adoption experiences all help shape the roadmap.
For contributions and feedback:
- 🐛 Bugs and issues → Issues
- 💡 Design discussions and feature ideas → Discussions
- 🔗 Private feedback → LinkedIn
If OpenAPI Generics helped solve a real problem in your environment, hearing about that experience is often just as valuable as a code contribution.
MIT — see LICENSE


