@@ -106,76 +106,176 @@ description: Augment your Java SDK with custom utilities
106106
107107## Adding custom client configuration
108108
109- When you need to intercept and transform client configuration before the SDK client is created, you can extend the generated builder classes. This allows you to add custom code and logic during the client initialization process .
109+ The Java SDK generator now supports builder extensibility through a template method pattern. By extending the generated builder classes and overriding protected methods, you can customize how your SDK client is configured without modifying generated code .
110110
111111Common use cases include:
112112- ** Dynamic URL construction** : Replace placeholders with runtime values (e.g., ` https://api.${DEV_NAMESPACE}.example.com ` )
113113- ** Custom authentication** : Implement complex auth flows beyond basic token authentication
114114- ** Request transformation** : Add custom headers or modify requests globally
115115
116+ ### How it works
117+
118+ Generated builders use protected methods that can be overridden:
119+
120+ ``` java
121+ public class BaseApiClientBuilder {
122+ protected ClientOptions buildClientOptions () {
123+ ClientOptions . Builder builder = ClientOptions . builder();
124+ setEnvironment(builder);
125+ setAuthentication(builder); // Only if API has auth
126+ setCustomHeaders(builder); // Only if API defines headers
127+ setVariables(builder); // Only if API has variables
128+ setHttpClient(builder);
129+ setTimeouts(builder);
130+ setRetries(builder);
131+ setAdditional(builder);
132+ return builder. build();
133+ }
134+ }
135+ ```
136+
116137<Steps >
117138
118- ### Create a custom client that extends the base client
139+ ### Create a custom client class
119140
120- This client will serve as the entry point for your customized SDK.
141+ Extend the generated base client:
121142
122143``` java title="src/main/java/com/example/MyClient.java"
123144package com.example ;
124145
125- import com.example.client .BaseClient ;
126- import com.example.client .ClientOptions ;
146+ import com.example.api .BaseClient ;
147+ import com.example.api.core .ClientOptions ;
127148
128149public class MyClient extends BaseClient {
129150 public MyClient (ClientOptions clientOptions ) {
130151 super (clientOptions);
131152 }
132-
153+
133154 public static MyClientBuilder builder () {
134155 return new MyClientBuilder ();
135156 }
136157}
137158```
138159
139- ### Create a custom builder with your transformation logic
160+ ### Create a custom builder
140161
141- Override the ` buildClientOptions() ` method to intercept and modify the configuration before the client is created.
162+ Override methods to customize behavior:
142163
143- ``` java title="src/main/java/com/example/MyClientBuilder.java" {10}
164+ ``` java title="src/main/java/com/example/MyClientBuilder.java"
144165package com.example ;
145166
146- import com.example.client .BaseClient.BaseClientBuilder ;
147- import com.example.client .ClientOptions ;
148- import com.example.environment .Environment ;
167+ import com.example.api .BaseClient.BaseClientBuilder ;
168+ import com.example.api.core .ClientOptions ;
169+ import com.example.api.core .Environment ;
149170
150171public class MyClientBuilder extends BaseClientBuilder {
172+
151173 @Override
152- protected ClientOptions buildClientOptions () {
153- ClientOptions base = super . buildClientOptions();
154-
155- // Transform configuration as needed
156- String transformedUrl = transformEnvironmentVariables(
157- base. environment(). getUrl()
158- );
159-
160- return ClientOptions . builder()
161- .from(base)
162- .environment(Environment . custom(transformedUrl))
163- .build();
174+ protected void setEnvironment (ClientOptions .Builder builder ) {
175+ String url = this . environment. getUrl();
176+ String expandedUrl = expandEnvironmentVariables(url);
177+ builder. environment(Environment . custom(expandedUrl));
178+ }
179+
180+ @Override
181+ protected void setAdditional (ClientOptions .Builder builder ) {
182+ builder. addHeader(" X-Request-ID" , () - > UUID . randomUUID(). toString());
183+ }
184+
185+ @Override
186+ public MyClient build () {
187+ return new MyClient (buildClientOptions());
164188 }
165189}
166190```
167191
168192### Update ` .fernignore `
169193
170- Add the ` MyClient.java ` and ` MyClientBuilder.java ` to ` .fernignore ` .
171-
172194``` diff title=".fernignore"
173195+ src/main/java/com/example/MyClient.java
174196+ src/main/java/com/example/MyClientBuilder.java
175197```
176198
177199</Steps >
178200
201+ ### Method reference
202+
203+ Each method serves a specific purpose and is only generated when needed:
204+
205+ | Method | Purpose | Available When |
206+ | --------| ---------| ----------------|
207+ | ` setEnvironment(builder) ` | Customize environment/URL configuration | Always |
208+ | ` setAuthentication(builder) ` | Modify or add authentication | Only if API has auth |
209+ | ` setCustomHeaders(builder) ` | Add custom headers defined in API spec | Only if API defines headers |
210+ | ` setVariables(builder) ` | Configure API variables | Only if API has variables |
211+ | ` setHttpClient(builder) ` | Customize OkHttp client | Always |
212+ | ` setTimeouts(builder) ` | Modify timeout settings | Always |
213+ | ` setRetries(builder) ` | Modify retry settings | Always |
214+ | ` setAdditional(builder) ` | Final extension point for any custom configuration | Always |
215+ | ` validateConfiguration() ` | Add custom validation logic | Always |
216+ | ` buildClientOptions() ` | Orchestrates all configuration methods (rarely need to override) | Always |
217+
218+ ### Common patterns
219+
220+ The ` setAdditional() ` method is your extension point for adding configuration that doesn't fit into other methods:
221+
222+ <Accordion title = " Multi-tenant URLs" >
223+ ``` java
224+ @Override
225+ protected void setEnvironment(ClientOptions . Builder builder) {
226+ String baseUrl = this . environment. getUrl();
227+ String tenantUrl = baseUrl. replace(" /api/" , " /api/tenants/" + tenantId + " /" );
228+ builder. environment(Environment . custom(tenantUrl));
229+ }
230+ ```
231+ </Accordion >
232+
233+ <Accordion title = " Dynamic authentication" >
234+ ``` java
235+ @Override
236+ protected void setAuthentication(ClientOptions . Builder builder) {
237+ builder. addHeader(" Authorization" , () - >
238+ " Bearer " + tokenProvider. getAccessToken()
239+ );
240+ }
241+ ```
242+ </Accordion >
243+
244+ <Accordion title = " Request tracking and monitoring" >
245+ ``` java
246+ @Override
247+ protected void setAdditional(ClientOptions . Builder builder) {
248+ // Add request tracking
249+ builder. addHeader(" X-Request-ID" , () - > UUID . randomUUID(). toString());
250+ builder. addHeader(" X-Client-Version" , " 1.0.0" );
251+
252+ // Add feature flags
253+ if (FeatureFlags . isEnabled(" new-algorithm" )) {
254+ builder. addHeader(" X-Feature-Flag" , " new-algorithm" );
255+ }
256+ }
257+ ```
258+ </Accordion >
259+
260+ <Accordion title = " Advanced HTTP client configuration" >
261+ ``` java
262+ @Override
263+ protected void setHttpClient(ClientOptions . Builder builder) {
264+ OkHttpClient customClient = new OkHttpClient .Builder ()
265+ .connectTimeout(30 , TimeUnit . SECONDS )
266+ .readTimeout(60 , TimeUnit . SECONDS )
267+ .addInterceptor(new RetryInterceptor ())
268+ .connectionPool(new ConnectionPool (50 , 5 , TimeUnit . MINUTES ))
269+ .build();
270+ builder. httpClient(customClient);
271+ }
272+ ```
273+ </Accordion >
274+
275+ ### Requirements
276+
277+ - ** Fern Java SDK version** : 2.39.1 or later
278+
179279## Adding custom dependencies
180280
181281<Markdown src = " /snippets/pro-callout.mdx" />
0 commit comments