Java (OpenFeature)
OpenFeature is a CNCF standard for feature flag evaluation.
com.quonfig:openfeature-server-java is a thin provider that wraps the native
com.quonfig:sdk-java SDK and implements the OpenFeature Java server-side FeatureProvider
interface.
Install
Gradle (Kotlin DSL):
dependencies {
implementation("com.quonfig:openfeature-server-java:1.2.0")
implementation("dev.openfeature:sdk:1.20.2")
}
Maven:
<dependency>
<groupId>com.quonfig</groupId>
<artifactId>openfeature-server-java</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>dev.openfeature</groupId>
<artifactId>sdk</artifactId>
<version>1.20.2</version>
</dependency>
The provider brings in com.quonfig:sdk-java as a transitive dependency.
Initialize
import com.quonfig.openfeature.QuonfigProvider;
import com.quonfig.openfeature.QuonfigProviderOptions;
import dev.openfeature.sdk.Client;
import dev.openfeature.sdk.OpenFeatureAPI;
QuonfigProvider provider = new QuonfigProvider(
QuonfigProviderOptions.builder()
.sdkKey("qf_sk_production_...")
.build());
// Registers the provider and blocks until initialize() completes.
OpenFeatureAPI.getInstance().setProviderAndWait(provider);
Client client = OpenFeatureAPI.getInstance().getClient();
Evaluate flags
import dev.openfeature.sdk.Value;
// Boolean flag
boolean enabled = client.getBooleanValue("checkout-v2", false);
// String config
String welcome = client.getStringValue("welcome-message", "Hello!");
// Integer config (Quonfig stores 64-bit ints; narrowed to int)
int seats = client.getIntegerValue("max-seats", 5);
// Double config
double limit = client.getDoubleValue("upload-limit-gb", 1.0);
// Object config (JSON or string_list)
Value allowedPlans = client.getObjectValue("allowed-plans", new Value());
Use the get*Details variants (for example getBooleanDetails) to read the reason, variant
and error code alongside the value.
Evaluation context
Pass per-request context as an EvaluationContext:
import dev.openfeature.sdk.MutableContext;
MutableContext ctx = new MutableContext("user-123") // targetingKey -> user.id by default
.add("user.plan", "pro")
.add("org.tier", "enterprise");
boolean isPro = client.getBooleanValue("pro-feature", false, ctx);
OpenFeature context is flat; Quonfig context is namespace-nested. The provider maps between them using dot-notation:
| OpenFeature key | Quonfig namespace | Quonfig property |
|---|---|---|
targetingKey | user | id (configurable) |
"user.email" | user | email |
"org.tier" | org | tier |
"country" (no dot) | "" (default) | country |
"user.ip.address" | user | ip.address (split on first dot) |
Custom targetingKey mapping
QuonfigProvider provider = new QuonfigProvider(
QuonfigProviderOptions.builder()
.sdkKey("qf_sk_...")
.targetingKeyMapping("org.id")
.build());
Local / offline mode
Use datadir instead of sdkKey to evaluate against a Quonfig workspace on disk. No
network calls are made.
QuonfigProvider provider = new QuonfigProvider(
QuonfigProviderOptions.builder()
.datadir("/path/to/workspace")
.environment("Production")
.build());
Lifecycle events
The provider emits standard OpenFeature provider events:
PROVIDER_READYafterinitialize()succeeds.PROVIDER_ERRORif initialization fails (the exception is also rethrown frominitialize()).PROVIDER_CONFIGURATION_CHANGEDwhen the SDK receives a config update after the provider is ready.PROVIDER_STALEis not emitted.
OpenFeatureAPI.getInstance().onProviderReady(details ->
System.out.println("Quonfig provider ready"));
OpenFeatureAPI.getInstance().onProviderConfigurationChanged(details ->
System.out.println("Config updated"));
Reasons and errors
Evaluation never throws. On an error, or when the value is null, you get your default value back, with the error code and message set on the evaluation details.
- Reasons pass through from the SDK:
STATIC,TARGETING_MATCH,SPLIT,DEFAULT(anything else becomesUNKNOWN). - A missing flag returns reason
DEFAULTwith error codeFLAG_NOT_FOUND. Other errors (TYPE_MISMATCH,GENERAL) return reasonERROR. - Calls made before the provider is initialized return the default with
PROVIDER_NOT_READY.
Native SDK escape hatch
provider.getClient() returns the underlying com.quonfig.sdk.Quonfig client for features
not available in OpenFeature. It returns null until the provider has been initialized.
import com.quonfig.sdk.Quonfig;
import java.time.Duration;
import org.slf4j.event.Level;
Quonfig q = provider.getClient();
// Parsed duration values
Duration ttl = q.getDuration("cache.ttl", Duration.ZERO);
// Log level integration
boolean shouldLog = q.shouldLog("com.example.Auth", Level.DEBUG);
// List all config keys
java.util.Set<String> keys = q.keys();
What you lose vs. the native SDK
OpenFeature is designed for feature flags, not general configuration. Some Quonfig
features require the native com.quonfig:sdk-java SDK directly:
- Log levels --
shouldLog()andgetLogLevel()are native-only; useprovider.getClient(). string_listconfigs -- returned as a listValueviagetObjectValue().durationconfigs -- returned as the ISO 8601 string (e.g."PT90S") viagetStringValue(); usegetClient().getDuration()for ajava.time.Duration.bytesconfigs -- not accessible (no binary type in OpenFeature).keys()and raw config access -- useprovider.getClient().- Context keys use dot-notation -- pass
"user.email", not nested objects. targetingKeymaps touser.idby default -- configuretargetingKeyMappingif different.