1. Overview

In this article, we’ll briefly define feature flags and propose an opinionated and pragmatic approach to implement them in Spring Boot applications. Then, we’ll dig into more sophisticated iterations taking advantage of different Spring Boot features.

We’ll discuss various scenarios that might require feature flagging and talk about possible solutions. We’ll do this using a Bitcoin Miner example application.

2. Feature Flags

Feature Flags – sometimes called feature toggles – are a mechanism that allows us to enable or disable specific functionality of our application without having to modify code or, ideally, redeploy our app.

Depending on the dynamics required by a given feature flag we might need to configure them globally, per app instance, or more granularly – perhaps per user or request.

As with many situations in Software Engineering, it’s important to try to use the most straightforward approach that tackles the problem at hand without adding unnecessary complexity.

Feature flags are a potent tool that, when used wisely, can bring reliability and stability to our system. However, when they’re misused or under-maintained, they can quickly become sources of complexity and headaches.

There are many scenarios where feature flags could come in handy:

Trunk-based development and nontrivial features

In trunk-based development, particularly when we want to keep integrating frequently, we might find ourselves not ready to release a certain piece of functionality. Feature flags can come in handy to enable us to keep releasing without making our changes available until complete.

Environment-specific configuration

We might find ourselves requiring certain functionality to reset our DB for an E2E testing environment.

Alternatively, we might need to use a different security configuration for non-production environments from that used in the production environment.

Hence, we could take advantage of feature flags to toggle the right setup in the right environment.

A/B testing

Releasing multiple solutions for the same problem and measuring the impact is a compelling technique that we could implement using feature flags.

Canary releasing

When deploying new features, we might decide to do it gradually, starting with a small group of users, and expanding its adoption as we validate the correctness of its behavior. Feature flags allow us to achieve this.

In the following sections, we’ll try to provide a practical approach to tackle the above-mentioned scenarios.

Let’s break down different strategies to feature flagging, starting with the simplest scenario to then move into a more granular and more complex setup.

3. Application-Level Feature Flags

If we need to tackle any of the first two use cases, application-level features flags are a simple way of getting things working.

A simple feature flag would typically involve a property and some configuration based on the value of that property.

3.1. Feature Flags Using Spring Profiles

In Spring we can take advantage of profiles. Conveniently, profiles enable us to configure certain beans selectively. With a few constructs around them, we can quickly create a simple and elegant solution for application-level feature flags.

Let’s pretend we’re building a BitCoin mining system. Our software is already in production, and we’re tasked to create an experimental, improved mining algorithm.

In our JavaConfig we could profile our components:

@Configuration
public class ProfiledMiningConfig {

    @Bean
    @Profile("!experimental-miner")
    public BitcoinMiner defaultMiner() {
        return new DefaultBitcoinMiner();
    }

    @Bean
    @Profile("experimental-miner")
    public BitcoinMiner experimentalMiner() {
        return new ExperimentalBitcoinMiner();
    }
}

Then, with the previous configuration, we simply need to include our profile to opt-in for our new functionality. There’re tons of ways of configuring our app in general and enabling profiles in particular. Likewise, there are testing utilities to make our lives easier.

As long as our system is simple enough, we could then create an environment-based configuration to determine which features flags to apply and which ones to ignore.

Let’s imagine we have a new UI based on cards instead of tables, together with the previous experimental miner.

We’d like to enable both features in our acceptance environment (UAT). We could create the following profile group in our application.yml file:

spring:
  profiles:
    group:
      uat: experimental-miner,ui-cards

With the previous property in place, we’d just need to enable the UAT profile in the UAT environment to get the desired set of features. Of course, we could also add an application-uat.yml file in our project to include additional properties for our environment setup.

In our case, we want the uat profile also to include experimental-miner and ui-cards.

Note: if we’re using a Spring Boot version prior to 2.4.0, we’d use the spring.profiles.include property in a UAT profile-specific document to configure the additional profiles. Compared to spring.profiles.active, the former enables us to include profiles in an additive manner.

3.2. Feature Flags Using Custom Properties

Profiles are a great and simple way to get the job done. However, we might require profiles for other purposes. Or perhaps, we might want to build a more structured feature flag infrastructure.

For these scenarios, custom properties might be a desirable option.

Let’s rewrite our previous example taking advantage of @ConditionalOnProperty and our namespace:

@Configuration
public class CustomPropsMiningConfig {

    @Bean
    @ConditionalOnProperty(
      name = "features.miner.experimental", 
      matchIfMissing = true)
    public BitcoinMiner defaultMiner() {
        return new DefaultBitcoinMiner();
    }

