Adding custom code

This page covers how to add custom logic, methods, and dependencies to your TypeScript SDK.

Adding custom logic

To get started adding custom code:

1

Create a new file and add your custom logic

src/main/java/<package>/Helper.java
1package com.example.helper;
2
3public class Helper {
4
5public static void myHelper() {
6 System.out.println("Hello World!");
7}
8
9}
2

Add your file to .fernignore

.fernignore
1# Specify files that shouldn't be modified by Fern
2
3src/main/java/<package>/Helper.java
3

Consume the helper

Now your users can consume the helper function by importing it from the SDK.

1import com.example.helper.Helper;
2
3public class Main {
4
5public static void main(String[] args) {
6 Helper.myHelper();
7}
8
9}

Adding custom SDK methods

Fern also allows you to add custom methods to the SDK itself (e.g. client.my_method() ) by inheriting the Fern generated client and then extending it.

1

Update generators.yml configuration

Name your Fern-generated client something like BaseClient to reflect that this client will be extended.

generators.yml
1- name: fernapi/fern-java-sdk
2 version: "..."
3 config:
4 client-class-name: BaseClient
2

Import and extend the generated client

First, import the Fern generated base client and extend it. Then, add whatever methods you want.

src/main/java/com/example/MyClient.java
1package com.example;
2
3import com.example.client.BaseClient;
4
5public class MyClient extends BaseClient { // extend the Fern generated client
6
7 public void myHelper() {
8 System.out.println("Hello World!");
9 }
10
11}
3

Update .fernignore

Add the MyClient.java to .fernignore.

.fernignore
1+ src/main/java/com/example/MyClient.java
4

Consume the method

Now your users can consume the helper function by importing it from the SDK.

1client.myHelper();

Adding custom client configuration

The Java SDK generator supports builder extensibility through an opt-in self-type pattern. When enabled via the enable-extensible-builders flag, generated builders can be extended while maintaining type safety during method chaining.

Common use cases include:

  • Dynamic URL construction: Replace placeholders with runtime values (e.g., https://api.${TENANT}.example.com)
  • Custom authentication: Implement complex auth flows beyond basic token authentication
  • Request transformation: Add custom headers or modify requests globally
  • Multi-tenant support: Add tenant-specific configuration and headers
1

Enable extensible builders

Add the flag to your generators.yml:

generators.yml
1groups:
2 local:
3 generators:
4 - name: fernapi/fern-java-sdk
5 version: 2.39.6
6 config:
7 enable-extensible-builders: true
2

How it works

Generated builders use the self-type pattern for type-safe method chaining:

1public abstract class BaseClientBuilder<T extends BaseClientBuilder<T>> {
2 protected abstract T self();
3
4 public T token(String token) {
5 return self(); // Returns your custom type, not BaseClientBuilder
6 }
7}
3

Create a custom builder

Extend the generated builder:

src/main/java/com/example/CustomApiBuilder.java
1public class CustomApiBuilder extends BaseClientBuilder<CustomApiBuilder> {
2 @Override
3 protected CustomApiBuilder self() {
4 return this;
5 }
6
7 @Override
8 protected void setEnvironment(ClientOptions.Builder builder) {
9 // Customize environment URL
10 String url = this.environment.getUrl();
11 String expandedUrl = expandEnvironmentVariables(url);
12 builder.environment(Environment.custom(expandedUrl));
13 }
14
15 @Override
16 protected void setAdditional(ClientOptions.Builder builder) {
17 // Add custom headers
18 builder.addHeader("X-Request-ID", () -> UUID.randomUUID().toString());
19 }
20}
4

Use your custom builder

1BaseClient client = new CustomApiBuilder()
2 .token("my-token") // returns CustomApiBuilder
3 .tenantId("tenant-123") // returns CustomApiBuilder
4 .timeout(30) // returns CustomApiBuilder
5 .build();
6
7client.users().list();
5

Update .fernignore

Add your custom builder to .fernignore so Fern won’t overwrite it:

.fernignore
1+ src/main/java/com/example/CustomApiBuilder.java

Default implementation

If you don’t need to extend the builder, use the provided Impl class:

1BaseClient client = BaseClientBuilder.Impl()
2 .token("my-token")
3 .timeout(30)
4 .build();

Method reference

Each method serves a specific purpose and is only generated when needed:

MethodPurposeAvailable When
self()Returns the concrete builder type for chainingAlways (abstract)
setEnvironment(builder)Customize environment/URL configurationAlways
setAuthentication(builder)Modify or add authenticationOnly if API has auth
setCustomHeaders(builder)Add custom headers defined in API specOnly if API defines headers
setVariables(builder)Configure API variablesOnly if API has variables
setHttpClient(builder)Customize OkHttp clientAlways
setTimeouts(builder)Modify timeout settingsAlways
setRetries(builder)Modify retry settingsAlways
setAdditional(builder)Final extension point for any custom configurationAlways
validateConfiguration()Add custom validation logicAlways

Common patterns

1@Override
2protected void setEnvironment(ClientOptions.Builder builder) {
3 String url = this.environment.getUrl()
4 .replace("/api/", "/tenants/" + tenantId + "/");
5 builder.environment(Environment.custom(url));
6}
1@Override
2protected void setAuthentication(ClientOptions.Builder builder) {
3 super.setAuthentication(builder); // Keep existing auth
4 builder.addHeader("Authorization", () ->
5 "Bearer " + tokenProvider.getAccessToken()
6 );
7}
1@Override
2protected void setEnvironment(ClientOptions.Builder builder) {
3 String url = this.environment.getUrl();
4 // Replace ${VAR_NAME} with environment variables
5 Pattern pattern = Pattern.compile("\\$\\{([^}]+)\\}");
6 Matcher matcher = pattern.matcher(url);
7 StringBuffer result = new StringBuffer();
8
9 while (matcher.find()) {
10 String envVar = System.getenv(matcher.group(1));
11 matcher.appendReplacement(result,
12 envVar != null ? envVar : matcher.group(0));
13 }
14 matcher.appendTail(result);
15
16 builder.environment(Environment.custom(result.toString()));
17}
1@Override
2protected void setAdditional(ClientOptions.Builder builder) {
3 builder.addHeader("X-Request-ID", () -> UUID.randomUUID().toString());
4 builder.addHeader("X-Tenant-ID", this.tenantId);
5
6 if (FeatureFlags.isEnabled("new-feature")) {
7 builder.addHeader("X-Feature-Flag", "new-feature");
8 }
9}

Requirements

  • Fern Java SDK version: 2.39.6 or later
  • Configuration: enable-extensible-builders: true in generators.yml

Adding custom dependencies

Pro Feature

This feature is only available on paid plans. Please schedule a demo or email us to get started.

To add packages that your custom code requires, update your generators.yml.

generators.yml
1- name: fernapi/fern-java-sdk
2 version: "..."
3 config:
4 custom-dependencies:
5 - org.apache.commons:commons-lang3:3.12.0
6 - org.slf4j:slf4j-api:2.0.7