Quarkus Feature Flags: A Lightweight and Extensible Solution

quarkus

Discover Quarkus Feature Flags, a lightweight and extensible Quarkiverse extension for managing application functionality. Learn about its APIs, built-in providers (config, in-memory, Hibernate ORM, Qute, Security, CRON), and dynamic evaluators for flexible control over features, enabling gradual rollouts and conditional access.

Feature flags are a proven and popular technique that enables the dynamic control and configuration of specific functionalities within an application. Also known as toggles or switches, feature flags facilitate various strategies:

  • Killer Switches: Instantly disable problematic features.
  • Gradual Rollouts: Deliver new features incrementally to small user groups (e.g., beta testers).
  • Permission Flags: Control user access to different features.

This article introduces Quarkus Feature Flags, a Quarkiverse project designed to provide a lightweight and extensible feature flag extension for Quarkus. It aims to integrate seamlessly within the Quarkus ecosystem, offering a flexible option rather than replacing robust solutions like OpenFeature and Unleash.

Core Capabilities

The Quarkus Feature Flags extension provides:

  • A blocking/non-blocking API for accessing feature flags.
  • A non-blocking SPI for providing flags and externalizing the computation of flag values.
  • Several built-in flag providers, including:
    • Quarkus configuration for defining feature flags.
    • An in-memory flag repository, ideal for testing and dynamic registration.
    • Hibernate ORM module, mapping flags from annotated entities loaded directly from the database.
    • Security module, enabling flag evaluation based on the current SecurityIdentity.
    • Qute module, allowing direct use of flags in templates.
    • CRON module, providing a flag evaluator that matches specific CRON expressions.

The Flag Interface

In this extension, a feature flag is represented by the io.quarkiverse.flags.Flag interface. It identifies a specific feature using a string identifier and offers methods to compute its current value. Flag values can be boolean, string, or integer. Only one flag can exist for a given feature at any time, and flags can expose metadata leveraging the SPI.

Simple Example: Defining a Flag in application.properties

To define a feature flag, my-feature-alpha, with an initial value of true:

quarkus.flags.runtime."my-feature-alpha".value=true

A runtime feature flag can be overridden dynamically using system properties or environment variables. Alternatively, you can define a build-time flag (e.g., quarkus.flags.build."my-feature-alpha".value=true), but its value cannot be changed at runtime.

The io.quarkiverse.flags.Flags interface serves as the central access point for feature flags. Quarkus automatically registers a CDI bean implementing Flags, allowing for simple injection:

package org.example;

import io.quarkiverse.flags.Flags;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

@ApplicationScoped
public class MyService {

    @Inject
    Flags flags;

    void call() {
        if (flags.isEnabled("my-feature-alpha")) {
            // This business logic is executed only if "my-feature-alpha" evaluates to true
        }
    }
}

Accessing Flags in Qute Templates

Feature flags can also be accessed directly within your Qute UI templates. First, ensure you've added the io.quarkiverse.flags:quarkus-flags-qute extension to your build file. Then, you can use the flag: namespace:

<!DOCTYPE html>
<html>
<head>
   <title>Flags</title>
