Initialize the Java SDK
How to initialize the Optimizely Feature Experimentation Java SDK in your application.
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.
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:
- Set up an error handler and logger to catch issues.
- Manage network calls through a custom event dispatcher.
- Ensure sticky bucketing using your own User Profile Service.
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. |
withOdpManager(OdpManager) | OdpManager contains the logic supporting Real-Time Segments for Feature Experimentation-related features, including audience segments. The Java SDK enables the Real-Time Segments for Feature Experimentation methods by default. But, the methods do nothing unless you have enabled and configured the Real-Time Segments for Feature Experimentation integration. To optionally disable Real-Time Segments for Feature Experimentation, see OdpManager. |
Returns
An instantiated instance of the Optimizely class.
Example
The example below shows how to initialize the Optimizely Feature Experimentation Java SDK using the builder options.
Optimizely Feature Experimentation provides default implementations of HttpProjectConfigManager
, BatchEventProcessor
, and AsyncEventHandler
.
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
ConfigParseException – The SDK could not parse the datafile because it is malformed or has an incorrect schema.
HttpProjectConfigManager
Whenever the experiment configuration changes, the SDK uses automatic datafile management (ADM) to handle the change for you.
HttpProjectConfigManager
is an implementation of the abstract PollingProjectConfigManager
. 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 example below shows how to initialize the HttpProjectConfigManager
with builder options.
ProjectConfigManager projectConfigManager = HttpProjectConfigManager.builder()
.withSdkKey(sdkKey)
.withDatafile(datafile)
.withPollingInterval(1, TimeUnit.MINUTES)
.withBlockingTimeout(10, TimeUnit.SECONDS)
.build();
SDK key
The SDK key composes the outbound HTTP request to the default datafile location on the Optimizely CDN.
Polling 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 also serves as 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 Optimizely Feature Experimentation datafile |
withFormat(String) | 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 Feature Experimentation 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 Feature Experimentation project SDK key |
Update Config Notifications
The SDK triggers a notification signal after fetching a datafile. To subscribe to these notifications, use theOptimizely.addUpdateConfigNotificationHandler
:
NotificationHandler<UpdateConfigNotification> handler = message ->
System.out.println("Received new datafile configuration");
optimizely.addUpdateConfigNotificationHandler(handler);
Alternatively, you can add the handler directly to the NotificationCenter
:
notificationCenter.addNotificationHandler(UpdateConfigNotification.class, handler);
AsyncEventHandler
AsyncEventHandler
provides an implementation of EventHandler
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 Feature Experimentation 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.
Note
When using the Optimizely builder class, you must provide an implementation of the event handler as shown below. Otherwise, the Optimizely Feature Experimentation 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 if you want to use the default AsyncEventHandler
implementation.
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) | 5000 | Time to maintain idol connections (in milliseconds) |
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 | 5000 | Time to maintain idle connections (in milliseconds) |
BatchEventProcessor
The Optimizely Feature Experimentation 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.
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 Feature Experimentation resource. Refer to the resource implementation for available configuration parameters.
An example optimizely.properties
file:
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.
ODPManager
ODPManager
contains the logic supporting Real-Time Segments for Feature Experimentation-related features, including audience segments.
When instantiated with Optimizely Builder
, a default or custom ODPManager
instance must be provided with withODPManager()
. Otherwise, the Real-Time Segments 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) –
withSegmentsCacheTimeout
- Default – 600 secs (10 minutes)
- Set to 0 to disable timeout (never expires).
- ODP enable –
withOdpDisabled
- Default – false (enabled)
- The Java SDK returns or logs an
odpNotEnabled
error when ODP is disabled and its features are requested.
See Customize ODPSegmentManager.
ODPSegmentManager
This module provides an interface to the remote ODP server for audience segment mappings.
It fetches the qualified segments for the given user context and returns them as a string array in the completion handler.
It also manages a 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 (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).
Note
The Java SDK tries to dispatch all events (stored in a persistent queue and retried on recoverable errors) but does not guarantee completion.
// You must first configure Real-Time Segments 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:
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
.
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.
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
.
int cacheSize = 100;
int cacheTimeoutInSeconds= 600; // 10 mins = 600 secs
ODPSegmentManager odpSegmentManager = new ODPSegmentManager(defaultODPApiManager, cacheSize, cacheTimeoutInSecs);
// Second method to set custom cache size and timeout using odpManager
ODPApiManager defaultODPApiManager = new DefaultODPApiManager();
ODPManager odpManager = ODPManager.builder()
.withSegmentCacheSize(cacheSize)
.withSegmentCacheTimeout(cacheTimeoutInSecs) // seconds
.withApiManager(defaultODPApiManager)
.build();
Custom cache
You can use the custom cache, which stores the fetchQualifiedSegments
results, by implementing the Cache
interface.
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();
}
Here is an example of customCache implementation:
public class CustomCache<T> implements Cache<T> {
private final Object lock = new Object();
final Map<String, T> linkedHashMap = new LinkedHashMap<>();
public CustomCache() {
}
public void save(String key, T value) {
synchronized (lock) {
linkedHashMap.put(key, value);
}
}
public T lookup(String key) {
synchronized (lock) {
if (linkedHashMap.containsKey(key)) {
T entity = linkedHashMap.get(key);
return entity;
}
return null;
}
}
public void reset() {
synchronized (lock) {
linkedHashMap.clear();
}
}
}
Pass this customCache
object to OdpManager
using its builder option and do not pass odpSegmentManager
object:
ODPApiManager defaultODPApiManager = new DefaultODPApiManager();
ODPEventManager odpEventManager = new ODPEventManager(defaultODPApiManager);
// Note: To use custom Cache user should not pass odpSegmentManager
ODPManager odpManager = ODPManager.builder()
.withEventManager(odpEventManager) // Optional
.withSegmentCache(new CustomCache<>()); //Optional
.withApiManager(defaultODPApiManager)
.build();
OptimizelyFactory
In this package, OptimizelyFactory
provides a basic utility to instantiate the Optimizely Feature Experimentation 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 via their respective builder classes.
When instantiated with the OptimizelyFactory
methods, the Java SDK uses the default configuration of HttpProjectConfigManager
, BatchEventProcessor
, and AsyncEventHandler
.
The example below shows how to initialize the Java SDK using the OptimizelyFactory methods.
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 our default event dispatcher implementation, or provide your own implementation as described in Configure the event dispatcher.
Use authenticated datafile in a secure environment
You can fetch the Optimizely datafile from an authenticated endpoint using a server-side (only) Optimizely Feature Experimentation SDK, for example, the Java SDK.
To use an authenticated datafile, 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 example below shows how to initialize the Optimizely client using an access token and sdkKey, enabling the client to fetch the authenticated datafile and complete initialization.
// 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);
Source files
The language and platform source files containing the implementation for Java are available on GitHub.
Updated 6 months ago