Quarkus OpenFeature

This extension integrates the OpenFeature SDK, version 1.23.0, with Quarkus, providing feature flag evaluation as a CDI service.

OpenFeature is an open specification for feature flag management. It provides a vendor-neutral API, so your application code doesn’t depend on a specific feature flag provider. You can switch between providers (such as flagd, Unleash, or others) without changing your application code.

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</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</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</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</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</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</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, long, 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);
long maxBytes = client.getLongValue("max-bytes", 10_000_000_000L);
double threshold = client.getDoubleValue("threshold", 0.5);
Value config = client.getObjectValue("complex-config", new Value());
Only the configuration-based providers resolve long flags natively. The remote-sync providers fall back to the OpenFeature SDK default, which resolves the flag as a double, so values outside the range of [-(253 - 1), 253 - 1] result in an error.

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.

The Flipt and GO Feature Flag providers evaluate in a pool of WebAssembly engine instances. When the pool has grown to its maximum size and all instances are in use, an evaluation waits up to 20 milliseconds for an instance to become available, which blocks the calling thread. Size the pool for the application’s evaluation concurrency to avoid that; see "Evaluation concurrency" on the Flipt and GO Feature Flag pages.

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

A domain that combines several providers does not report provider state and does not emit provider events properly. Such domain is registered with the OpenFeature SDK as a multi-provider, which is an experimental feature of the OpenFeature Java SDK.

Flag evaluation is unaffected: each provider keeps evaluating the flag definitions it synchronized last, exactly as it does in a single-provider domain.

If you rely on observing provider state or provider events, use one provider per domain.

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. This is how a domain comes into existence: the extension knows about a named domain only because quarkus.openfeature."<domain>".provider is configured for it. Provider-specific configuration such as quarkus.openfeature."experimentation".flagd.url does not declare the domain by itself, so if you set only that, no Client is produced for the domain and injecting @Identifier("experimentation") Client fails at build time with an unsatisfied dependency.

Domains 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

A flag server that is down never fails the application. A provider that cannot connect is given 15 seconds, after which the error is logged, startup proceeds with that provider in the ERROR state, and its flags fall back to their default values until a reconnect succeeds. A misconfigured provider is the one exception: a URL the provider cannot use at all is reported at startup and does fail it, because no reconnect can fix it.

Application health

This extension deliberately does not contribute a health check for provider state. A flag server outage is not an application outage: the provider keeps evaluating the flag definitions it synchronized last, so the application continues to serve the values it was serving before the outage. If a flag definition was never synchronized (the provider has not become ready yet, or the flag does not exist), the default value is used.

The default value is also used for all flags when the server rejects the provider’s credentials, which puts the provider in the FATAL state. This may happen when a token is rotated on the server but not in the application. Note that when the provider enters the FATAL state, it stays there until the application restarts.

The application may observe provider state by registering a provider event handler through the OpenFeature SDK.

Testing

The quarkus-openfeature-junit artifact provides a @TestFlag annotation for overriding feature flag values in @QuarkusTest 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 auto, providers are awaited in the dev and test launch modes but not in production. When true, startup blocks until all providers have received their initial flag data. When false, providers initialize asynchronously and return default values until ready.

Environment variable: QUARKUS_OPENFEATURE_AWAIT_PROVIDERS

auto, true, false

auto

quarkus.openfeature."domain-name".provider

Which provider(s) to use for this domain.

Environment variable: QUARKUS_OPENFEATURE_PROVIDER

list of string