Quarkus OpenFeature

This extension integrates the OpenFeature SDK 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.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 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