Skip to content

Commit f2e9578

Browse files
committed
docs: full documentation overhaul for generics-aware architecture and RFC 7807 alignment
- Rewrote root README.md with modern architectural overview, response flow diagram, and end-to-end example - Updated all adoption guides under /docs/adoption: - server-side-adoption.md → now includes {data, meta} envelope and optional Problem extensions - client-side-adoption.md → fully refactored for RFC 7807 ProblemDetail decoding and RestClient integration - client-side-adoption-pom.md → clarified Maven plugin chain, properties, and new additionalProperties syntax - Added consistent formatting, section numbering, and Google-style code blocks - Unified terminology: “ServiceResponse<T> / ServiceClientResponse<T>” and “Meta” across all examples - Refined OpenAPI 3.1 alignment (springdoc 2.8.13, generator 7.16.0, HttpClient5 5.5) - Introduced clean architecture diagram for end-to-end client generation flow - Improved readability and SEO structure for GitHub Pages rendering Result: project docs now reflect the final {data, meta} response model, nested generics support, and full ProblemDetail compliance.
1 parent 042a149 commit f2e9578

18 files changed

+1109
-944
lines changed

README.md

Lines changed: 122 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Spring Boot + OpenAPI Generator — Type-Safe Generics for Clean API Clients
1+
# Spring Boot + OpenAPI Generator — End-to-End Generics-Aware API Clients
22