    @Bean
    @ConditionalOnProperty(
      name = "features.miner.experimental")
    public BitcoinMiner experimentalMiner() {
        return new ExperimentalBitcoinMiner();
    }
}

The previous example builds on top of Spring Boot’s conditional configuration and configures one component or another, depending on whether the property is set to true or false (or omitted altogether).

The result is very similar to the one in 3.1, but now, we have our namespace. Having our namespace allows us to create meaningful YAML/properties files:

#[...] Some Spring config

features:
  miner:
    experimental: true
  ui:
    cards: true
    
#[...] Other feature flags

Also, this new setup allows us to prefix our feature flags – in our case, using the features prefix*.*

It might seem like a small detail, but as our application grows and complexity increases, this simple iteration will help us keep our feature flags under control.

Let’s talk about other benefits of this approach.

3.3. Using @ConfigurationProperties

As soon as we get a prefixed set of properties, we can create a POJO decorated with @ConfigurationProperties to get a programmatic handle in our code.

Following our ongoing example:

@Component
@ConfigurationProperties(prefix = "features")
public class ConfigProperties {

    private MinerProperties miner;
    private UIProperties ui;

    // standard getters and setters

    public static class MinerProperties {
        private boolean experimental;
        // standard getters and setters
    }

    public static class UIProperties {
        private boolean cards;
        // standard getters and setters
    }
}

By putting our feature flags’ state in a cohesive unit, we open up new possibilities, allowing us to easily expose that information to other parts of our system, such as the UI, or to downstream systems.

3.4. Exposing Feature Configuration

Our Bitcoin mining system got a UI upgrade which is not entirely ready yet. For that reason, we decided to feature-flag it. We might have a single-page app using React, Angular, or Vue.

Regardless of the technology, we need to know what features are enabled so that we can render our page accordingly.

Let’s create a simple endpoint to serve our configuration so that our UI can query the backend when needed:

@RestController
public class FeaturesConfigController {

    private ConfigProperties properties;

    // constructor

    @GetMapping("/feature-flags")
    public ConfigProperties getProperties() {
        return properties;
    }
}

There might be more sophisticated ways of serving this information, such as creating custom actuator endpoints. But for the sake of this guide, a controller endpoint feels like good enough a solution.

3.5. Keeping the Camp Clean

Although it might sound obvious, once we’ve implemented our feature flags thoughtfully, it’s equally important to remain disciplined in getting rid of them once they’re no longer needed.

Feature flags for the first use case – trunk-based development and non-trivial features – are typically short-lived. This means that we’re going to need to make sure that our ConfigProperties, our Java configuration, and our YAML files stay clean and up-to-date.

4. More Granular Feature Flags

Sometimes we find ourselves in more complex scenarios. For A/B testing or canary releases, our previous approach is simply not enough.

To get feature flags at a more granular level, we may need to create our solution. This could involve customizing our user entity to include feature-specific information, or perhaps extending our web framework.

Polluting our users with feature flags might not be an appealing idea for everybody, however, and there are other solutions.

As an alternative, we could take advantage of some built-in tools such as Togglz. This tool adds some complexity but offers a nice out-of-the-box solution and provides first-class integration with Spring Boot.

Togglz supports different activation strategies:

  1. Username: Flags associated with specific users
  2. Gradual rollout: Flags enabled for a percentage of the user base. This is useful for Canary releases, for example, when we want to validate the behavior of our features
  3. Release date: We could schedule flags to be enabled at a certain date and time. This might be useful for a product launch, a coordinated release, or offers and discounts
  4. Client IP: Flagged features based on clients IPs. These might come in handy when applying the specific configuration to specific customers, given they have static IPs
  5. Server IP: In this case, the IP of the server is used to determine whether a feature should be enabled or not. This might be useful for canary releases too, with a slightly different approach than the gradual rollout – like when we want to assess performance impact in our instances
  6. ScriptEngine: We could enable feature flags based on arbitrary scripts. This is arguably the most flexible option
  7. System Properties: We could set certain system properties to determine the state of a feature flag. This would be quite similar to what we achieved with our most straightforward approach

5. Summary

In this article, we had a chance to talk about feature flags. Additionally, we discussed how Spring could help us achieve some of this functionality without adding new libraries.

We started by defining how this pattern can help us with a few common use cases.

Next, we built a few simple solutions using Spring and Spring Boot out-of-the-box tools. With that, we came up with a simple yet powerful feature flagging construct.

Down below, we compared a couple of alternatives. Moving from the simpler and less flexible solution to a more sophisticated, although more complex, pattern.

Finally, we briefly provided a few guidelines to build more robust solutions. This is useful when we need a higher degree of granularity.