---
title: Initialize the Java SDK
slug: feature-experimentation/docs/initialize-sdk-java
docTags: 
createdAt: 2026-08-05T20:39:56.000Z
---

Use the Optimizely Builder to initialize the Java SDK and instantiate an instance of the Optimizely client class that exposes API methods like the [Decide methods](docId:6ZSh7uRg7JxlrHwft2Ogf).

## Version

v4.0.0 and higher

## Description

The SDK provides a default implementation, but you may want to override the optional parameters for your production environments.

For example, you can override the defaults to

- configure an [error handler](docId\:jPr2jKUhTSqT1t0AP5_g_) and [logger](docId:3QpHnFTDVRySxOqR7NiaT) to catch issues.
- manage network calls through a custom [event dispatcher](docId\:BSfueGrncnMBnGu5LoIW-).
- ensure sticky bucketing using your own [User Profile Service](docId\:spdIYrRY-ons6xj4aAzPq).

## Parameters

The following table lists the optional builder methods:

| Parameter                                        | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| \`withConfigManager(ProjectConfigManager)\`      | \`ProjectConfigManager\` object to manage project configuration. You should provide an instance of \`HttpProjectConfigManager\` or your own custom object. If this is not set, the parameter uses static \`ProjectConfig\` without polling datafile periodically.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| \`withEventProcessor(EventProcessor)\`           | \`EventProcessor\` object to process events. You should provide an instance of \`BatchEventProcessor\` with \`AsyncEventHandler\` or your own custom object. If this is not set, the parameter uses \`ForwardingEventProcessor\` with \`NoopEventHandler()\` by default.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| \`\*withUserProfileService(UserProfileService)\` | \`UserProfileService\` object to support persistent variation assignments. If this is not set, persistent decisions are not supported.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| \`withErrorHandler(ErrorHandler)\`               | \`ErrorHandler\` object to handle errors.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| \`withDatafile(String)\`                         | The JSON string representing the project. If an instance of \`ProjectConfigManager\` is not provided. You must use this datafile to initialize the \`ProjectConfig\` statically.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| \`withDefaultDecideOptions(List)\`               | An array of \`OptimizelyDecideOption\` enums. When the Optimizely client is constructed with this parameter, it sets default decide options which are applied to the Decide calls made during the lifetime of the Optimizely client. Additionally, you can pass options to individual Decide methods (does not override the defaults). For example code, see \[OptimizelyDecideOption]\(https\://docs.developers.optimizely.com/feature-experimentation/docs/decide-methods-java#optimizelydecideoption).                                                                                                                                                                                                                                                                                                  |
| \`withODPManager(ODPManager)\`                   | \`ODPManager\` contains the logic supporting Real-Time Audiences for Feature Experimentation-related features, including audience segments. You must configure this parameter to use Real-Time Audiences for Feature Experimentation. The Java SDK enables the Real-Time Audiences for Feature Experimentation methods by default. But, the methods do nothing unless you have enabled and configured the \[Real-Time Audiences for Feature Experimentation]\(https\://docs.developers.optimizely.com/feature-experimentation/docs/real-time-audiences-for-feature-experimentation) integration. To optionally disable Real-Time Audience for Feature Experimentation completely, see \[ODPManager]\(https\://docs.developers.optimizely.com/feature-experimentation/docs/initialize-sdk-java#odpmanager). |

## Returns

An instantiated instance of the Optimizely class.

## Example

The following example shows how to initialize the Optimizely Feature Experimentation Java SDK using the builder options.

Optimizely provides default implementations of `HttpProjectConfigManager`, `BatchEventProcessor`, and `AsyncEventHandler`.

```java
ProjectConfigManager configManager = HttpProjectConfigManager.builder()
    .withSdkKey(sdkKey)
    .withDatafile(datafile)
    .build();

EventHandler eventHandler = AsyncEventHandler.builder()
    .withQueueCapacity(20000)
    .withNumWorkers(1)
    .build();

