11# spring-boot-openapi-generics-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 )
4- [ ![ 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 )
4+ [ ![ 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 )
55[ ![ Java] ( https://img.shields.io/badge/Java-21-red?logo=openjdk )] ( https://openjdk.org/projects/jdk/21/ )
66[ ![ Spring Boot] ( https://img.shields.io/badge/Spring%20Boot-3.4.9-green?logo=springboot )] ( https://spring.io/projects/spring-boot )
77[ ![ OpenAPI Generator] ( https://img.shields.io/badge/OpenAPI%20Generator-7.x-blue?logo=openapiinitiative )] ( https://openapi-generator.tech/ )
1414</p >
1515
1616** Type-safe client generation with Spring Boot & OpenAPI using generics.**
17- This repository demonstrates how to teach OpenAPI Generator to work with generics in order to avoid boilerplate, reduce
17+ This repository demonstrates how to extend OpenAPI Generator to work with generics in order to avoid boilerplate, reduce
1818duplicated wrappers, and keep client code clean.
1919
2020---
@@ -43,9 +43,40 @@ This project shows how to:
4343
4444---
4545
46+ ### How it works (under the hood)
47+
48+ At generation time, the reference service ** auto-registers** wrapper schemas in the OpenAPI doc:
49+
50+ * A Spring ` OpenApiCustomizer ` scans controller return types and unwraps ` ResponseEntity ` , ` CompletionStage ` , Reactor (
51+ ` Mono ` /` Flux ` ), etc. until it reaches ` ServiceResponse<T> ` .
52+ * For every discovered ` T ` , it adds a ` ServiceResponse{T} ` schema that composes the base envelope + the concrete ` data `
53+ type, and marks it with vendor extensions:
54+
55+ * ` x-api-wrapper: true `
56+ * ` x-api-wrapper-datatype: <T> `
57+
58+ The Java client then uses a tiny Mustache override to render ** thin shells** for those marked schemas:
59+
60+ ``` mustache
61+ // api_wrapper.mustache
62+ public class {{classname}}
63+ extends io.github.bsayli.openapi.client.common.ServiceClientResponse<{{vendorExtensions.x-api-wrapper-datatype}}> {
64+ }
65+ ```
66+
67+ This is what turns e.g. ` ServiceResponseCustomerCreateResponse ` into:
68+
69+ ``` java
70+ public class ServiceResponseCustomerCreateResponse
71+ extends ServiceClientResponse<CustomerCreateResponse > {
72+ }
73+ ```
74+
75+ ---
76+
4677## ⚡ Quick Start
4778
48- Run the sample service:
79+ Run the reference service:
4980
5081``` bash
5182cd customer-service
@@ -66,11 +97,11 @@ ServiceClientResponse<CustomerCreateResponse> response =
6697 customerControllerApi. createCustomer(request);
6798```
6899
69- ### 🖼 Demo Swagger Screenshot
100+ ### 🖼 Swagger Screenshot
70101
71102Here’s what the ` create customer ` endpoint looks like in Swagger UI after running the service:
72103
73- ![ Customer create demo ] ( docs/images/swagger-customer-create.png )
104+ ![ Customer create example ] ( docs/images/swagger-customer-create.png )
74105
75106### 🖼 Generated Client Wrapper
76107
@@ -83,6 +114,18 @@ Comparison of how OpenAPI Generator outputs looked **before** vs **after** addin
83114** After (thin generic wrapper):**
84115
85116![ Generated client (after)] ( docs/images/generated-client-wrapper-after.png )
117+
118+ ---
119+
120+ ## ✅ Verify in 60 Seconds
121+
122+ 1 . Clone this repo
123+ 2 . Run ` mvn clean install -q `
124+ 3 . Open ` customer-service-client/target/generated-sources/... `
125+ 4 . See the generated wrappers → they now extend a ** generic base class** instead of duplicating fields.
126+
127+ You don’t need to write a single line of code — the generator does the work.
128+
86129---
87130
88131## 🛠 Tech Stack & Features
@@ -120,23 +163,24 @@ spring-boot-openapi-generics-clients/
120163
121164### ✨ Usage Example: Adapter Interface
122165
123- Sometimes you don’t want to expose all the thin wrappers directly.
166+ Sometimes you don’t want to expose all the thin wrappers directly.
124167A simple adapter interface can consolidate them into clean, type-safe methods:
125168
126169``` java
127170public interface CustomerClientAdapter {
128- ServiceClientResponse<CustomerCreateResponse > createCustomer (CustomerCreateRequest request );
171+ ServiceClientResponse<CustomerCreateResponse > createCustomer (CustomerCreateRequest request );
129172
130- ServiceClientResponse<CustomerDto > getCustomer (Integer customerId );
173+ ServiceClientResponse<CustomerDto > getCustomer (Integer customerId );
131174
132- ServiceClientResponse<CustomerListResponse > getCustomers ();
175+ ServiceClientResponse<CustomerListResponse > getCustomers ();
133176
134- ServiceClientResponse<CustomerUpdateResponse > updateCustomer (
135- Integer customerId , CustomerUpdateRequest request );
177+ ServiceClientResponse<CustomerUpdateResponse > updateCustomer (
178+ Integer customerId , CustomerUpdateRequest request );
136179
137- ServiceClientResponse<CustomerDeleteResponse > deleteCustomer (Integer customerId );
180+ ServiceClientResponse<CustomerDeleteResponse > deleteCustomer (Integer customerId );
138181}
139182```
183+
140184---
141185
142186## 🔍 Why This Matters
@@ -163,7 +207,7 @@ This pattern is useful when:
163207
164208## 🔧 How to Run
165209
166- 1 . ** Start the sample service**
210+ 1 . ** Start the reference service**
167211
168212 ``` bash
169213 cd customer-service
@@ -186,6 +230,24 @@ This pattern is useful when:
186230
187231---
188232
233+ ## 👤 Who Should Use This?
234+
235+ * Backend developers maintaining multiple microservices
236+ * API platform teams standardizing response envelopes
237+ * Teams already invested in OpenAPI Generator looking to reduce boilerplate
238+
239+ ---
240+
241+ ## ⚠️ Why Not Use It?
242+
243+ This project may not be the right fit if:
244+
245+ * Your APIs do ** not** use a common response wrapper
246+ * You are fine with duplicated wrapper models
247+ * You don’t generate client code from OpenAPI specs
248+
249+ ---
250+
189251## 📖 Related Article
190252
191253This repository is based on my article:
0 commit comments