33
[![Build](https://github.com/bsayli/spring-boot-openapi-generics-clients/actions/workflows/build.yml/badge.svg)](https://github.com/bsayli/spring-boot-openapi-generics-clients/actions/workflows/build.yml)
44
[![Release](https://img.shields.io/github/v/release/bsayli/spring-boot-openapi-generics-clients?logo=github\&label=release)](https://github.com/bsayli/spring-boot-openapi-generics-clients/releases/latest)
@@ -13,89 +13,97 @@
1313
<p align="center">
1414
<img src="docs/images/openapi-generics-cover.png" alt="OpenAPI Generics Cover" width="720"/>
1515
<br/>
16-
<em>End-to-end generics-aware OpenAPI clients — unified `{ data, meta }` responses without boilerplate.</em>
16+
<em>End-to-end generics-aware OpenAPI clients — unified <code>{ data, meta }</code> responses without boilerplate.</em>
1717
</p>
1818

19-
**Type-safe client generation with Spring Boot & OpenAPI using generics.**
20-
This repository demonstrates how to extend OpenAPI Generator to support **nested generics** and the new unified
21-
`{ data, meta }` response model — eliminating duplicated wrappers and repetitive boilerplate.
19+
**Modern, type-safe OpenAPI client generation** — powered by **Spring Boot 3.4**, **Java 21**, and **OpenAPI Generator 7.16.0**.
20+
This repository demonstrates a production-grade architecture where backend and client are fully aligned through generics, enabling nested generic envelopes (ServiceResponse<Page<T>>) and RFC 7807 ProblemDetail (Problem Details for HTTP APIs)–based error handling.
2221

2322
---
2423

2524
## 📑 Table of Contents
2625

27-
* 📦 [Modules](#-modules-in-this-repository)
26+
* 📦 [Modules](#-modules)
2827
* 🚀 [Problem & Motivation](#-problem--motivation)
2928
* 💡 [Solution Overview](#-solution-overview)
3029
* ⚙️ [New Architecture Highlights](#-new-architecture-highlights)
3130
*[Quick Start](#-quick-start)
31+
* 🖼 [Generated Client Wrapper — Before & After](#-generated-client-wrapper--before--after)
32+
* 🧱 [Example Responses](#-example-responses)
3233
* 🧩 [Tech Stack](#-tech-stack)
3334
*[Key Features](#-key-features)
3435
*[Usage Example](#-usage-example)
3536
* 📘 [Adoption Guides](#-adoption-guides)
3637
* 🔗 [References & Links](#-references--links)
3738

38-
> *A practical reference for building fully generics-aware OpenAPI clients using Spring Boot 3.4, Java 21, and Mustache
39-
overlays.*
39+
> *A clean architecture pattern for building generics-aware OpenAPI clients that stay fully type-safe, consistent, and boilerplate-free.*
4040
4141
---
4242

43-
## 📦 Modules in this Repository
43+
## 📦 Modules
4444

45-
* [**customer-service**](customer-service/README.md) — sample backend exposing `/v3/api-docs.yaml`
46-
* [**customer-service-client**](customer-service-client/README.md) — generated OpenAPI client with generics-aware
47-
wrappers
45+
* [**customer-service**](customer-service/README.md) — sample backend exposing `/v3/api-docs.yaml` via Springdoc
46+
* [**customer-service-client**](customer-service-client/README.md) — generated OpenAPI client with generics-aware wrappers
4847

4948
---
5049

5150
## 🚀 Problem & Motivation
5251

53-
OpenAPI Generator doesn’t natively understand **generic types**. When backend responses use envelopes like
54-
`ServiceResponse<T>`, the generator produces one wrapper per endpoint, duplicating fields (`status`, `message`,
55-
`errors`, or now `data`, `meta`).
52+
OpenAPI Generator, by default, does not handle **generic response types**.
53+
When backend APIs wrap payloads in `ServiceResponse<T>` (e.g., the unified `{ data, meta }` envelope),
54+
the generator produces **duplicated models per endpoint** instead of a single reusable generic base.
5655

57-
This leads to:
58-
59-
* ❌ Dozens of nearly identical classes
60-
* ❌ High maintenance cost
61-
* ❌ Hard-to-evolve contracts
56+
This results in:
57+
* ❌ Dozens of almost-identical response classes
58+
* ❌ Higher maintenance overhead
59+
* ❌ Harder to evolve a single envelope contract across services
6260

6361
---
6462

6563
## 💡 Solution Overview
6664

67-
This repository defines a **complete pattern** for Spring Boot + OpenAPI Generator:
65+
This project provides a **full-stack pattern** to align Spring Boot services and OpenAPI clients:
66+
67+
### Server-Side (Producer)
68+
69+
A `Springdoc` customizer automatically scans controller return types and marks generic wrappers (`ServiceResponse<T>`) using vendor extensions:
70+
71+
```yaml
72+
x-api-wrapper: true
73+
x-api-wrapper-datatype: CustomerDto
74+
x-data-container: Page
75+
x-data-item: CustomerDto
76+
```
6877
69-
* On the **server side**, a `Springdoc` customizer automatically marks generic wrappers (`ServiceResponse<T>`) with
70-
vendor extensions:
78+
### Client-Side (Consumer)
7179
72-
* `x-api-wrapper`
73-
* `x-api-wrapper-datatype`
74-
* `x-data-container` / `x-data-item`
75-
* On the **client side**, Mustache overlays generate **thin wrappers** extending the reusable generic base class
76-
`ServiceClientResponse<T>`.
80+
Mustache overlays redefine OpenAPI templates to generate **thin, type-safe wrappers** extending a reusable base class `ServiceClientResponse<T>`.
7781

78-
Example generated wrapper:
82+
**Example generated output:**
7983

8084
```java
81-
public class ServiceResponseCustomerDto
82-
extends ServiceClientResponse<CustomerDto> {
83-
}
85+
public class ServiceResponseCustomerDto extends ServiceClientResponse<CustomerDto> {}
8486
```
8587

86-
This new structure supports **nested generics** like `ServiceClientResponse<Page<CustomerDto>>` and includes both `data`
87-
and `meta` sections.
88+
This pattern supports **nested generics** like `ServiceClientResponse<Page<CustomerDto>>` and maps all error responses into **ProblemDetail** objects.
8889

8990
---
9091

9192
## ⚙️ New Architecture Highlights
9293

93-
| Layer | Description |
94-
|-----------------------|-------------------------------------------------------------------------------------------------|
95-
| **Server (Producer)** | Publishes `/v3/api-docs.yaml` via Springdoc; marks generic wrappers with vendor extensions |
96-
| **Client (Consumer)** | Uses OpenAPI Generator 7.16.0 with custom Mustache templates to produce generics-aware wrappers |
97-
| **Envelope Model** | Unified `{ data, meta }` response model; compatible with `ProblemDetail` (RFC 7807) for errors |
98-
| **Nested Generics** | Full support for `ServiceResponse<Page<T>>` structures |
94+
<p align="center">
95+
<img src="docs/images/architectural-diagram.png" alt="OpenAPI Generics Architecture" width="900"/>
96+
<br/>
97+
<em>End-to-end generics-aware architecture: from Spring Boot producer to OpenAPI client consumer.</em>
98+
</p>
99+
100+
| Layer | Description |
101+
| --------------------- | ------------------------------------------------------------------------- |
102+
| **Server (Producer)** | Publishes OpenAPI 3.1 spec with auto-registered wrapper schemas |
103+
| **Client (Consumer)** | Uses OpenAPI Generator 7.16.0 + Mustache overlays for generics support |
104+
| **Envelope Model** | Unified `{ data, meta }` response structure |
105+
| **Error Handling** | RFC 7807-compliant `ProblemDetail` decoding into `ClientProblemException` |
106+
| **Nested Generics** | Full support for `ServiceResponse<Page<T>>` |
99107

100108
---
101109

@@ -109,62 +117,102 @@ cd customer-service && mvn spring-boot:run
109117
cd ../customer-service-client && mvn clean install
110118
```
111119

112-
Generated wrappers are located under:
120+
Generated wrappers appear under:
113121

114122
```
115123
target/generated-sources/openapi/src/gen/java
116124
```
117125

118-
Each wrapper extends `ServiceClientResponse<T>` and is aligned with the new `{ data, meta }` envelope.
126+
Each wrapper extends `ServiceClientResponse<T>` and aligns perfectly with the `{ data, meta }` envelope model.
119127

120128
---
121129

122-
## 🧱 Example Response
130+
## 🖼 Generated Client Wrapper — Before & After
131+
132+
Comparison of how OpenAPI Generator outputs looked **before** vs **after** enabling the generics-aware wrapper support.
133+
134+
**Before (duplicated full model):**
135+
136+
<p align="center">
137+
<img src="docs/images/generated-client-wrapper-before.png" alt="Generated client before generics support" width="800"/>
138+
<br/>
139+
<em>Each endpoint generated its own full response model — duplicated <code>data</code> and <code>meta</code> fields across classes.</em>
140+
</p>
141+
142+
**After (thin generic wrapper):**
143+
144+
<p align="center">
145+
<img src="docs/images/generated-client-wrapper-after.png" alt="Generated client after generics support" width="800"/>
146+
<br/>
147+
<em>Now every endpoint extends the reusable <code>ServiceClientResponse&lt;Page&lt;T&gt;&gt;</code> base, eliminating boilerplate and preserving type safety.</em>
148+
</p>
149+
150+
---
151+
152+
## 🧱 Example Responses
153+
154+
The unified envelope applies to both single and paged responses. Below is a paged example:
155+
156+
### Paged Example (`ServiceClientResponse<Page<CustomerDto>>`)
123157

124158
```json
125159
{
126160
"data": {
127-
"customerId": 1,
128-
"name": "Jane Doe",
129-
"email": "jane@example.com"
161+
"content": [
162+
{ "customerId": 1, "name": "Jane Doe", "email": "jane@example.com" },
163+
{ "customerId": 2, "name": "John Smith", "email": "john@example.com" }
164+
],
165+
"page": 0,
166+
"size": 5,
167+
"totalElements": 37,
168+
"totalPages": 8,
169+
"hasNext": true,
170+
"hasPrev": false
130171
},
131172
"meta": {
132173
"serverTime": "2025-01-01T12:34:56Z",
133-
"sort": []
174+
"sort": [
175+
{ "field": "CUSTOMER_ID", "direction": "ASC" }
176+
]
134177
}
135178
}
136179
```
137180

138181
Client usage:
139182

140183
```java
141-
ServiceClientResponse<CustomerDto> response = api.getCustomer(1);
142-
CustomerDto dto = response.getData();
143-
Instant serverTime = response.getMeta().serverTime();
184+
ServiceClientResponse<Page<CustomerDto>> resp =
185+
customerClientAdapter.getCustomers(
186+
"Jane", null, 0, 5, SortField.CUSTOMER_ID, SortDirection.ASC);
187+
188+
Page<CustomerDto> page = resp.getData();
189+
for (CustomerDto c : page.content()) {
190+
// ...
191+
}
144192
```
145193

146194
---
147195

148196
## 🧩 Tech Stack
149197

150-
| Component | Version | Purpose |
151-
|-----------------------|---------|--------------------------------|
152-
| **Java** | 21 | Language baseline |
153-
| **Spring Boot** | 3.4.10 | REST + OpenAPI provider |
154-
| **Springdoc** | 2.8.13 | OpenAPI 3.1 integration |
155-
| **OpenAPI Generator** | 7.16.0 | Generics-aware code generation |
156-
| **HttpClient5** | 5.5 | Production-grade HTTP backend |
198+
| Component | Version | Purpose |
199+
| --------------------- | ------- | ------------------------------------- |
200+
| **Java** | 21 | Language baseline |
201+
| **Spring Boot** | 3.4.10 | REST + OpenAPI provider |
202+
| **Springdoc** | 2.8.13 | OpenAPI 3.1 integration |
203+
| **OpenAPI Generator** | 7.16.0 | Generics-aware code generation |
204+
| **HttpClient5** | 5.5 | Pooled, production-ready HTTP backend |
157205

158206
---
159207

160208
## ✅ Key Features
161209

162-
* **Unified `{ data, meta }` response model**
163-
* **Nested generic support**`ServiceResponse<Page<T>>`
164-
* **RFC 7807-compliant error mapping** via `ClientProblemException`
165-
* **Mustache template overlay** for type-safe wrapper generation
166-
* **End-to-end compatibility** between backend & client
167-
* **No duplicated models or boilerplate**
210+
* 🔹 Unified `{ data, meta }` response model
211+
* 🔹 Nested generics support — `ServiceResponse<Page<T>>`
212+
* 🔹 RFC 7807-compliant error mapping (`ProblemDetail`)
213+
* 🔹 Mustache overlay templates for thin wrapper generation
214+
* 🔹 Seamless compatibility between backend and client
215+
* 🔹 Zero boilerplate — clean, evolvable, and type-safe
168216

169217
---
170218

@@ -180,13 +228,13 @@ public interface CustomerClientAdapter {
180228
}
181229
```
182230

183-
This interface provides a clean boundary for your business logic to consume the generated API in a type-safe manner.
231+
This adapter defines a stable contract that hides generated artifacts and provides type-safe access to your APIs.
184232

185233
---
186234

187235
## 📘 Adoption Guides
188236

189-
See the detailed step-by-step setup under [`docs/adoption`](docs/adoption):
237+
See the detailed integration steps under [`docs/adoption`](docs/adoption):
190238

191239
* [Server-Side Adoption](docs/adoption/server-side-adoption.md)
192240
* [Client-Side Adoption](docs/adoption/client-side-adoption.md)
@@ -196,43 +244,36 @@ See the detailed step-by-step setup under [`docs/adoption`](docs/adoption):
196244
## 🔗 References & Links
197245

198246
* 🌐 [GitHub Pages — Adoption Guides](https://bsayli.github.io/spring-boot-openapi-generics-clients/)
199-
*
200-
📘 [Medium — Type-Safe Generic API Responses](https://medium.com/@baris.sayli/type-safe-generic-api-responses-with-spring-boot-3-4-openapi-generator-and-custom-templates-ccd93405fb04)
201-
*
202-
💬 [Dev.to — Type-Safe OpenAPI Clients Without Boilerplate](https://dev.to/barissayli/spring-boot-openapi-generator-type-safe-generic-api-clients-without-boilerplate-3a8f)
247+
* 📘 [Medium — Type-Safe Generic API Responses](https://medium.com/@baris.sayli/type-safe-generic-api-responses-with-spring-boot-3-4-openapi-generator-and-custom-templates-ccd93405fb04)
248+
* 💬 [Dev.to — Type-Safe OpenAPI Clients Without Boilerplate](https://dev.to/barissayli/spring-boot-openapi-generator-type-safe-generic-api-clients-without-boilerplate-3a8f)
203249

204250
---
205251

206252
## 🛡 License
207253

208-
This repository is licensed under **MIT** (see [LICENSE](LICENSE)). Submodules inherit the license.
209-
210-
---
211-
212-
**Note:** CLI examples should always be provided on a single line.
213-
If parameters include spaces or special characters, wrap them in quotes `"..."`.
254+
Licensed under **MIT** — see [LICENSE](LICENSE).
214255

215256
---
216257

217258
## 💬 Feedback
218259

219-
If you spot any mistakes in this README or have questions about the project, feel free to open an issue or start a
220-
discussion. I’m happy to improve the documentation and clarify concepts further!
260+
If you spot an error or have suggestions, open an issue or join the discussion — contributions are welcome.
261+
💭 [Start a discussion →](https://github.com/bsayli/spring-boot-openapi-generics-clients/discussions)
221262

222263
---
223264

224265
## 🤝 Contributing
225266

226267
Contributions, issues, and feature requests are welcome!
227-
Feel free to [open an issue](../../issues) or submit a PR.
268+
Feel free to [open an issue](https://github.com/bsayli/spring-boot-openapi-generics-clients/issues) or submit a PR.
228269

229270
---
230271

231272
## ⭐ Support
232273

233-
If you found this project useful, please consider giving it a star ⭐ on GitHub — it helps others discover it too!
274+
If you found this project helpful, please give it a ⭐ on GitHub — it helps others discover it.
234275

235276
---
236277

237-
**Barış Saylı**
238-
[GitHub](https://github.com/bsayli) · [Medium](https://medium.com/@baris.sayli)
278+
**Barış Saylı**
279+
[GitHub](https://github.com/bsayli) · [Medium](https://medium.com/@baris.sayli)

0 commit comments

Comments
 (0)