Quarkus OpenFeature
This extension integrates the OpenFeature SDK with Quarkus, providing feature flag evaluation as a CDI service.
Installation
Select an OpenFeature provider and add its extension to your project.
You don’t need to explicitly add a dependency on io.quarkiverse.openfeature:quarkus-openfeature, it is included transitively.
Available providers:
- flagd provider
-
Connects to a flagd instance for dynamic feature flag evaluation. Flags are evaluated in-process using flag definitions synced from flagd over gRPC.
<dependency> <groupId>io.quarkiverse.openfeature</groupId> <artifactId>quarkus-openfeature-flagd</artifactId> <version>1.0.0.Alpha1</version> </dependency> - Flipt provider
-
Connects to a Flipt instance for dynamic feature flag evaluation. Flags are evaluated in-process using flag definitions synced from Flipt over HTTP streaming and a compiled WebAssembly engine.
<dependency> <groupId>io.quarkiverse.openfeature</groupId> <artifactId>quarkus-openfeature-flipt</artifactId> <version>1.0.0.Alpha1</version> </dependency> - GO Feature Flag provider
-
Connects to a GO Feature Flag relay proxy for dynamic feature flag evaluation. Flags are evaluated in-process using flag definitions synced from the relay proxy over HTTP and SSE, and a compiled WebAssembly engine.
<dependency> <groupId>io.quarkiverse.openfeature</groupId> <artifactId>quarkus-openfeature-gofeatureflag</artifactId> <version>1.0.0.Alpha1</version> </dependency> - Unleash provider
-
Connects to an Unleash instance for dynamic feature flag evaluation. Flags are evaluated in-process using flag definitions polled from Unleash over HTTP and the native Yggdrasil engine.
<dependency> <groupId>io.quarkiverse.openfeature</groupId> <artifactId>quarkus-openfeature-unleash</artifactId> <version>1.0.0.Alpha1</version> </dependency> - Runtime configuration provider
-
Reads flag values from Quarkus runtime configuration. Values can be overridden at runtime using environment variables, system properties, or config files.
<dependency> <groupId>io.quarkiverse.openfeature</groupId> <artifactId>quarkus-openfeature-runtime-config</artifactId> <version>1.0.0.Alpha1</version> </dependency> - Build-time configuration provider
-
Reads flag values from Quarkus build-time configuration. They are fixed during build and cannot be changed at runtime.
<dependency> <groupId>io.quarkiverse.openfeature</groupId> <artifactId>quarkus-openfeature-buildtime-config</artifactId> <version>1.0.0.Alpha1</version> </dependency>
Provider implementation
This extension does not use the upstream OpenFeature providers. Instead, it implements its own providers that use Vert.x for non-blocking synchronization with the feature flag server and delegate flag evaluation to the same in-process engines (flagd-core, Yggdrasil, etc.).
The upstream providers often rely on blocking I/O and heavyweight dependencies (such as OkHttp), conflicting with Quarkus’s reactive core. By implementing providers directly on top of Vert.x, this extension avoids extra dependencies, integrates with the Quarkus TLS registry and credential providers, and guarantees that flag evaluations never perform I/O — making them safe to call from any thread, including the Vert.x event loop.
Usage
Inject the OpenFeature Client and evaluate feature flags:
import dev.openfeature.sdk.Client;
@ApplicationScoped
public class MyService {
@Inject
Client client;
public String greet() {
if (client.getBooleanValue("new-greeting", false)) {
return "Hello, OpenFeature!";
}
return "Hello!";
}
}
The Client supports boolean, string, integer, double, and object flag evaluations:
boolean enabled = client.getBooleanValue("feature-enabled", false);
String variant = client.getStringValue("ui-variant", "default");
int maxRetries = client.getIntegerValue("max-retries", 3);
double threshold = client.getDoubleValue("threshold", 0.5);
Value config = client.getObjectValue("complex-config", new Value());
Object evaluation returns structured data whose richness depends on the provider.
Providers like flagd support full JSON structures, while the configuration-based providers return the raw config value as a string wrapped in a Value.
|
It is safe to call the Client methods on a non-blockable thread (such as Vert.x event loop), because this extension guarantees feature flags are evaluated purely in memory; no I/O occurs.
You can also inject OpenFeatureAPI directly if you need lower-level access.
Provider selection
If your application has exactly one provider extension on the classpath, it is used automatically for the default domain. No configuration is needed.
If you have multiple provider extensions, you must configure which provider(s) to use:
quarkus.openfeature.provider=runtime-config
You can also combine multiple providers. The first provider in the list that has a value for a given flag wins:
quarkus.openfeature.provider=runtime-config,buildtime-config
Multiple domains
OpenFeature supports the concept of domains — named scopes that can use different providers. For example, you might use one provider for payment-related flags and another for experimentation.
Named domains must always declare their provider explicitly, even if only one provider extension is on the classpath. They are configured under quoted keys:
# Default domain
quarkus.openfeature.provider=runtime-config
quarkus.openfeature.runtime.my-flag=true
# Named domain "experimentation"
quarkus.openfeature."experimentation".provider=buildtime-config
quarkus.openfeature."experimentation".buildtime.experiment-a=true
quarkus.openfeature."experimentation".buildtime.experiment-b=false
Inject domain-specific clients using the @Identifier qualifier:
import dev.openfeature.sdk.Client;
import io.smallrye.common.annotation.Identifier;
@ApplicationScoped
public class MyService {
@Inject
Client defaultClient; (1)
@Inject
@Identifier("experimentation")
Client experimentationClient; (2)
}
| 1 | The default domain client. |
| 2 | A client for the "experimentation" domain, using its own provider configuration. |
Each domain has its own provider and flag configuration. Flags from one domain are not visible in another.
Provider initialization
Providers that connect to a remote service (such as flagd or Unleash) initialize asynchronously: they open a connection, receive the initial set of flag definitions, and only then become ready. Until a provider is ready, flag evaluations return default values.
By default, this extension blocks application startup until all providers are ready in the dev and test launch modes, but lets them initialize asynchronously in the production launch mode. This means:
-
In development and testing, flags are always available when your first request arrives. This avoids confusing "default value" responses during development.
-
In production, the application starts immediately and begins serving traffic while providers connect. This is typically what you want for rolling deployments and health checks.
You can override this behavior explicitly:
# Always block startup until providers are ready
quarkus.openfeature.await-providers=true
# Never block startup, even in dev/test mode
quarkus.openfeature.await-providers=false
Testing
The quarkus-openfeature-junit artifact provides a @TestFlag annotation for overriding feature flag values in tests.
See Testing for details.
Extension Configuration Reference
Configuration property fixed at build time - All other configuration properties are overridable at runtime
Configuration property |
Type |
Default |
|---|---|---|
Whether to wait for providers to be ready before the application starts. When Environment variable: |
|
|
Which provider(s) to use for this domain. Environment variable: |
list of string |