Quarkus Feature Flags

This projects aims to provide a lightweight and extensible feature flag Quarkus extension. More specifically, it provides:

  • An API to access feature flags.

  • An SPI to provide flags and externalize the computation of a flag value.

  • Several built-in flag providers

    • Leverage Quarkus config to define feature flags,

    • In-memory repository (useful for testing and dynamic registration).

  • Hibernate ORM module, where feature flags are mapped from an annotated entity and are automatically loaded from the database.

  • Security module, so that it’s possible to evaluate flags based on the current SecurityIdentity.

  • Qute module, so that it’s possible to use the flags directly in templates.

  • Cron module with a flag evaluator that matches a specific CRON expression.

What is a feature flag?

A feature flag makes it possible to turn on/off or configure a specific functionality in your application. It’s also referred to as toggles or switches. In this extension, a feature flag is represented by the io.quarkiverse.flags.Flag interface. It refers to a specific feature with Flag#feature() and provides several convenient methods to compute the current value. The value of a feature flag can be represented as boolean, string or integer. A flag can also define metadata with Flag#metadata(). Metadata enable further configuration which can be leveraged in SPI.

Accessing feature flags

The io.quarkiverse.flags.Flags represents a central point to access feature flags. A CDI bean that implements Flags is automatically registered.

import io.quarkiverse.flags.Flags;
import io.quarkiverse.flags.Feature;
import jakarta.inject.Inject;

@ApplicanScoped
public class MyService {

    @Inject
    Flags flags; (1)

    @Feature("my-feature-alpha")
    Flag alpha; (2)

    void serviceCall1() {
        if (flags.isEnabled("my-feature-alpha")) { (3)
            // Business logic that is executed only if the boolean representation of the "my-feature-alpha" value evaluates to true
        }
    }

    void serviceCall2() {
        if (alpha.isEnabled()) { (4)
            // Business logic that is executed only if the boolean representation of the "my-feature-alpha" value evaluates to true
        }
    }
}
1 You can inject Flags in any CDI bean.
2 You can also inject a Flag for the given feature. The injected flag is never null but subsequent computations can throw NoSuchElementException if no such feature flag exists.
3 Flags#isEnabled(String) is a shortcut for findAndAwait(feature).orElseThrow().isEnabled().
4 Flag#isEnabled() computes the current value and returns its boolean representation. It blocks the caller thread.
If the quarkus-flags-qute module is present, you can access feature flags directly in templates, e.g. {#if flag:enabled('my-feature-alpha')}…​{/if}

Flags provider SPI

The feature flags are collected at runtime. More specifically, the extension injects all CDI beans that implement io.quarkiverse.flags.spi.FlagProvider and calls the FlagProvider#getFlags() method. The result must not contain flags with duplicate feature names. A flag from a provider with higher priority takes precedence and overrides flags with the same Flag#feature() from providers with lower priority.

The core extension provides two built-in flag providers. The first one is built on top of Quarkus config service. It allows users to configure build time and runtime feature flags. The build time feature flags are configured through properties with prefix quarkus.flags.build and the initial value is fixed at build time. The runtime feature flags are configured through properties with prefix quarkus.flags.runtime and the initial value is determined when the application starts.

A feature flag defined in Quarkus config
quarkus.flags.build."my-feature-alpha".value=true (1)
quarkus.flags.runtime."my-feature-bravo".value=false (2)
quarkus.flags.runtime."my-feature-bravo".meta.foo=bar (3)
1 Define a build time flag for feature my-feature-alpha with initial value true.
2 Define a runtime flag for feature my-feature-bravo with value false.
3 The metadata of my-feature-bravo will contain key "foo" with value "bar".

The other built-in flag provider provides an in-memory repository that can be useful for testing and dynamic registration.

import io.quarkiverse.flags.InMemoryFlagProvider;
import jakarta.inject.Inject;

@ApplicanScoped
public class MyService {

    @Inject
    InMemoryFlagProvider provider; (1)

    void addFlag() {
        provider.addFlag(Flag.builder("my-feature-alpha")
                .setEnabled(true).build()); (2)
    }

    void removeFlag() {
        provider.removeFlag("my-feature-alpha"); (3)
    }
}
1 You can inject InMemoryFlagProvider in any CDI bean.
2 It’s possible to register a new feature flag at any time.
3 You can also remove the flag.

Flag evaluator SPI

The io.quarkiverse.flags.spi.FlagEvaluator SPI makes it possible to externalize the computation of the current value of a feature flag. This allows for more dynamic evaluation logic based on some external state, such as the current SecurityIdentity. Flag evaluators must be CDI beans. Qualifiers are ignored. @Dependent beans are reused.

By default, a Flag can reference one FlagEvaluator in its metadata with a key evaluator. This evaluator is automatically used to compute the current value for any Flag produced by means of Flag.Builder (i.e. created by Flag#builder(String)).

Time span flag evaluator

The core extension provides a built-in flag evaluator - io.quarkiverse.flags.TimeSpanFlagEvaluator that evaluates a flag based on the current date-time obtained from the system clock in the default time-zone.

A feature flag defined in Quarkus config
quarkus.flags.runtime."my-feature-alpha".value=true
quarkus.flags.runtime."my-feature-alpha".meta.evaluator=quarkus.time-span (1)
quarkus.flags.runtime."my-feature-alpha".meta.start-time=2001-01-01T10:15:30+01:00[Europe/Prague] (2)
1 The TimeSpanFlagEvaluator is used to compute the current value of the feature flag.
2 The current date-time must be after the specified start-time. The java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME is used to parse the start-time value.

Composite flag evaluator

Sometimes it might be useful to combine some evaluators to compute the value of a flag. The core extension provides io.quarkiverse.flags.CompositeFlagEvaluator that evaluates a flag with the specified sub-evaluators.

A feature flag defined in Quarkus config
quarkus.flags.runtime."my-feature-alpha".value=true
quarkus.flags.runtime."my-feature-alpha".meta.evaluator=quarkus.composite (1)
quarkus.flags.runtime."my-feature-alpha".meta.sub-evaluators=quarkus.time-span, quarkus.security.identity (2)
quarkus.flags.runtime."my-feature-alpha".meta.start-time=2026-01-01T12:00:00+01:00[Europe/Prague] (3)
quarkus.flags.runtime."my-feature-alpha".meta.roles-allowed=admin (4)
1 The CompositeFlagEvaluator is used to compute the current value of the feature flag.
2 The value of sub-evaluators represents a comma-separated list of sub-evaluator identifiers. They are executed in the specified order. In this particular case, first the TimeSpanFlagEvaluator is executed and then the SecurityIdentityFlagEvaluator.
3 The current date-time must be after the specified start-time.
4 The current user must have the role admin.

Extension configuration reference

Configuration property fixed at build time - All other configuration properties are overridable at runtime

Configuration property

Type

Default

The value.

Environment variable: QUARKUS_FLAGS_BUILD__FLAG_NAME__VALUE

string

true

The metadata.

Environment variable: QUARKUS_FLAGS_BUILD__FLAG_NAME__META__META_

Map<String,String>

The value.

Environment variable: QUARKUS_FLAGS_RUNTIME__FLAG_NAME__VALUE

string

true

The metadata.

Environment variable: QUARKUS_FLAGS_RUNTIME__FLAG_NAME__META__META_

Map<String,String>