Skip to content

bsayli/spring-boot-openapi-generics-clients

Spring Boot + OpenAPI Generator — End-to-End Generics-Aware API Clients

Build Release codecov Java Spring Boot OpenAPI Generator License: MIT


OpenAPI Generics Cover
End-to-end generics-aware OpenAPI clients — unified { data, meta } responses without boilerplate.

Modern, type-safe OpenAPI client generation — powered by Spring Boot 3.4, Java 21, and OpenAPI Generator 7.16.0.
This repository demonstrates a production-grade setup where backend and client remain fully aligned through generics, supporting nested envelopes like ServiceResponse<Page<T>> and standardized RFC 9457 — Problem Details for HTTP APIs error handling.

🧠 RFC 9457 vs RFC 7807
RFC 9457 supersedes 7807 and formalizes application/problem+json / application/problem+xml for HTTP APIs.
Spring Framework 6+ natively supports this via the built-in ProblemDetail class, ensuring consistent error serialization between server and client.


📑 Table of Contents

A clean architecture pattern for building generics-aware OpenAPI clients that stay fully type-safe, consistent, and boilerplate-free.


📦 Modules


🚀 Problem & Motivation

OpenAPI Generator, by default, does not handle generic response types.

When backend APIs wrap payloads in ServiceResponse<T> (e.g., the unified { data, meta } envelope), the generator produces duplicated models per endpoint instead of a single reusable generic base.

This leads to:

  • ❌ Dozens of almost-identical response classes
  • ❌ Higher maintenance overhead
  • ❌ Harder to evolve a single envelope contract across services
// Default OpenAPI output (before)
public class ServiceResponseCustomerDto {
  private CustomerDto data;
  private Meta meta;
}

public class ServiceResponsePageCustomerDto {
  private PageCustomerDto data; // instead of Page<CustomerDto>
  private Meta meta;
}

public class ServiceResponseCustomerDeleteResponse {
  private CustomerDeleteResponse data;
  private Meta meta;
}
// ... dozens of duplicates

By applying a Mustache overlay, these are replaced by thin wrappers extending a single generic base (ServiceClientResponse<T>), preserving Page<T> and ensuring consistent, type-safe API clients.


💡 Solution Overview

This project provides a full-stack pattern aligning Spring Boot services and OpenAPI clients through automatic schema introspection and template overlay.

🖥️ Server-Side (Producer)

A Springdoc customizer inspects controller return types such as:

ResponseEntity<ServiceResponse<CustomerDto>>
ResponseEntity<ServiceResponse<Page<CustomerDto>>>

and enriches the generated OpenAPI schema with vendor extensions:

Single type (ServiceResponse<T>):

x-api-wrapper: true
x-api-wrapper-datatype: CustomerDto

Nested generics (ServiceResponse<Page<T>>):

x-api-wrapper: true
x-data-container: Page
x-data-item: CustomerDto

These extensions make the OpenAPI spec aware of generic and nested structures — no manual annotations required.

💻 Client-Side (Consumer)

Custom Mustache overlays redefine OpenAPI templates to generate thin, type-safe wrappers extending the reusable base ServiceClientResponse<T>.

Generated output:

// Single
public class ServiceResponseCustomerDto
        extends ServiceClientResponse<CustomerDto> {}

// Paged
public class ServiceResponsePageCustomerDto
        extends ServiceClientResponse<Page<CustomerDto>> {}

✅ Supports nested generics like ServiceClientResponse<Page<CustomerDto>> ✅ Automatically maps error responses into RFC 9457 Problem Details


⚙️ Architecture Overview

OpenAPI Generics Architecture
End-to-end generics-aware architecture: from Spring Boot producer to OpenAPI client consumer.

Layer Description
Server (Producer) Publishes an OpenAPI 3.1-compliant spec via Springdoc 2.8.13 with auto-registered wrapper schemas
Client (Consumer) Uses OpenAPI Generator 7.16.0 with Mustache overlays for generics support
Envelope Model Unified { data, meta } response structure
Error Handling RFC 9457-compliant Problem Details decoded into ClientProblemException
Nested Generics Full support for ServiceResponse<Page<T>>

⚡ Quick Start

