Sitelet https://docs.growthbook.io/lib/java
Skip to main content
This supports Java applications using Java version 1.8 and higher.
Upgrading from 0.10.x0.11.0 upgrades OkHttp from 4.11.0 to 5.4.0 (#218). The lib module publishes these runtime dependencies:
  • com.squareup.okhttp3:okhttp:5.4.0
  • com.squareup.okhttp3:okhttp-sse:5.4.0
OkHttp 5 also brings in kotlin-stdlib 2.x and okio 3.x transitively. If your application or another library declares OkHttp 4.x, move it to 5.4.0 as well:
  • Gradle reads the OkHttp Gradle module metadata, so com.squareup.okhttp3:okhttp keeps resolving to a single version. A forced or BOM-managed 4.x version wins over the 5.4.0 this SDK requests.
  • Maven does not read that metadata. At 5.x, the com.squareup.okhttp3:okhttp jar contains no classes. The JVM classes are in com.squareup.okhttp3:okhttp-jvm, which the SDK gets through okhttp-sse. Do not keep a direct okhttp:4.x dependency next to the SDK, because both versions end up on the classpath. If you depend on OkHttp directly, use com.squareup.okhttp3:okhttp-jvm:5.4.0.
  • Spring Boot 3.3 and earlier (including 2.7) manage okhttp.version as 4.x in their dependency BOM, which downgrades the SDK’s OkHttp. Override it, for example with ext['okhttp.version'] = '5.4.0' in Gradle or <okhttp.version>5.4.0</okhttp.version> in Maven. Spring Boot 3.4 and later no longer manage OkHttp.
The published coordinates changed in the same release. 0.10.x installed com.github.growthbook:growthbook-sdk-java. At 0.11.0 that coordinate is a JitPack aggregator POM: it depends on com.github.growthbook.growthbook-sdk-java:lib, plus the Caffeine and JCache adapter modules, which also pull in com.github.ben-manes.caffeine:caffeine:2.9.3 and javax.cache:cache-api:1.1.1. Depend on lib for the SDK itself, and add an adapter module only when you want that cache.Other changes to check when upgrading:
  • GrowthBookClient.initialize() validates Options and returns false for settings 0.10.x accepted. See Configuration validation.
  • Feature fetches retry by default. A failing synchronous initialize() or refreshFeatures() can block for about 15 seconds before it fails. See Customizing refresh timing and retries.
  • Server-Sent Events reconnects stop after retryPolicy max attempts consecutive failures (default: 5). 0.10.x reconnected indefinitely.
  • Each GrowthBookClient has its own features repository. In 0.10.x, all clients in a JVM shared the repository of the first client that was initialized.
  • TrackData.getResult() returns ExperimentResult<T> instead of FeatureResult<T>. Custom implementations of IGrowthBook must implement the new FeatureKey methods.

Installation

JitPack version badge for the GrowthBook Java SDK

Gradle

To install in a Gradle project, add Jitpack to your repositories, and then add the dependency with the latest version to your project’s dependencies.

Maven

To install in a Maven project, add Jitpack to your repositories:
Next, add the dependency with the latest version to your project’s dependencies:

Usage

There are two main approaches to using the GrowthBook Java SDK:
  1. Enhanced Client (Recommended) - For better performance with multi-context support
  2. Traditional per-request approach - Create a new context and SDK instance per request
Available starting in version 0.9.0 For improved performance and better resource management, especially in web applications, use the enhanced client pattern. This approach allows you to reuse a single client instance across multiple requests while providing different user contexts for each evaluation while calling the feature methods like isOn(). This GrowthBookClient instance is decoupled from the GBContext, creates a singleton featureRepository based on your refreshStrategy and uses the latest features at the time of evaluation, all managed internally.

Basic Enhanced Client Usage

Configuring the client with Options

The Options builder configures the GrowthBookClient. apiHost and clientKey are required. apiHost must use http or https; a host without a scheme is treated as https.

Configuration validation

GrowthBookClient.initialize() validates Options before it fetches features. OptionsValidator.validate(options) throws InvalidOptionsException (an IllegalArgumentException) and lists every problem on getViolations(). initialize() catches that exception, logs it, and returns false. Checks include:
  • apiHost is present and uses http or https (a host without a scheme is treated as https)
  • clientKey is present
  • swrTtlSeconds is greater than 0 when set
  • backgroundFetchInterval is not negative when set
  • remoteEvalCacheTtlSeconds is greater than 0 when set
  • Cache settings do not conflict (CacheMode.CUSTOM without a cacheManager, or a cacheManager while caching is disabled)
  • Remote evaluation constraints, listed under Remote Evaluation

Using the Enhanced Client with Different User Contexts

Thread Safety Note

While the enhanced client is designed for concurrent use, full concurrency support is still being refined. For high-concurrency applications, consider:
  • Using connection pooling for database-backed sticky bucket services
  • Implementing proper synchronization for custom tracking callbacks
  • Testing thoroughly under expected load conditions

Traditional Usage

For new projects, we recommend using the Enhanced Client instead for better performance. There are 2 steps to initializing the traditional GrowthBook SDK:
  1. Create a GrowthBook context GBContext with the features JSON and the user attributes
  2. Create the GrowthBook SDK class with the context

GrowthBook context

The GrowthBook context GBContext can be created either by implementing the builder class, available at GBContext.builder(), or by using the GBContext constructor.

Using the GBContext builder

The builder is the easiest to use way to construct a GBContext, allowing you to provide as many or few arguments as you’d like. All fields mentioned above are available via the builder.
HttpClient recommendationThe above example uses java.net.http.HttpClient which, depending on your web framework, may not be the best option, in which case it is recommended to use a networking library more suitable for your implementation.

Using the GBContext constructor

You can also use GBContext constructor if you prefer, which will require you to pass all arguments explicitly.
For complete examples, see the Examples section below.

Features

The features JSON is equivalent to the features property that is returned from the SDK Connection endpoint.

Attributes

Attributes are a JSON string. You can specify attributes about the current user and request. Here’s an example:
If you need to set or update attributes asynchronously, you can do so with Context#attributesJson or GrowthBook#setAttributes. This will completely overwrite the attributes object with whatever you pass in. Also, be aware that changing attributes may change the assigned feature values. This can be disorienting to users if not handled carefully.

Secure Attributes

When secure attribute hashing is enabled, all targeting conditions in the SDK payload referencing attributes with datatype secureString or secureString[] will be anonymized via SHA-256 hashing. This allows you to safely target users based on sensitive attributes. You must enable this feature in your SDK Connection for it to take effect. If your SDK Connection has secure attribute hashing enabled, you will need to manually hash any secureString or secureString[] attributes that you pass into the GrowthBook SDK. To hash an attribute, use a cryptographic library with SHA-256 support, and compute the SHA-256 hashed value of your attribute plus your organization’s secure attribute salt.

Using Features

Every feature has a “value” which is assigned to a user. This value can be any JSON data type. If a feature doesn’t exist, the value will be null. There are 4 main methods for evaluating features. GrowthBookClient methods take a UserContext. On that client, getFeatureValue and evalFeature also take a Class<T>, such as String.class. GrowthBook.evalFeature takes a Class<T> as well. The primitive GrowthBook.getFeatureValue overloads still infer the type from defaultValue.

isOn() / isOff()

These methods return a boolean for truthy and falsy values. Only the following values are considered to be “falsy”:
  • null
  • false
  • ""
  • 0
Everything else is considered “truthy”, including empty arrays and objects. If the value is “truthy”, then isOn() will return true and isOff() will return false. If the value is “falsy”, then the opposite values will be returned.

getFeatureValue(featureKey, defaultValue)

This method has a variety of overloads to help with casting values to primitive and complex types. In short, the type of the defaultValue argument will determine the return type of the function. See the Java Docs for more information. See the unit tests for example implementations including type casting for all above-mentioned methods.

evalFeature(String)

The evalFeature method returns a FeatureResult<T> object with more info about why the feature was assigned to the user. The T type corresponds to the value type of the feature. In the above example, T is Float. FeatureResult<T> has the following getters. getValue() returns the raw evaluated value as Object, so cast it yourself, or use getFeatureValue when you only need the typed value. As expected in Kotlin, you can access these getters using property accessors.

Typed feature access

Available starting in version 0.11.0 FeatureKey carries both the feature identifier and its value type. Declare keys once with TypedKey, then pass them to GrowthBookClient or GrowthBook instead of raw strings.
getFeature returns the raw evaluated value. It does not deserialize that value into the key’s type. Use getFeatureValue(FeatureKey, defaultValue, UserContext) on GrowthBookClient, or getFeatureValue(FeatureKey, defaultValue) on GrowthBook, when you need a deserialized object. TypedKey also provides ofDouble and ofFloat. The matching client methods are getDoubleFeature and getFloatFeature.

Inline Experiments

Instead of declaring all features up-front in the context and referencing them by IDs in your code, you can also just run an experiment directly. This is done with the growthbook.run(Experiment<T>) method. The Experiment builder takes ArrayList values for variations and weights, and a Gson JsonObject for conditionJson.

Inline experiment return value ExperimentResult

An ExperimentResult<T> is returned where T is the generic value type for the experiment. There’s also a number of methods available. As expected in Kotlin, you can access these getters using property accessors.

Tracking feature usage and experiment impressions

This section covers how to track and monitor feature usage and experiment impressions in your application. There are several types of callbacks and events you can subscribe to:
  1. Tracking Callback - Called when a user is included in an experiment
  2. Feature Usage Callback - Called every time a feature is evaluated
  3. Experiment Run Callback - Called when an inline experiment is run with run() (regardless of inclusion)

Tracking Callback

Any time an experiment is run to determine the value of a feature, we may call this callback so you can record the assigned value in your event tracking or analytics system of choice. The tracking callback is only called when the user is in the experiment. If they are not in the experiment, this will not be called.

Feature Usage Callback

Any time a feature is evaluated (regardless of experiment participation), this callback is called with the feature key and result.

Experiment Run Callback

You can subscribe to inline experiment runs using the ExperimentRunCallback. This callback is called regardless of whether the user is included in the experiment. Subscriptions fire from run() only, not from feature evaluation such as isOn() or evalFeature(). They fire the first time an experiment key is run, and again only when inExperiment or the variation changes for that key on the same instance. On a shared GrowthBookClient, that state is kept per experiment key across all users, not per user.

Multiple Callback Subscriptions

You can subscribe to multiple experiment run callbacks. onRun is a generic method, so implement the callback with a class or an anonymous class. A Java lambda cannot implement it.

Working with Encrypted features

As of version 0.3.0, the Java SDK supports decrypting encrypted features. You can learn more about SDK Connection Endpoint Encryption. The main difference is you create a GBContext by passing an encryption key (.encryptionKey() when using the builder) and using the encrypted payload as the features JSON (.featuresJson() for the builder).

Fetching, Caching, and Refreshing features with GBFeaturesRepository

As of version 0.4.0, the Java SDK provides an optional GBFeaturesRepository class which will manage networking for you in the following ways:
  • Fetching features from the SDK endpoint when initialize() is called
  • Decrypting encrypted features when provided with a decryption key, e.g. .builder().decryptionKey(clientKey) (encryptionKey on this builder is deprecated)
  • Caching features (in-memory)
  • Refreshing features
If you wish to manage fetching, refreshing, and caching features on your own, you can choose to not implement this class.
RecommendationThis class should be implemented as a singleton class as it includes caching and refreshing functionality.
If you have more than one SDK endpoint you’d like to implement, you can extend the GBFeaturesRepository class with your own class to make it easier to work with dependency injection frameworks. Each of these instances should be singletons.

Fetching the features

You will need to create a singleton instance of the GBFeaturesRepository class either by implementing its .builder() or by using its constructor. Then, you would call myGbFeaturesRepositoryInstance.initialize() in order to make the initial (blocking) request to fetch the features. Then, you would call myGbFeaturesRepositoryInstance.getFeaturesJson() and provided that to the GBContext initialization.
For more references, see the Examples below.

Caching and refreshing behavior

There are 3 refresh strategies available, selected with FeatureRefreshStrategy on the Options or GBFeaturesRepository builder.
Stale While Revalidate
This is the default strategy but can be explicitly stated by passing FeatureRefreshStrategy.STALE_WHILE_REVALIDATE as the refresh strategy option to the GBFeaturesRepository builder or constructor. The GBFeaturesRepository will automatically refresh the features when the features become stale. Features are considered stale every 60 seconds. This amount is configurable with the swrTtlSeconds option, which also sets the interval of the background poll that drives the refresh. When you fetch features and they are considered stale, the stale features are returned from the getFeaturesJson() method and a network call to refresh the features is enqueued asynchronously. When that request succeeds, the features are updated with the newest features, and the next call to getFeaturesJson() will return the refreshed features. The repository sends If-None-Match and keeps the current payload when the server responds 304 Not Modified.
Re-read getFeaturesJson() per requestGBContext holds the features JSON you pass it as a fixed snapshot. If you build a single GBContext at startup, refreshed features will never reach it no matter what swrTtlSeconds is set to. Either call getFeaturesJson() again when building each request’s context, or use the Enhanced Client, which handles this for you.
swrTtlSeconds has no effect under the SERVER_SENT_EVENTS refresh strategy, where updates are pushed as they happen.
Server-Sent Events
This is a new strategy that can be enabled by passing FeatureRefreshStrategy.SERVER_SENT_EVENTS as the refresh strategy option to the GBFeaturesRepository builder or constructor. If you’re using GrowthBook Cloud , this is ready for you to use. If you are self-hosting, you will need to set up the GrowthBook Proxy to enable it.
Remote Eval strategy
FeatureRefreshStrategy.REMOTE_EVAL_STRATEGY is a repository refresh mode. On initialize(), GBFeaturesRepository calls fetchForRemoteEval with the configured RequestBodyForRemoteEval. That strategy is separate from Options.remoteEval(true), which is the client remote evaluation path described in Remote Evaluation. Setting only REMOTE_EVAL_STRATEGY does not set remoteEval. When remoteEval(true) is set, GrowthBookClient.initialize() does not create a features repository for evaluation, so this strategy does not apply.

Caching feature payloads

The SDK can persist the fetched feature payload so a restart does not require a fresh network call before features are available. Configure this with cacheMode (CacheMode):
AUTO and FILE use cacheDirectory when it is set. Otherwise AUTO tries the growthbook.cache.dir system property, then the platform cache directory, then java.io.tmpdir, so it usually writes to disk. isCacheDisabled(true) turns persistence off. CacheMode.CUSTOM requires a cacheManager. Supplying a cacheManager while caching is disabled fails configuration validation.
Caffeine cache adapter
The optional growthbook-cache-caffeine module is a Caffeine-backed GbCacheManager. It depends on com.github.ben-manes.caffeine:caffeine:2.9.3. Published on JitPack as com.github.growthbook.growthbook-sdk-java:growthbook-cache-caffeine:0.11.0. That module already depends on lib.
CaffeineGbCacheManager.builder() returns CaffeineCacheOptions.Builder. buildManager() returns the cache manager. maximumSize defaults to 1000. maximumWeight(long) bounds the cache by UTF-8 payload bytes and takes precedence over maximumSize. recordStats(true) turns on the counters reported by stats(); without it they stay at 0.
JCache adapter
The optional growthbook-cache-jcache module wraps a JSR-107 cache. Published on JitPack as com.github.growthbook.growthbook-sdk-java:growthbook-cache-jcache:0.11.0. It depends on javax.cache:cache-api:1.1.1 and on lib. It does not include a cache provider. Put a JSR-107 provider such as Ehcache or Hazelcast on the runtime classpath.
JCacheGbCacheManager.builder() returns JCacheCacheOptions.Builder. cacheManager is required. cacheName defaults to growthbook-features. Eviction and expiry are configured on the provider, not on this adapter.

Customizing refresh timing and retries

You can set a minimum interval between non-forced background refreshes and a bounded exponential retry policy for feature fetches. When retryPolicy is omitted, the repository uses FeatureFetchRetryPolicy’s default: 5 total attempts, with retry waits of 1s, 2s, 4s, and 8s. The default maximum delay is 16 seconds. Network errors, 408, 429, and 5xx responses are retried. Retries run on the calling thread, so a failing initialize() can block for about 15 seconds with the default policy. The same maxAttempts limits consecutive Server-Sent Events reconnect attempts.

Forcing a refresh

GrowthBookClient.refreshFeatures() honors the configured background fetch interval. refreshFeatures(RefreshMode.FORCE) always performs a network request and bypasses cache freshness checks. Current features stay available for evaluation while the refresh runs.
refreshFeature() is deprecated and calls refreshFeatures(), except when remote evaluation is enabled. In that case it clears the remote evaluation cache.

Sticky Bucketing

By default, GrowthBook does not persist assigned experiment variations for a user. It relies on deterministic hashing so the same user attributes always map to the same experiment variation. That is not always enough. If you change targeting conditions in the middle of an experiment, users may stop being shown a variation even if they were previously bucketed into it. Sticky Bucketing keeps users on the same experiment variant when user attributes, login status, or experiment parameters change. See the Sticky Bucketing docs. Provide a StickyBucketService. The SDK includes InMemoryStickyBucketServiceImpl. Implement the interface yourself for a database or other store. The SDK chooses which attributes to look up. GrowthBookClient requests assignments for every primitive user attribute. GrowthBook uses each experiment’s hashAttribute and fallbackAttribute. The stickyBucketIdentifierAttributes option is accepted but not used for this in 0.11.0. A StickyAssignmentsDocument has three fields:
  • attributeName: the attribute used to identify the user, such as id or cookie_id
  • attributeValue: the value of that attribute, such as 123
  • assignments: persisted experiment assignments, for example {"exp1__0":"control"}
attributeName and attributeValue together are the primary key.

Implementing a custom StickyBucketService

Implement StickyBucketService to persist assignments. getAssignments, saveAssignments, and getAllAssignments are the interface methods. The map returned by getAllAssignments must be keyed by attributeName + "||" + attributeValue, because the SDK looks documents up by that key. InMemoryStickyBucketServiceImpl uses the same format.
Do not set stickyBucketService when remoteEval is enabled. Remote evaluation rejects that combination. See Remote Evaluation.

Remote Evaluation

The remoteEval option is available starting in version 0.11.0. refreshForRemoteEval and FeatureRefreshStrategy.REMOTE_EVAL_STRATEGY were available earlier. Remote Evaluation evaluates feature flags on a private server. Targeting rules and unused variations stay off the client. On a trusted server it is usually unnecessary, because the SDK can evaluate the full payload locally. It is mainly useful for Android and other clients you don’t control. Enable it in the SDK Connection settings. Cloud customers need a self-hosted GrowthBook Proxy or another remote evaluation backend. Read the remote evaluation guide. On GrowthBookClient, set remoteEval(true). initialize() prepares the remote evaluation client and returns false when setup fails. Later evaluations call the remote API for the current user attributes and cache the response. If that request fails, the SDK logs a warning and evaluates against an empty feature set, so features return their fallback values. preloadRemoteEval(UserContext) warms that cache and returns false when remote evaluation is off or the request fails.
apiHost cannot be growthbook.io or a subdomain of it. OptionsValidator also rejects remote evaluation when:
  • decryptionKey is set
  • stickyBucketService is set
  • refreshStrategy is explicitly FeatureRefreshStrategy.STALE_WHILE_REVALIDATE
Leave refreshStrategy unset, as in the example above. remoteEval(true) does not change that field. The validator reads getRefreshStrategy(), which is null until you set it, and null is allowed. getRefreshingStrategy() still reports STALE_WHILE_REVALIDATE when the field is unset, but remote evaluation does not use that default. Setting the strategy explicitly to STALE_WHILE_REVALIDATE is what makes initialize() return false. Sticky bucketing for this mode belongs on the remote evaluation backend. The client must not be given a StickyBucketService. refreshForRemoteEval(RequestBodyForRemoteEval) sends an explicit payload. RequestBodyForRemoteEval accepts attributes (JsonObject), forcedFeatures, forcedVariations, and url.
With refreshStrategy(FeatureRefreshStrategy.SERVER_SENT_EVENTS), the client listens for feature updates and clears the remote evaluation cache when they arrive. GBContext.builder().remoteEval(true) is the same flag for a traditional GrowthBook instance. It also requires apiHost and clientKey. With an invalid configuration, new GrowthBook(context) throws InvalidOptionsException instead of returning a failure value.

Custom Fields

Available starting in version 0.11.0 Experiments can carry custom metadata configured in GrowthBook. Experiment exposes:
  • getCustomField(String fieldId) returns the raw value (Object), or null if it is absent
  • getCustomField(String fieldId, Class<FieldType> fieldType) casts or deserializes the value with Gson
  • hasCustomField(String fieldId) returns whether the field is present
FeatureRule stores the same metadata on getCustomFields(), which returns Map<String, Object>. It does not have the getCustomField(String) helpers that Experiment does.

Diagnostics

Available starting in version 0.11.0 GrowthBookClient.getDiagnostics() returns a Diagnostics snapshot. toJson() serializes it. getHealth().getState() is a DiagnosticsHealthState: READY, NOT_READY, DEGRADED, ERROR, or SHUTDOWN. getHealth().getIssues() lists DiagnosticsIssue values, each with getCode(), getSeverity(), and getMessage(). The snapshot also includes SDK, config, client, feature, refresh, cache, streaming, and remote evaluation sections through getSdk(), getConfig(), getClient(), getFeatures(), getRefresh(), getCache(), getStreaming(), and getRemoteEval().

Overriding Feature Values

The Java SDK allows you to override feature values and experiments using the URL.

Force Experiment Variations

You can force an experiment variation by passing the experiment key and variation index as query parameters in the URL you set on the GBContext. For example, if you add ?my-experiment-id=2 to the URL, users will be forced into the variation at index 2 in the variations list when evaluating the experiment with key my-experiment-id.

Force Feature Values via the URL

You can force a value for a feature by passing the key, prefixed by gb~, and the URI-encoded value in the URL’s query parameters. You must also set allowUrlOverrides to true when building your GBContext in order to enable this feature as it is not enabled by default.
The above code sample sets the following:
  • dark_mode: true
  • banner_text: "Hello, everyone! I hope you are all doing well!"
  • donut_price: 3.33

Supported types

The value passed in the URL is cast at runtime based on the generic type argument passed in when evaluating the feature. This means that when you call <ValueType>getFeatureValue(), what you pass into the URL must successfully cast as ValueType otherwise the value in the URL will be ignored. All keys must be prefixed with gb~.

Using with Proguard and R8

Many Android projects use code-shrinking and obfuscation tools like Proguard and R8 in production. If you are experiencing unexpected feature evaluation results with your release Android builds that do not occur in your debug builds, it’s most likely related to this. You will need to add the following to your proguard-rules.pro file to ensure that all of the GrowthBook SDK classes are kept so that your features are evaluated properly in projects that use Proguard and R8:

Code Examples

Web Framework Integration with Spring RestController

Additional Examples

Further Reading

Supported Features