# Adding custom code

> Augment your Java SDK with custom utilities

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

<Info>
  Before getting started, 

  [read about how Fern SDKs use custom code and learn about the `.fernignore` file](/sdks/overview/custom-code)

  .
</Info>

## Adding custom logic

To get started adding custom code:

<Steps>
  ### Create a new file and add your custom logic

  ```java title="src/main/java/<package>/Helper.java"
  package com.example.helper;

  public class Helper {

  public static void myHelper() {
      System.out.println("Hello World!");
  }

  }
  ```

  ### Add your file to `.fernignore`

  ```yaml {3} title=".fernignore"
  # Specify files that shouldn't be modified by Fern

  src/main/java/<package>/Helper.java
  ```

  ### Consume the helper

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

  ```java
  import com.example.helper.Helper;

  public class Main {

  public static void main(String[] args) {
      Helper.myHelper();
  }

  }
  ```
</Steps>

## 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.

<Steps>
  ### Update `generators.yml` configuration

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

  ```yml {4} title="generators.yml"
  - name: fernapi/fern-java-sdk
      version: "..."
      config:
        client-class-name: BaseClient
  ```

  ### Import and extend the generated client

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

  ```java title="src/main/java/com/example/MyClient.java"
  package com.example;

  import com.example.client.BaseClient;

  public class MyClient extends BaseClient { // extend the Fern generated client

    public void myHelper() {
      System.out.println("Hello World!");
    }

  }
  ```

  ### Update `.fernignore`

  Add the `MyClient.java` to `.fernignore`.

  ```diff title=".fernignore"
  + src/main/java/com/example/MyClient.java
  ```

  ### Consume the method

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

  ```java
  client.myHelper();
  ```
</Steps>

## 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

<Steps>
  ### Enable extensible builders

  Add the flag to your `generators.yml`:

  ```yaml {7} title="generators.yml"
  groups:
    local:
      generators:
        - name: fernapi/fern-java-sdk
          version: 2.39.6
          config:
            enable-extensible-builders: true
  ```

  ### How it works

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

  ```java
  public abstract class BaseClientBuilder<T extends BaseClientBuilder<T>> {
      protected abstract T self();
      
      public T token(String token) {
          return self();  // Returns your custom type, not BaseClientBuilder
      }
  }
  ```

  ### Create a custom builder

  Extend the generated builder:

  ```java title="src/main/java/com/example/CustomApiBuilder.java"
  public class CustomApiBuilder extends BaseClientBuilder<CustomApiBuilder> {
      @Override
      protected CustomApiBuilder self() {
          return this;
      }

      @Override
      protected void setEnvironment(ClientOptions.Builder builder) {
          // Customize environment URL
          String url = this.environment.getUrl();
          String expandedUrl = expandEnvironmentVariables(url);
          builder.environment(Environment.custom(expandedUrl));
      }

      @Override
      protected void setAdditional(ClientOptions.Builder builder) {
          // Add custom headers
          builder.addHeader("X-Request-ID", () -> UUID.randomUUID().toString());
      }
  }
  ```

  ### Use your custom builder

  ```java
  BaseClient client = new CustomApiBuilder()
      .token("my-token")        // returns CustomApiBuilder
      .tenantId("tenant-123")   // returns CustomApiBuilder  
      .timeout(30)              // returns CustomApiBuilder
      .build();

  client.users().list();
  ```

  ### Update `.fernignore`

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

  ```diff title=".fernignore"
  + src/main/java/com/example/CustomApiBuilder.java
  ```
</Steps>

### Default implementation

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

```java
BaseClient client = BaseClientBuilder.Impl()
    .token("my-token")
    .timeout(30)
    .build();
```

### Method reference

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

| Method                       | Purpose                                            | Available When              |
| ---------------------------- | -------------------------------------------------- | --------------------------- |
| `self()`                     | Returns the concrete builder type for chaining     | Always (abstract)           |
| `setEnvironment(builder)`    | Customize environment/URL configuration            | Always                      |
| `setAuthentication(builder)` | Modify or add authentication                       | Only if API has auth        |
| `setCustomHeaders(builder)`  | Add custom headers defined in API spec             | Only if API defines headers |
| `setVariables(builder)`      | Configure API variables                            | Only if API has variables   |
| `setHttpClient(builder)`     | Customize OkHttp client                            | Always                      |
| `setTimeouts(builder)`       | Modify timeout settings                            | Always                      |
| `setRetries(builder)`        | Modify retry settings                              | Always                      |
| `setAdditional(builder)`     | Final extension point for any custom configuration | Always                      |
| `validateConfiguration()`    | Add custom validation logic                        | Always                      |

### Common patterns

<Accordion title="Multi-tenant URLs">
  ```java
  @Override
  protected void setEnvironment(ClientOptions.Builder builder) {
      String url = this.environment.getUrl()
          .replace("/api/", "/tenants/" + tenantId + "/");
      builder.environment(Environment.custom(url));
  }
  ```
</Accordion>

<Accordion title="Dynamic authentication">
  ```java
  @Override
  protected void setAuthentication(ClientOptions.Builder builder) {
      super.setAuthentication(builder); // Keep existing auth
      builder.addHeader("Authorization", () ->
          "Bearer " + tokenProvider.getAccessToken()
      );
  }
  ```
</Accordion>

<Accordion title="Environment variable expansion">
  ```java
  @Override
  protected void setEnvironment(ClientOptions.Builder builder) {
      String url = this.environment.getUrl();
      // Replace ${VAR_NAME} with environment variables
      Pattern pattern = Pattern.compile("\\$\\{([^}]+)\\}");
      Matcher matcher = pattern.matcher(url);
      StringBuffer result = new StringBuffer();
      
      while (matcher.find()) {
          String envVar = System.getenv(matcher.group(1));
          matcher.appendReplacement(result, 
              envVar != null ? envVar : matcher.group(0));
      }
      matcher.appendTail(result);
      
      builder.environment(Environment.custom(result.toString()));
  }
  ```
</Accordion>

<Accordion title="Request tracking">
  ```java
  @Override
  protected void setAdditional(ClientOptions.Builder builder) {
      builder.addHeader("X-Request-ID", () -> UUID.randomUUID().toString());
      builder.addHeader("X-Tenant-ID", this.tenantId);
      
      if (FeatureFlags.isEnabled("new-feature")) {
          builder.addHeader("X-Feature-Flag", "new-feature");
      }
  }
  ```
</Accordion>

### Requirements

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

## Adding custom dependencies

<Warning title="Pro and Enterprise feature">
  This feature is available only for the [Pro and Enterprise plans](https://buildwithfern.com/pricing). To get started, reach out to [support@buildwithfern.com](mailto:support@buildwithfern.com).
</Warning>

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

```yaml {4-7} title="generators.yml"
- name: fernapi/fern-java-sdk
  version: "..."
  config:
    custom-dependencies:
      - org.apache.commons:commons-lang3:3.12.0
      - org.slf4j:slf4j-api:2.0.7

```