# Run backend service
cd customer-service && mvn spring-boot:run

# Generate and build client
cd ../customer-service-client && mvn clean install

Generated wrappers appear under:

target/generated-sources/openapi/src/gen/java

Each wrapper extends ServiceClientResponse<T> and aligns with the unified { data, meta } envelope.

Now you can test end-to-end type-safe responses via the generated client — validating both single and paged envelopes.


🔄 Generated Wrappers — Before & After

Before (duplicated full models):

Generated client before generics support
Each endpoint generated its own response class, duplicating data and meta fields.

After (thin generic wrapper):

Generated client after generics support
Each endpoint now extends the reusable ServiceClientResponse<Page<T>> base, eliminating boilerplate and preserving type safety.


🧱 Example Responses

Unified envelope structure applies to both single and paged results.

🧩 Single Item Example (ServiceClientResponse<CustomerDto>)

{
  "data": {
    "customerId": 1,
    "name": "Jane Doe",
    "email": "[email protected]"
  },
  "meta": {
    "serverTime": "2025-01-01T12:34:56Z",
    "sort": []
  }
}

📄 Paged Example (ServiceClientResponse<Page<CustomerDto>>)

{
  "data": {
    "content": [
      { "customerId": 1, "name": "Jane Doe", "email": "[email protected]" },
      { "customerId": 2, "name": "John Smith", "email": "[email protected]" }
    ],
    "page": 0,
    "size": 5,
    "totalElements": 37,
    "totalPages": 8,
    "hasNext": true,
    "hasPrev": false
  },
  "meta": {
    "serverTime": "2025-01-01T12:34:56Z",
    "sort": [ { "field": "CUSTOMER_ID", "direction": "ASC" } ]
  }
}

Content-Type: application/json (success) Content-Type: application/problem+json (error — RFC 9457)

Client Usage

ServiceClientResponse<Page<CustomerDto>> resp =
    customerClientAdapter.getCustomers("Jane", null, 0, 5, SortField.CUSTOMER_ID, SortDirection.ASC);

Page<CustomerDto> page = resp.getData();
for (CustomerDto c : page.content()) {
    // ...
}

🧩 Tech Stack

Component Version Purpose
Java 21 Language baseline
Spring Boot 3.4.10 REST + OpenAPI provider
Springdoc 2.8.13 OpenAPI 3.1 integration
OpenAPI Generator 7.16.0 Generics-aware code generation
HttpClient5 5.5 Pooled, production-ready HTTP backend

✅ Key Features

  • 🔹 Unified { data, meta } response model
  • 🔹 Nested generics support — ServiceResponse<Page<T>>
  • 🔹 RFC 9457-compliant Problem Details (application/problem+json)
  • 🔹 Mustache overlays for thin wrapper generation
  • 🔹 Full alignment between producer and consumer
  • 🔹 Zero boilerplate — clean, evolvable, and type-safe

✨ Usage Example

public interface CustomerClientAdapter {
    ServiceClientResponse<CustomerDto> createCustomer(CustomerCreateRequest request);
    ServiceClientResponse<CustomerDto> getCustomer(Integer customerId);
    ServiceClientResponse<Page<CustomerDto>> getCustomers();
}

A stable adapter contract hides generated artifacts while preserving strong typing and client independence.


📘 Adoption Guides

See integration details under docs/adoption:


🔗 References & External Links


🛡 License

Licensed under MIT — see LICENSE.


💬 Feedback

If you spot an error or have suggestions, open an issue or join the discussion — contributions are welcome. 💭 Start a discussion →


🗣️ Community & Discussions

Using this pattern in your project?
Share your experience or ideas in GitHub Discussions — even short notes help others learn and improve the pattern.

💡 Real-world usage feedback helps evolve the templates and guides faster.


🤝 Contributing

Contributions, issues, and feature requests are welcome! Feel free to open an issue or submit a PR.


⭐ Support

If you found this project helpful, please give it a ⭐ on GitHub — it helps others discover it.


Barış Saylı GitHub · Medium · LinkedIn


📖 This repository accompanies the article
We Made OpenAPI Generator Think in Generics
published on Medium, which serves as the canonical write-up for this implementation.