EventProcessor batchProcessor = BatchEventProcessor.builder()
    .withBatchSize(50)
    .withEventHandler(eventHandler)
    .withFlushInterval(TimeUnit.MINUTES.toMillis(1))
    .build();

Optimizely optimizely = Optimizely.builder()
    .withConfigManager(configManager)
    .withEventProcessor(batchProcessor)
    .build();
```

## Exceptions

`build()` does not throw if the datafile is malformed or has an incorrect schema. The Java SDK logs the parse failure and passes an `OptimizelyRuntimeException` (wrapping the `ConfigParseException`) to the configured error handler.

## `HttpProjectConfigManager`

Whenever the experiment configuration changes, the SDK uses automatic datafile management (ADM) to handle the change for you.

[`HttpProjectConfigManager`](https://github.com/optimizely/java-sdk/blob/master/core-httpclient-impl/src/main/java/com/optimizely/ab/config/HttpProjectConfigManager.java) is an implementation of the abstract [`PollingProjectConfigManager`](https://github.com/optimizely/java-sdk/blob/master/core-api/src/main/java/com/optimizely/ab/config/PollingProjectConfigManager.java). The `poll` method is extended and makes an HTTP `GET` request to the configured URL to asynchronously download the project datafile and initialize an instance of the `ProjectConfig`.

By default, `HttpProjectConfigManager` blocks until the first successful datafile retrieval, up to a configurable timeout.

Set the frequency of the polling method and the blocking timeout with `withPollingInterval()` and `withBlockingTimeout()`, pulling the default values from global properties.

The following example shows how to initialize the `HttpProjectConfigManager` with builder options.

```java
ProjectConfigManager projectConfigManager = HttpProjectConfigManager.builder()
    .withSdkKey(sdkKey)
    .withDatafile(datafile)
    .withPollingInterval(1L, TimeUnit.MINUTES)
    .withBlockingTimeout(10L, TimeUnit.SECONDS)
    .build();
```

### SDK key

The SDK key composes the outbound HTTP request to the default datafile location on the Optimizely CDN.

### Poll interval

The polling interval specifies a fixed delay between consecutive HTTP requests for the datafile.

### Initial datafile

You can provide an initial datafile through the builder to bootstrap the `ProjectConfigManager` to use it immediately without blocking execution. The initial datafile is also a fallback datafile if an HTTP connection cannot be established. This is useful in mobile environments where internet connectivity is not guaranteed.

The preceding datafile is discarded after the first successful datafile poll.

### Builder methods

You can use the following builder methods to customize the `HttpProjectConfigManager` configuration.

| Method                                | Default value                                                                                | Description                                                                |
| ------------------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `withDatafile(String)`                | null                                                                                         | Initial datafile, typically sourced from a local cached source.            |
| `withUrl(String)`                     | null                                                                                         | URL override location used to specify custom HTTP source for the datafile. |
| `withFormat(String)`                  | [https://cdn.optimizely.com/datafiles/%s.json](https://cdn.optimizely.com/datafiles/%s.json) | Parameterized datafile URL by SDK key.                                     |
| `withPollingInterval(Long, TimeUnit)` | 5 minutes                                                                                    | Fixed delay between fetches for the datafile.                              |
| `withBlockingTimeout(Long, TimeUnit)` | 10 seconds                                                                                   | Maximum time to wait for initial bootstrapping.                            |
| `withSdkKey(String)`                  | null                                                                                         | Optimizely project SDK key, required.                                      |

### Advanced configuration

The following properties can be set to override the default configuration for `HttpProjectConfigManager`.

| Property                                        | Default value | Description                                      |
| ----------------------------------------------- | ------------- | ------------------------------------------------ |
| `http.project.config.manager.polling.duration`  | 5             | The fixed delay between fetches for the datafile |
| `http.project.config.manager.polling.unit`      | MINUTES       | Time unit corresponding to the polling interval  |
| `http.project.config.manager.blocking.duration` | 10            | Maximum time to wait for initial bootstrapping   |
| `http.project.config.manager.blocking.unit`     | SECONDS       | Time unit corresponding to blocking duration     |
| `http.project.config.manager.sdk.key`           | null          | Optimizely project SDK key                       |

### Update config notifications

The Java SDK triggers a notification signal after fetching a datafile. To subscribe to these notifications, use the `Optimizely.addUpdateConfigNotificationHandler`.

```java
NotificationHandler<UpdateConfigNotification> handler = message ->
    System.out.println("Received new datafile configuration");
