Quarkus feature flags

Wait 5 sec.

Feature flags are a proven and popular technique.In essence, a feature flag makes it possible to turn on-off and/or configure a specific functionality in your application.It is also referred to as toggles or switches.There are many types of feature flags.You may have heard of a "killer switch" that simply disables a problematic feature instantly.Feature flags can be used to implement "gradual rollout" to deliver new features gradually to small groups of users (aka beta testers).Permission flags are used to control access for different users.And so on.However, that’s not the subject of this blogpost.We would like to introduce Quarkus Feature Flags - a Quarkiverse project that aims to provide a lightweight and extensible feature flags Quarkus extension.More specifically, it provides:A blocking/non-blocking API to access feature flags.A non-blocking SPI to provide flags and externalize the computation of a flag value.Several built-in flag providers:Quarkus config can be used to define feature flags,An in-memory flag repository which is useful for testing and dynamic registration of flags.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.Note that the goal of this extension is not to replace robust solutions such as OpenFeature and Unleash.Instead, we would like to offer a flexible option that integrates well with other parts of the Quarkus ecosystem.FlagIn this extension, a feature flag is represented by the io.quarkiverse.flags.Flag interface.It refers to a specific feature with a string identifier and provides several convenient methods to compute the current value.The value of a feature flag can be represented as boolean, string or integer.There can be only one flag for a given feature at a given time.A flag can also expose metadata which that can be leveraged in the SPI.Simple exampleLet’s start simple.We will create a flag for a feature called my-feature-alpha in the application.properties file.quarkus.flags.runtime."my-feature-alpha".value=true (1)1Define a runtime flag for feature my-feature-alpha with initial value true.A runtime feature flag can be overriden at runtime, e.g. using a system property or an environment variable. You can also define a flag fixed at build time, i.e. quarkus.flags.build."my-feature-alpha".value=true. However, its values ​​cannot be modified at runtime.The io.quarkiverse.flags.Flags interface represents the central point to access feature flags.The container registers a CDI bean that implements Flags automatically.Therefore, we will simply inject Flags and use one of its convenient methods.package org.example;import io.quarkiverse.flags.Flags;import jakarta.inject.Inject;@ApplicanScopedpublic class MyService { @Inject Flags flags; void call() { if (flags.isEnabled("my-feature-alpha")) { // This business logic is executed only if "my-feature-alpha" value evaluates to true } }}You can also access the flag in your UI built with Qute: Flags Hello - Quarkus Club 2025 {#if flag:enabled('my-feature-alpha')} (1) Feature alpha is enabled! {/if}1The flag: namespace provides other useful properties and methods.If you want to use the Qute integration in your application then you’ll need to add the io.quarkiverse.flags:quarkus-flags-qute extension to your build file first.Flag providersSo far we defined the flag in the Quarkus config.This is not very flexible.Let’s try some other built-in flag providers.We can use the io.quarkiverse.flags.InMemoryFlagProvider - an in-memory repository that can be useful for testing and dynamic registration.import io.quarkiverse.flags.BooleanValue;import io.quarkiverse.flags.Flag;import io.quarkiverse.flags.InMemoryFlagProvider;import io.quarkus.runtime.Startup;import jakarta.inject.Inject;@ApplicationScopedpublic class MyInitService { AtomicBoolean alpha = new AtomicBoolean(); @Inject InMemoryFlagProvider provider; (1) @Startup (2) void addFlags() { provider.addFlag(Flag.builder("my-feature-alpha") (3) .setCompute(ctx -> BooleanValue.from(alpha.get())) (4) .build()); }}1Inject InMemoryFlagProvider to add/remove flags.2This method is automatically executed at application startup.3The InMemoryFlagProvider has higher priority and overrides the flag provided in config.4The current value of my-feature-alpha is calculated from MyInitService#alpha.This way we can control the current value of my-feature-alpha easily.However, in real use cases we will probably need to persist the feature flags in an external system.The quarkus-flags-hibernate-orm extension provides integration with Hibernate ORM.It discovers all JPA entities annotated with @io.quarkiverse.flags.hibernate.common.FlagDefinition and generates a flag provider automatically.The generated provider simply loads all flags from the database.A mapping looks like: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 (1)@Entitypublic class MyFlag extends PanacheEntity { @FlagFeature (2) public String feature; @FlagValue (3) public String value;}1Marks a flag definition entity.2Defines the feature name of a feature flag.3Defines the value of a feature flag.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. You can easily implement your own provider.Flag evaluatorsIn real applications, you will very likely need some dynamic evaluation logic based on some application state, such as the current user or current time.There is the io.quarkiverse.flags.spi.FlagEvaluator SPI which makes it possible to externalize the computation of the current value of a feature flag.Flag evaluators must be CDI beans.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)).There are also several built-in evaluators available.Current timeThe io.quarkiverse.flags.TimeSpanFlagEvaluator evaluates a flag based on the current date-time obtained from the system clock in the default time-zone.The current time must be after the start-time (exclusive) and before the end-time (exclusive).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) (3)1Assign the evaluator to the flag. Note that we do not specify the initial value for my-feature-alpha - it is true by default.2The 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.3We do not specify the end-time metadata value, so there is no upper bound for the time inverval.Current userThe SecurityIdentityFlagEvaluator from the quarkus-flags-security extension can be used to compute the current value of a feature flag based on the current SecurityIdentity.A typical feature flag configuration looks like:quarkus.flags.runtime."my-feature-alpha".meta.evaluator=quarkus.security.identity (1)quarkus.flags.runtime."my-feature-alpha".meta.authenticated=true (2)quarkus.flags.runtime."my-feature-alpha".meta.roles-allowed=foo,bar (3)1Assign the evaluator to the flag. Note that we do not specify the initial value for my-feature-alpha - it is true by default.2The current user must be authenticated.3The current user must have one of the defined roles.The UsernameRolloutFlagEvaluator, on the other hand, is an evaluator using a simple percentage-based rollout strategy, based on a consistent numerical representation of the current user name.It can be used to implement gradual rollout.A typical feature flag configuration may look like:quarkus.flags.runtime."my-feature-alpha".meta.evaluator=quarkus.security.username-rollout (1)quarkus.flags.runtime."my-feature-alpha".meta.rollout-percentage=20 (2)1Assign the evaluator to the flag. Note that we do not specify the initial value for my-feature-alpha - it is true by default.2Enable the flag for the given percentage of users.CRONThe quarkus-flags-cron extension provides the CronFlagEvaluator that can be used to compute the current value of a feature flag based on the current date-time obtained from the system clock in the default time-zone and the configured CRON expression.A typical feature flag configuration may look like:quarkus.flags.runtime."my-feature-alpha".meta.evaluator=quarkus.cron (1)quarkus.flags.runtime."my-feature-alpha".meta.authenticated=* * * * mon (2)1Assign the evaluator to the flag. Note that we do not specify the initial value for my-feature-alpha - it is true by default.2The current date-time must match the given CRON expression. In this particular case, the flag will be enabled every Monday.By default, the Unix/crontab syntax is used. However, it is also possible to use the syntax from Cron4j, Quartz and Spring. For more information, see the documentation.The case for multiple evaluatorsSometimes it might be useful to combine several 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.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)1Assign the evaluator to the flag. Note that we do not specify the initial value for my-feature-alpha - it is true by default.2The value of sub-evaluators represents a comma-separated list of sub-evaluator identifiers. They are executed in the specified order. In this particular case, the TimeSpanFlagEvaluator is executed first and then the SecurityIdentityFlagEvaluator.3The current date-time must be after the specified start-time.4The current user must have the role admin.ExtensibilityFlag providers and flag evaluators are CDI beans.So all you have to do is add a bean that implements FlagProvider or FlagEvaluator in your application.You can use CDI interceptors and even decorate these components.ConclusionQuarkus Feature Flags is a lightweight and extensible extension that can help you build more flexible applications.Feedback, feature requests and contributions are welcome.Feel free to create questions and issues on GitHub repository: https://github.com/quarkiverse/quarkus-flags/issues.