</head>
<body>
   <h1>Hello - Quarkus Club 2025</h1>
   {#if flag:enabled('my-feature-alpha')}
     <p>Feature alpha is enabled!</p>
   {/if}
</body>
</html>

The flag: namespace provides other useful properties and methods for template integration.

Built-in Flag Providers

While defining flags in Quarkus config is convenient, the extension offers more flexible providers.

In-Memory Flag Provider

The io.quarkiverse.flags.InMemoryFlagProvider acts as an in-memory repository, useful for testing and dynamic registration. This provider has higher priority and will override flags defined in the Quarkus configuration.

import io.quarkiverse.flags.BooleanValue;
import io.quarkiverse.flags.Flag;
import io.quarkiverse.flags.InMemoryFlagProvider;
import io.quarkus.runtime.Startup;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.util.concurrent.atomic.AtomicBoolean;

@ApplicationScoped
public class MyInitService {

   AtomicBoolean alpha = new AtomicBoolean();

   @Inject
   InMemoryFlagProvider provider; // Inject InMemoryFlagProvider to add/remove flags.

   @Startup // This method is automatically executed at application startup.
   void addFlags() {
      provider.addFlag(Flag.builder("my-feature-alpha")
         // The current value of my-feature-alpha is calculated from MyInitService#alpha
         .setCompute(ctx -> BooleanValue.from(alpha.get()))
         .build());
   }
}

This allows for easy control over the my-feature-alpha flag's value.

Hibernate ORM Integration

For persisting feature flags in an external system, the quarkus-flags-hibernate-orm extension provides seamless integration with Hibernate ORM. It automatically discovers JPA entities annotated with @io.quarkiverse.flags.hibernate.common.FlagDefinition and generates a flag provider that loads flags from the database. An example mapping:

import jakarta.persistence.Entity;

import io.quarkiverse.flags.hibernate.common.FlagDefinition;
import io.quarkiverse.flags.hibernate.common.FlagFeature;
import io.quarkiverse.flags.hibernate.common.FlagValue;
import io.quarkus.hibernate.orm.panache.PanacheEntity;

@FlagDefinition // Marks a flag definition entity.
@Entity
public class MyFlag extends PanacheEntity {

    @FlagFeature // Defines the feature name of a feature flag.
    public String feature;

    @FlagValue // Defines the value of a feature flag.
    public String value;

}

At runtime, the extension collects feature flags by injecting all CDI beans that implement io.quarkiverse.flags.spi.FlagProvider and invoking their FlagProvider#getFlags() method. This design allows for easy implementation of custom providers.

Flag Evaluators

In real-world applications, dynamic evaluation logic based on application state (e.g., current user, current time) is often required. The io.quarkiverse.flags.spi.FlagEvaluator SPI enables externalizing the computation of a feature flag's current value. Flag evaluators must be CDI beans. By default, a flag can reference a FlagEvaluator in its metadata with the key evaluator, which is then used to compute the value for flags created via Flag.Builder.

Several built-in evaluators are available:

Current Time (TimeSpanFlagEvaluator)

The io.quarkiverse.flags.TimeSpanFlagEvaluator evaluates a flag based on the system clock's current date and time in the default time zone. The flag is active if the current time falls after the specified start-time (exclusive) and before an optional end-time (exclusive). The java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME is used for parsing time values.

quarkus.flags.runtime."my-feature-alpha".meta.evaluator=quarkus.time-span
quarkus.flags.runtime."my-feature-alpha".meta.start-time=2001-01-01T10:15:30+01:00[Europe/Prague]

In this configuration, the quarkus.time-span evaluator is assigned, and the flag my-feature-alpha (which defaults to true) becomes active after 2001-01-01T10:15:30+01:00[Europe/Prague]. No end-time is specified, meaning there's no upper bound for the time interval.

Current User (SecurityIdentityFlagEvaluator, UsernameRolloutFlagEvaluator)

From the quarkus-flags-security extension, the SecurityIdentityFlagEvaluator computes a flag's value based on the current SecurityIdentity.

quarkus.flags.runtime."my-feature-alpha".meta.evaluator=quarkus.security.identity
quarkus.flags.runtime."my-feature-alpha".meta.authenticated=true
quarkus.flags.runtime."my-feature-alpha".meta.roles-allowed=foo,bar

Here, the quarkus.security.identity evaluator is used. The flag (defaulting to true) is enabled only if the current user is authenticated AND possesses either the foo or bar role.

The UsernameRolloutFlagEvaluator, on the other hand, implements a simple percentage-based rollout strategy, consistent with a numerical representation of the current username, facilitating gradual rollouts:

quarkus.flags.runtime."my-feature-alpha".meta.evaluator=quarkus.security.username-rollout
quarkus.flags.runtime."my-feature-alpha".meta.rollout-percentage=20

This configuration enables the flag for 20% of users.

CRON-Based Evaluation (CronFlagEvaluator)

The quarkus-flags-cron extension provides the CronFlagEvaluator, which computes a flag's value based on the current date-time and a configured CRON expression. By default, it uses Unix/crontab syntax, but Cron4j, Quartz, and Spring syntaxes are also supported (refer to documentation).

quarkus.flags.runtime."my-feature-alpha".meta.evaluator=quarkus.cron
quarkus.flags.runtime."my-feature-alpha".meta.expression=* * * * mon

In this example, the quarkus.cron evaluator is assigned, and the flag (defaulting to true) will be enabled every Monday, matching the given CRON expression.

The Case for Multiple Evaluators (CompositeFlagEvaluator)

Combining multiple evaluators to determine a flag's value can be highly beneficial. The core extension includes io.quarkiverse.flags.CompositeFlagEvaluator, which evaluates a flag using a sequence of specified sub-evaluators.

quarkus.flags.runtime."my-feature-alpha".meta.evaluator=quarkus.composite
quarkus.flags.runtime."my-feature-alpha".meta.sub-evaluators=quarkus.time-span, quarkus.security.identity
quarkus.flags.runtime."my-feature-alpha".meta.start-time=2026-01-01T12:00:00+01:00[Europe/Prague]
quarkus.flags.runtime."my-feature-alpha".meta.roles-allowed=admin

Here, the quarkus.composite evaluator is used. The sub-evaluators property lists quarkus.time-span and quarkus.security.identity, executed in that order. The flag (defaulting to true) will be active only if the current date-time is after 2026-01-01T12:00:00+01:00[Europe/Prague] AND the current user has the admin role.

Extensibility

Both flag providers and flag evaluators are CDI beans. This modular design allows developers to easily create custom providers or evaluators by implementing the FlagProvider or FlagEvaluator interfaces in their applications. Furthermore, CDI interceptors can be used to extend or decorate these components.

Conclusion

Quarkus Feature Flags is a lightweight and extensible extension designed to help developers build more flexible and adaptable applications. Feedback, feature requests, and contributions are highly encouraged. Feel free to create questions and issues on the GitHub repository.