optimizely.addUpdateConfigNotificationHandler(handler);
```

Alternatively, you can add the handler directly to the `NotificationCenter`.

```java
notificationCenter.addNotificationHandler(UpdateConfigNotification.class, handler);
```

## `AsyncEventHandler`

[`AsyncEventHandler`](https://github.com/optimizely/java-sdk/blob/master/core-httpclient-impl/src/main/java/com/optimizely/ab/event/AsyncEventHandler.java) provides an implementation of [`EventHandler`](https://github.com/optimizely/java-sdk/blob/master/core-api/src/main/java/com/optimizely/ab/event/EventHandler.java) backed by a `ThreadPoolExecutor`. The event handler queues the triggered events from the SDK immediately as discrete tasks to the executor and processes them in submission order.

Each worker must make outbound HTTP requests to the Optimizely log endpoint for metrics tracking. Configure the default queue size and number of workers through global properties. Use `AsyncEventHandler.Builder` to override the default queue size and number of workers.

:::hint{type="info"}
When using the Optimizely builder class, you must provide an implementation of the event handler as shown in the following example. Otherwise, the Optimizely instance will default to a no-op event handler.
:::

To use `AsyncEventHandler`, you must build an instance with `AsyncEventHandler.Builder` and pass the instance to the `Optimizely.Builder`.

You can also initialize the SDK with [the OptimizelyFactory methods](docId\:WlItbLTrjYBNvKSjoGIFY) if you want to use the default `AsyncEventHandler` implementation.

```java
EventHandler eventHandler = AsyncEventHandler.builder()
    .withQueueCapacity(10000)
    .withNumWorkers(5)
    .build();
```

### Queue capacity

You can set the queue capacity to initialize the backing queue for the executor service. This drops an event if the queue fills up and logs an exception. Setting a higher queue value prevents event loss but uses more memory if the workers cannot keep up with the production rate.

### Number of workers

The number of workers determines the number of threads the thread pool uses.

### Builder methods

You can use the following builder methods to customize the `AsyncEventHandler` configuration.

| Method                             | Default value | Description                                                                                          |
| ---------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------- |
| `withQueueCapacity(int)`           | 10000         | Queue size for pending logEvents.                                                                    |
| `withNumWorkers(int)`              | 2             | Number of worker threads.                                                                            |
| `withMaxTotalConnections(int)`     | 200           | Maximum number of connections.                                                                       |
| `withMaxPerRoute(int)`             | 20            | Maximum number of connections per route.                                                             |
| `withValidateAfterInactivity(int)` | 1000          | Period of inactivity (in milliseconds) after which a pooled connection is re-validated before reuse. |

### Advanced configuration

You can set the following properties to override the default configuration for `AsyncEventHandler`.

| Property                                  | Default value | Description                                                                                          |
| ----------------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------- |
| `async.event.handler.queue.capacity`      | 10000         | Queue size for pending logEvents.                                                                    |
| `async.event.handler.num.workers`         | 2             | Number of worker threads.                                                                            |
| `async.event.handler.max.connections`     | 200           | Maximum number of connections.                                                                       |
| `async.event.handler.event.max.per.route` | 20            | Maximum number of connections per route.                                                             |
| `async.event.handler.validate.after`      | 1000          | Period of inactivity (in milliseconds) after which a pooled connection is re-validated before reuse. |

## BatchEventProcessor

The Java SDK provides `BatchEventProcessor`, the default implementation of the `EventProcessor` interface, and batches events.

For more details and configuration options, refer to the [article on event batching in Java](docId:8Rgr211FHoEKTciY_VgG1).

```java
EventHandler eventHandler = AsyncEventHandler.builder()
    .withQueueCapacity(10000)
    .withNumWorkers(5)
    .build();

