Java SDK Reference
TL;DR: Add
io.featureflip:featureflip-java, callclient.boolVariation("flag-key", context, false), and you’re evaluating flags — thread-safe and non-blocking.
The io.featureflip:featureflip-java library provides a thread-safe client for evaluating feature flags in Java applications. The SDK targets Java 11+ via Gradle toolchains, so it runs on every modern JVM — including LTS releases (11, 17, 21) and recent non-LTS builds — with no module-system gymnastics. Distribution is Maven Central under io.featureflip:featureflip-java.
Internally, the client uses OkHttp for HTTP and SSE streaming and Jackson for JSON serialization. Both are well-known, battle-tested libraries that integrate cleanly with Spring, Micronaut, Quarkus, and Dropwizard. The streaming connection runs on a dedicated background thread; if it drops, the SDK falls back to polling every 30 seconds while continuing to serve the last known flag values.
The client is built once per process via FeatureflipClient.builder().sdkKey(...).build() and shared across threads. All variation methods (boolVariation, stringVariation, intVariation, doubleVariation, jsonVariation) read from a ConcurrentHashMap and return without blocking, so you can call them from request handlers, schedulers, or message consumers without coordinating locks. The matching client implementation also powers the Android SDK, which adds Kotlin coroutines and lifecycle integration on top.
Installation
Section titled “Installation”Gradle (Kotlin DSL)
Section titled “Gradle (Kotlin DSL)”dependencies { implementation("io.featureflip:featureflip-java:2.0.0")}Gradle (Groovy)
Section titled “Gradle (Groovy)”dependencies { implementation 'io.featureflip:featureflip-java:2.0.0'}<dependency> <groupId>io.featureflip</groupId> <artifactId>featureflip-java</artifactId> <version>2.0.0</version></dependency>Lifetime
Section titled “Lifetime”The Featureflip Java SDK is designed to be used as a singleton across your application. Obtain the client via the static factory:
FeatureflipClient client = FeatureflipClient.get("your-sdk-key");Or with custom configuration:
FeatureFlagConfig config = FeatureFlagConfig.builder() .baseUrl("https://eval.featureflip.io") .streaming(true) .build();FeatureflipClient client = FeatureflipClient.get("your-sdk-key", config);Calling get() (or builder().build()) multiple times with the same SDK key returns handles that share a single underlying client — the SDK maintains a process-wide cache keyed by SDK key. You cannot accidentally open duplicate streaming connections by constructing multiple clients.
If you’re using a DI container like Spring Boot or Micronaut, register the client as a singleton bean:
@Configurationpublic class FeatureflipConfig { @Bean public FeatureflipClient featureflipClient(@Value("${featureflip.sdk-key}") String sdkKey) { return FeatureflipClient.get(sdkKey); }}Even if you hand-roll a prototype-scoped or request-scoped registration, the factory’s deduplication ensures all resolves share one underlying client — the misregistration is neutralized, not catastrophic.
Closing
Section titled “Closing”close() is refcounted: when multiple handles share one cached client, closing one handle does not shut down the shared background tasks. The real shutdown runs only when the last handle is closed. In a typical application this means the client lives for the entire process lifetime and is cleaned up automatically at shutdown via a try-with-resources block in main or via the DI container’s shutdown lifecycle.
Configuration
Section titled “Configuration”The FeatureFlagConfig class is built via a builder with the following options:
| Option | Type | Default | Description |
|---|---|---|---|
baseUrl | String | "https://eval.featureflip.io" | Base URL of the Evaluation API |
connectTimeout | Duration | 5s | Timeout for establishing connections |
readTimeout | Duration | 10s | Timeout for reading responses |
streaming | boolean | true | Enable SSE for real-time flag updates |
pollInterval | Duration | 30s | Polling interval (used when streaming is disabled) |
flushInterval | Duration | 30s | Interval between event flush batches |
flushBatchSize | int | 100 | Maximum number of events per flush batch |
initTimeout | Duration | 10s | Maximum time to wait for initialization |
Configuration is applied through the client builder rather than constructed directly.
Initialization
Section titled “Initialization”Obtain a client via the static factory (recommended):
FeatureflipClient client = FeatureflipClient.get("sdk-dev-abc123");Or with configuration via the builder:
FeatureflipClient client = FeatureflipClient.get("sdk-dev-abc123", FeatureFlagConfig.builder() .baseUrl("https://eval.featureflip.io") .streaming(true) .initTimeout(Duration.ofSeconds(30)) .build());Both forms perform the initial flag fetch and start background streaming or polling. If the initial fetch fails, initialization continues in the background.
waitForInitialization()
Section titled “waitForInitialization()”Blocks until the client has loaded flag data. Throws FeatureFlagInitializationException if the timeout expires.
client.waitForInitialization();isInitialized()
Section titled “isInitialized()”boolean ready = client.isInitialized();Returns true once the client has successfully loaded flag data.
Builder methods
Section titled “Builder methods”| Method | Parameter | Description |
|---|---|---|
baseUrl() | String | Set the Evaluation API base URL |
connectTimeout() | Duration | Set the connection timeout |
readTimeout() | Duration | Set the read timeout |
streaming() | boolean | Enable or disable SSE streaming |
pollInterval() | Duration | Set the polling interval |
flushInterval() | Duration | Set the event flush interval |
flushBatchSize() | int | Set the maximum events per batch |
initTimeout() | Duration | Set the initialization timeout |
build() | — | Build the client and start initialization |
Evaluating Flags
Section titled “Evaluating Flags”All evaluation methods accept a flag key, an evaluation context, and a default value. If the flag is not found or an error occurs, the default value is returned.
boolVariation()
Section titled “boolVariation()”EvaluationContext context = EvaluationContext.builder("u-123").build();boolean enabled = client.boolVariation("dark-mode", context, false);Signature:
boolean boolVariation(String key, EvaluationContext context, boolean defaultValue)stringVariation()
Section titled “stringVariation()”String plan = client.stringVariation("pricing-tier", context, "free");Signature:
String stringVariation(String key, EvaluationContext context, String defaultValue)intVariation()
Section titled “intVariation()”int limit = client.intVariation("rate-limit", context, 100);Signature:
int intVariation(String key, EvaluationContext context, int defaultValue)doubleVariation()
Section titled “doubleVariation()”double ratio = client.doubleVariation("rollout-ratio", context, 0.5);Signature:
double doubleVariation(String key, EvaluationContext context, double defaultValue)jsonVariation()
Section titled “jsonVariation()”Deserializes a JSON flag value to the specified class using Jackson:
BannerConfig banner = client.jsonVariation( "banner-config", context, new BannerConfig("Welcome", "#000"), BannerConfig.class);Signature:
<T> T jsonVariation(String key, EvaluationContext context, T defaultValue, Class<T> type)Evaluation Details
Section titled “Evaluation Details”Detail methods return an EvaluationDetail<T> object with the value and metadata about the decision.
boolVariationDetail()
Section titled “boolVariationDetail()”EvaluationDetail<Boolean> detail = client.boolVariationDetail("dark-mode", context, false);
System.out.println(detail.getValue()); // trueSystem.out.println(detail.getReason()); // RULE_MATCHSystem.out.println(detail.getRuleId()); // "rule-abc"System.out.println(detail.getErrorMessage()); // nullstringVariationDetail()
Section titled “stringVariationDetail()”EvaluationDetail<String> detail = client.stringVariationDetail("pricing-tier", context, "free");intVariationDetail()
Section titled “intVariationDetail()”EvaluationDetail<Integer> detail = client.intVariationDetail("rate-limit", context, 100);doubleVariationDetail()
Section titled “doubleVariationDetail()”EvaluationDetail<Double> detail = client.doubleVariationDetail("rollout-ratio", context, 0.5);EvaluationDetail<T>
Section titled “EvaluationDetail<T>”| Method | Return type | Description |
|---|---|---|
getValue() | T | The evaluated flag value |
getReason() | EvaluationReason | Why this value was returned |
getRuleId() | String | The ID of the matched targeting rule (may be null) |
getErrorMessage() | String | Error message if evaluation failed (may be null) |
EvaluationReason
Section titled “EvaluationReason”| Value | Description |
|---|---|
RULE_MATCH | A targeting rule matched the context |
FALLTHROUGH | No rules matched; the fallthrough variation was served |
FLAG_DISABLED | The flag is disabled; the off variation was served |
FLAG_NOT_FOUND | The flag key does not exist |
ERROR | An error occurred during evaluation |
Evaluation Context
Section titled “Evaluation Context”The EvaluationContext class uses a builder pattern with a required user ID. Custom attributes feed into targeting rules, and the user ID anchors percentage rollouts so each user gets a stable bucket assignment:
EvaluationContext context = EvaluationContext.builder("u-123") .set("country", "US") .set("plan", "pro") .build();Builder methods
Section titled “Builder methods”| Method | Parameter | Description |
|---|---|---|
builder() | String userId | Create a builder with the required user ID |
set() | String key, Object value | Add a custom attribute (case-insensitive keys) |
build() | — | Build the immutable context |
Accessing attributes
Section titled “Accessing attributes”String userId = context.getUserId(); // "u-123"Object plan = context.getAttribute("plan"); // "pro"getAttribute() checks custom attributes first, then falls back to built-in properties (userId, user_id).
Event Tracking
Section titled “Event Tracking”track()
Section titled “track()”Records a custom analytics event.
Map<String, Object> metadata = Map.of( "plan", "pro", "amount", 29.99);client.track("purchase-completed", context, metadata);Signature:
void track(String eventName, EvaluationContext context, Map<String, Object> metadata)Events are batched and flushed automatically based on flushInterval and flushBatchSize.
flush()
Section titled “flush()”Forces all buffered events to be sent to the server immediately.
client.flush();Cleanup
Section titled “Cleanup”The client implements AutoCloseable. Use try-with-resources for automatic cleanup:
try (FeatureflipClient client = FeatureflipClient.get("sdk-key")) { boolean enabled = client.boolVariation("dark-mode", context, false);}// client.close() is called automaticallyclose()
Section titled “close()”Stops SSE streaming or polling, flushes remaining events, and shuts down the executor service (with a 5-second graceful timeout). Safe to call multiple times.
client.close();Testing
Section titled “Testing”FeatureflipClient.forTesting()
Section titled “FeatureflipClient.forTesting()”Creates a test client with fixed flag values. No network calls are made and no background threads are started.
FeatureflipClient client = FeatureflipClient.forTesting(Map.of( "dark-mode", true, "pricing-tier", "enterprise", "rate-limit", 500));
EvaluationContext ctx = EvaluationContext.builder("test-user").build();client.boolVariation("dark-mode", ctx, false); // trueclient.stringVariation("pricing-tier", ctx, "free"); // "enterprise"Signature:
static FeatureflipClient forTesting(Map<String, Object> values)The test client is fully initialized immediately. Flag values are returned regardless of the evaluation context. The values map is copied and immutable.
FeatureFlagInitializationException
Section titled “FeatureFlagInitializationException”Thrown by waitForInitialization() when the client cannot load flag data within the configured timeout. Also thrown if initialization is interrupted.
try { client.waitForInitialization();} catch (FeatureFlagInitializationException e) { // Handle initialization failure log.error("Failed to initialize: {}", e.getMessage());}Dependencies
Section titled “Dependencies”The SDK uses the following runtime dependencies:
| Library | Purpose |
|---|---|
| OkHttp 4.x | HTTP client and SSE |
| Jackson 2.x | JSON serialization/deserialization |
| SLF4J 2.x | Logging facade |
See also
Section titled “See also”- Java Quickstart — get started in under 5 minutes
- Targeting & Segments — control which users see which variation
- Rollout Strategies — gradual percentage-based rollouts
- Evaluation API — REST API reference
- SDK Overview — compare server-side and client-side SDKs