EventProcessor batchProcessor = BatchEventProcessor.builder()
    .withBatchSize(50)
    .withEventHandler(eventHandler)
    .build();
```

## Optimizely properties

You can use an available `optimizely.properties` file within the runtime classpath to provide default values of a given Optimizely resource. Refer to the resource implementation for available configuration parameters.

An example `optimizely.properties` file:

```java
http.project.config.manager.polling.duration = 1
http.project.config.manager.polling.unit = MINUTES

async.event.handler.queue.capacity = 20000
async.event.handler.num.workers = 5
```

## Dispose of the client

For effective resource management with the Optimizely Java SDK, you must properly close the Optimizely client instance when it is no longer needed. This is done by calling `.close()`.

The `.close()` method ensures that the processes and queues associated with the instance are properly released. This is essential for preventing memory leaks and ensuring that the application runs efficiently, especially in environments where resources are limited or in applications that create and dispose of many instances over their lifecycle.

See [Close Optimizely Feature Experimentation Java SDK on application exit](docId:8Rgr211FHoEKTciY_VgG1).

## `ODPManager`

The `ODPManager` contains the logic supporting Real-Time Audiences for Feature Experimentation-related features, including audience segments. You must configure this parameter to use Real-Time Audiences for Feature Experimentation.

When instantiated with the Optimizely Builder, a default or custom `ODPManager` instance must be provided with `withODPManager()`. Otherwise, the Real-Time Audiences for Feature Experimentation features are disabled.

When instantiated with `OptimizelyFactory`, `ODPManager` is enabled by default, and the default version of `ODPManager` is used.

The following settings are optionally configurable when the Java SDK is initialized:

- **ODP SegmentsCache size** – `withSegmentCacheSize`
  - **Default** – 10,000
  - Set to 0 to disable caching.
- **ODP SegmentsCache timeout** (in seconds) – `withSegmentCacheTimeout`
  - **Default** – 600 secs (10 minutes)
  - Set to 0 to disable timeout (never expires).
- **Disable ODP** – there is no disable flag. Build the client with `Optimizely.builder()` and do not call `withODPManager()`; Real-Time Audiences for Feature Experimentation features are then disabled.

See [Customize ODPSegmentManager](docId\:WlItbLTrjYBNvKSjoGIFY).

### `ODPSegmentManager`

This module provides an interface to the remote ODP server for audience segment mappings.

It fetches the qualified audience segments for the given user context and returns them as a string array in the completion handler.

It also manages an audience segment's cache shared for the user contexts. The cache is in memory (not persistent), and rebooting the device or terminating the app resets it.

### `ODPEventManager`

This module provides an interface to the remote ODP server for events.

It queues the pending events in memory (not persistent) and sends them (in batches of up to 10) to the ODP server when resources are available, including network connection and ODP public key (in the SDK's datafile).

:::hint{type="info"}
The Java SDK tries to dispatch all events (stored in an in-memory queue and retried on recoverable errors; pending events are lost if the process exits) but does not guarantee completion.
:::

```java
// You must first configure Real-Time Audiences for Feature Experimentation
// before being able to calling ODPApiManager, ODPEventManager, and ODPSegmentManager.
ODPApiManager defaultODPApiManager = new DefaultODPApiManager();
ODPEventManager odpEventManager = new ODPEventManager(defaultODPApiManager);
ODPSegmentManager odpSegmentManager = new ODPSegmentManager(defaultODPApiManager);

ODPManager odpManager = ODPManager.builder()
     .withEventManager(odpEventManager) // Optional
     .withSegmentManager(odpSegmentManager) //Optional
     .withApiManager(defaultODPApiManager)
     .build();
```

### Customize `ODPApiManager`

`ODPApiManager` is an interface, and its implementation requires the user to provide the functionality of the following methods:

```java
public interface ODPApiManager {
    List<String> fetchQualifiedSegments(String apiKey, String apiEndpoint, String userKey, String userValue, Set<String> segmentsToCheck);

    Integer sendEvents(String apiKey, String apiEndpoint, String eventPayload);
}
```

You can provide `segmentFetchTimeoutMillis` and `eventDispatchTimeoutMillis` in the constructor of `DefaultODPApiManager`.

```java
int segmentFetchTimeoutMillis = 20000;    // 20,000 msecs = 20 secs
int eventDispatchTimeoutMillis = 20000;		// 20,000 msecs = 20 secs

ODPApiManager defaultODPApiManager = new DefaultODPApiManager(segmentFetchTimeoutMillis, eventDispatchTimeoutMillis)
```

### Customize `ODPEventManager`

You can provide custom `queueSize` and `flushInterval` in the constructor. If set to zero, `batchSize` is 1; otherwise, `batchSize` is the default of 10.

```java
int queueSize = 20000;

// Note: if this is set to 0 then batchSize will be set to 1, otherwise batchSize will be default, which is 10.
int flushIntervalInMillis = 10000;  // 10,000 msecs = 10 secs 

ODPEventManager odpEventManager = new ODPEventManager(defaultODPApiManager, queueSize, flushIntervalInMillis);
```

### Customize `ODPSegmentManager`

You can provide `cacheSize` and `cacheTimeout` in the constructor, but a custom cache in `ODPManager` ignores these values. By default, `ODPSegmentManager` uses `LRUCacheManager`.

```java
int cacheSize = 100;
int cacheTimeoutInSeconds = 600;  // 10 mins = 600 secs

ODPSegmentManager odpSegmentManager = new ODPSegmentManager(defaultODPApiManager, cacheSize, cacheTimeoutInSeconds);

// Second method to set custom cache size and timeout using odpManager
ODPApiManager defaultODPApiManager = new DefaultODPApiManager();
ODPManager odpManager = ODPManager.builder()
                            .withSegmentCacheSize(cacheSize) 
                            .withSegmentCacheTimeout(cacheTimeoutInSeconds) // seconds
                            .withApiManager(defaultODPApiManager)
                            .build();
```

### Custom cache

You can use the custom cache, which stores the `fetchQualifiedSegments` results, by implementing the `Cache` interface.

```java
public interface Cache<T> {
   int DEFAULT_MAX_SIZE = 10000;
   int DEFAULT_TIMEOUT_SECONDS = 600;
   void save(String key, T value);
   T lookup(String key);
   void reset();
}
```

The following is an example of a `customCache` implementation:

```java
import com.optimizely.ab.internal.Cache;

import java.util.LinkedHashMap;
import java.util.Map;

public class CustomCache<T> implements Cache<T> {
   private final Object lock = new Object();
   private final Map<String, T> linkedHashMap = new LinkedHashMap<>();
   
   public CustomCache() {
   }
   
   @Override
   public void save(String key, T value) {
       synchronized (lock) {
           linkedHashMap.put(key, value);
       }
   }

   @Override
   public T lookup(String key) {
       synchronized (lock) {
           if (linkedHashMap.containsKey(key)) {
               return linkedHashMap.get(key);
           }
           return null;
       }
   }

   @Override
   public void reset() {
       synchronized (lock) {
           linkedHashMap.clear();
       }
   }
}
```

Pass this `customCache` object to `ODPManager` using its builder option and do not pass `odpSegmentManager` object.

```java
ODPApiManager defaultODPApiManager = new DefaultODPApiManager();

ODPEventManager odpEventManager = new ODPEventManager(defaultODPApiManager);
                
// To use a custom cache, do not pass `odpSegmentManager`.
ODPManager odpManager = ODPManager.builder()
     .withEventManager(odpEventManager) // Optional
     .withSegmentCache(new CustomCache<>()) //Optional
     .withApiManager(defaultODPApiManager)
     .build();
```

## `OptimizelyFactory`

In this package, [`OptimizelyFactory`](https://github.com/optimizely/java-sdk/blob/master/core-httpclient-impl/src/main/java/com/optimizely/ab/OptimizelyFactory.java) provides a basic utility to instantiate the Optimizely Java SDK with a minimal number of configuration options. The package sources configuration properties from Java system properties, environment variables, or an `optimizely.properties` file, in that order.

`OptimizelyFactory` does not capture all configuration and initialization options. For more use cases, build the resources through their respective builder classes.

When instantiated with the `OptimizelyFactory` methods, the Java SDK uses the default configuration of `HttpProjectConfigManager`, `BatchEventProcessor`, and `AsyncEventHandler`.

The following example shows how to initialize the Java SDK using the `OptimizelyFactory` methods.

```java
Optimizely optimizelyClient = OptimizelyFactory.newDefaultInstance(sdkKey);

// If you provide the SDK key via a global property, use the empty signature:
Optimizely optimizely = OptimizelyFactory.newDefaultInstance();

// with fallback datafile
Optimizely optimizelyClient = OptimizelyFactory.newDefaultInstance(sdkKey, datafile);
```

In addition to the datafile, you need to provide an event dispatcher (also called event handler) object as an argument to the `Optimizely.builder` function. Use the default event dispatcher implementation, or provide your own implementation as described in [Configure the event dispatcher](docId\:BSfueGrncnMBnGu5LoIW-).

### Use authenticated datafile in a secure environment

You can fetch the Optimizely datafile from an authenticated endpoint using a server-side (only) SDK, like the Java SDK.

To use an [authenticated datafile](docId\:YpSOMYlWkUccLCL-uB-ZE), download your Optimizely environment's access token from the Optimizely app at **Settings > Environments**. Select your secure environment, and copy the **Datafile access token**. The following example shows how to initialize the Optimizely client using an access token and `sdkKey`, enabling the client to fetch the authenticated datafile and complete initialization.

```java
// fetch the datafile from an authenticated endpoint
String accessToken = "YOUR_DATAFILE_ACCESS_TOKEN";
String sdkKey = "YOUR_SDK_KEY";
Optimizely optimizelyClient = OptimizelyFactory.newDefaultInstance(sdkKey, null, accessToken);
```

## CMAB configuration

Configure caching behavior for Contextual Multi-Armed Bandit (CMAB) decisions at initialization time.

Use `DefaultCmabService.Builder` class methods to configure CMAB caching before initializing your client.

### Parameters

| Parameter    | Method                                   | Type         | Default                   | Description                                                                                |
| ------------ | ---------------------------------------- | ------------ | ------------------------- | ------------------------------------------------------------------------------------------ |
| Cache Size   | `withCmabCacheSize(int)`                 | Integer      | 10000                     | Maximum number of cached decisions                                                         |
| Cache TTL    | `withCmabCacheTimeoutInSecs(int)`        | Integer      | 1800 seconds (30 minutes) | Time-to-live for cache entries                                                             |
| Custom Cache | `withCustomCache(Cache<CmabCacheValue>)` | Cache        | null                      | Optional custom cache implementation. If not provided, uses built-in thread-safe LRU cache |
| CMAB Client  | `withClient(CmabClient)`                 | `CmabClient` | Required                  | Client implementation for fetching CMAB decisions                                          |

For configuration examples, cache behavior details, and runtime control options, see [Configure the CMAB cache for the Java SDK](docId\:hHvoqIoDt1BEGHtMRk3B1).

## Source files

The language and platform source files containing the implementation for Java are available on [GitHub](https://github.com/optimizely/java-sdk/blob/master/core-api/src/main/java/com/optimizely/ab/Optimizely.java).
