Spring Boot Feature Flags: A Step-by-Step Implementation Guide with a Working Java Spring Boot Application

By
Abhishek Agarwal
on
June 6, 2024

By Abhishek Agarwal

Spring Boot Feature Flags Introduction

In today's fast-paced software development landscape, businesses are constantly seeking ways to release new features while minimising risks and maximising flexibility. One effective solution to achieve this is through the use of feature flags. Feature flags, also known as feature toggles or feature switches, allow developers to control the release of new functionality, enabling them to turn features on or off without deploying new code. 

In this article, we will explore the concept of Spring Boot feature flags and demonstrate with a working banking application. We'll leverage leading feature flag software, Flagsmith, to showcase how feature flags can enhance the development and deployment process.

dark blue background with spring logo and flagsmith logo in white

What are Feature Flags? 

Before we dive into the development work, let’s first understand what feature flags are and why they are required

Feature flags are a powerful technique in software development that involves wrapping sections of code or functionality with conditional statements. These conditions, referred to as flags, determine whether a specific feature is enabled or disabled. By toggling these flags, developers can control the behaviour of the application without making any code changes. This decoupling of code deployment from feature release allows for greater control, flexibility, and agility in software development.

Feature flags can be used for various purposes, such as:

  • Rolling out new features gradually: Release new features to a subset of users for testing and gradual adoption.
  • A/B testing: Compare the performance and user engagement of different feature variations.
  • Enabling or disabling functionality based on user roles: Tailor the user experience for different user groups.

If you're getting started, here are some feature flag best practices to keep in mind.

Before We Start

This guide will be for a working Spring Book application, if you're working with Java feature flags, we have a guide for that, too.

Let’s get started!

As a first step, we need to create a Flagsmith account. It offers simple sign in with Google or GitHub options. You can just fill your basic details on https://app.flagsmith.com/signup

Once signed up, it will ask you to create an organisation. For the purpose of this tutorial I’ll name it “Java Spring Boot Tutorial”. You can name it anything you prefer. 

Next, we need to create a project inside this organisation

flagsmith ui screenshot for project creation

Click on “Create A Project”. Enter your project name. I’m creating a project with the name “Spring Boot Banking App”. Feel free to name it as you want.

Congrats! You have already completed the Flagsmith setup process. You should see the below screen now.

flagsmith ui screenshot for feature creation

We will come back to this screen when we want to add a feature flag to our application. 

Now, let’s start by creating a Java Spring Boot application. 

Let’s code!

To maintain the simplicity of this tutorial, we will develop a basic Java Spring Boot application. This application will run locally on your system and utilise local storage. It will provide fundamental banking features for customers, including:

  1. Creating a new account
  2. Add money
  3. Withdraw money
  4. Check account balance
  5. Get current interest rate

Whenever you think about adding new features, always think about feature flags. They play a key role towards giving the best customer experience and allowing incremental rollout. 

TL;DR: All the code is present at https://github.com/abhishekag03/flagsmith-java-feature-flag-demo.git with step-by-step commits. Feel free to refer to it at any time if you feel confused.  

STEP 1: Java setup 

Make sure you have Java installed by running this command on your terminal:

java -version

If you do not have Java installed on your system, download and install the latest stable version from this link based on your OS and System specifications. 

STEP 2: Create a starter Spring Boot Application

Most modern IDEs offer the functionality to create a project using Spring Boot framework for Java with just a few clicks of buttons. Here, I will be creating a Spring Boot project on IntelliJ.

I have kept the project specifications as shown below:

spring boot application screenshot

The image below shows the base directory structure that got auto-created. The FlagsmithSpringBootBankingAppApplication.java file below has the `main` function that gets executed on Server start. 

spring boot application screenshot

STEP 3: Add basic functionality for all the endpoints

TL;DR - Refer to this Pull Request to understand the changes done in this step. 

We will be adding basic code for all the endpoints defined above. I have created 2 classes:

  1. Account class - to store all the Account related information
  2. AccountService class - to define the implementation of various functionalities 

Account.java class has the below code:


package com.example.flagsmithspringbootbankingapp;

import lombok.Getter;
import lombok.Setter;

import java.util.UUID;

@Getter
@Setter
public class Account {
    String id;
    String accountHolderName;
    double balance;

    public Account(String accountHolderName) {
        this.id = UUID.randomUUID().toString();
        this.accountHolderName = accountHolderName;
        this.balance = 0;
    }
}

@Getter
@Setter
class AddMoneyRequest {
    String accountId;
    double amount;
}


@Getter
@Setter
class WithdrawMoneyRequest {
    String accountId;
    double amount;
}

AccountService.java file has the below code:


package com.example.flagsmithspringbootbankingapp;

import java.util.HashMap;

public class AccountService {

    private final HashMap UserAccounts;

    // initialises empty map to store user's accounts info
    public AccountService() {
        UserAccounts = new HashMap<>();
    }

    // get account via accountID
    public Account GetAccount(String accountID)  {
        return this.UserAccounts.get(accountID);
    }

    // create a new account and add it to the map
    public Account CreateAccount(String accountHolderName) {
        Account newAccount = new Account(accountHolderName);
        this.UserAccounts.put(newAccount.getId(), newAccount);
        return newAccount;
    }

    // add money to user's bank account
    public Account AddMoney(String accountId, double amount) {
        Account acc = this.GetAccount(accountId);
        acc.balance += amount;
        return acc;
    }

    // withdraw money from user's bank account
    public Account WithdrawMoney(String accountId, double amount) {
        Account acc = this.GetAccount(accountId);
        acc.balance += amount;
        return acc;
    }
}

When we initialise an AccountService object, it would create a new map to store all the users account info. 

To do the wiring, we add a config class which initialises the AccountService object. Create a file AppConfig.java and add the following code to it:


package com.example.flagsmithspringbootbankingapp;

import com.flagsmith.FlagsmithClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {
   @Bean
   public AccountService accountService() {
       return new AccountService();
   }
}

In our main class, we add the code to initialise this and add the API endpoints. Main class now looks like this:


package com.example.flagsmithspringbootbankingapp;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@SpringBootApplication
@RestController
public class FlagsmithSpringBootBankingAppApplication {
    @Autowired
    private AccountService accountService;

    public static void main(String[] args) {
      SpringApplication.run(FlagsmithSpringBootBankingAppApplication.class, args);
    }
    // endpoint to create a new account
    @PostMapping(path = "/create-account", consumes = MediaType.APPLICATION_JSON_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Account> createAccount(@RequestBody String accountHolderName) {

        return ResponseEntity.ok(accountService.CreateAccount(accountHolderName));

    }

    // endpoint to add money to an account
    @PostMapping(path = "/add-money", consumes = MediaType.APPLICATION_JSON_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public Account addMoney(@RequestBody AddMoneyRequest req) {
        return accountService.AddMoney(req.accountId, req.amount);
    }


    // endpoint to withdraw money from an account
    @PostMapping(path = "/withdraw-money", consumes = MediaType.APPLICATION_JSON_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Account> withdrawMoney(@RequestBody WithdrawMoneyRequest req) {
        return ResponseEntity.ok(accountService.WithdrawMoney(req.accountId, req.amount));

    }

    // endpoint to check account balance
    @GetMapping("/check-balance")
    public double checkBalance(@RequestParam(value = "accountId") String accountId) {
        Account account = accountService.GetAccount(accountId);
        return account.balance;
    }

    // endpoint to get the current interest rate
    @GetMapping("/interest-rate")
    public ResponseEntity<Object> getInterestRate() {
        return ResponseEntity.ok(3.5);
    }
}

All the endpoints above have a very vanilla implementation just to set the application working. 

STEP 4: Run and Test

Let’s run what we have so far and verify if the basic functionality works fine.

Use your IDE to compile the Java code and run it. Make sure all the dependencies in pom.xml are as the ones in this PR.

When the server boots up, you can make a curl request to check the functionality. Let’s test the basic getInterest method first:

curl localhost:8080/interest-rate 

Running this command in your terminal should output: 3.5, which is also the value we hardcoded in our code above. 

STEP 5: Understand the need of Feature Flags in Banking App

First, let’s understand why we need feature flags in this application. Imagine a case where there is an issue with a downstream service or the banking network and we want to stop any money withdrawal or new account creation. In the current setup, we will have to:

  • Update the API implementation to remove the business logic  (~10 mins)
  • Get approvals and merge the code into main branch (~20 mins)
  • Deploy it to dev, test it (~20 mins)
  • Deploy to production (~10 mins)

This process will take up an hour and also require the engineering team to come online and update the code logic. Also, when we want to rollback, the same effort has to be repeated. 

What if all this could be done with the click of a button? That’s exactly what Flagsmith offers! A very intuitive User Interface, which allows users (with special access) to update the functionality with just a feature toggle. 

STEP 6: Create Feature Flags

Let’s create Spring Boot feature flags for each endpoint. We will add Java Feature flags to our code. Click on the “Create Feature” button on the UI. 

As shown below, create feature flag for - a) allowing withdraw money, b) allowing deposit money, and c) allowing account creation

spring boot feature flag creation screenshot

Similarly, create one for `allow_account_creation`. 

STEP 7: Add Feature Flags to code

TL;DR - Refer to this Pull Request

  1. Update the pom.xml file to add the below Flagsmith dependency


   com.flagsmith
   flagsmith-java-client
   7.1.0

  1. Get the Server API Key from Flagsmith UI by navigating to the Settings section for your environment. Click on Keys and then “Create Server Side Environment Key”.
screenshot of getting the server API key for Flagsmith
  1. Copy the generated API key
  2. Add flagsmith client object to the parent class and inject it via the AppConfig class. Ideally the API key should be picked from a config file, but to keep this tutorial simple, it has been hardcoded. Add the below code to AppConfig.java class (as shown in this PR)

@Bean
public FlagsmithClient flagsmithClient() {
    return FlagsmithClient
            .newBuilder()
            .setApiKey("ser.****")
            .build();
}

In the main class, simply add:


@Autowired
private FlagsmithClient flagsmithClient;


  1. Modify the implementation of endpoints to include a feature flag check before executing the core logic as shown below:

@PostMapping(path = "/create-account", consumes = MediaType.APPLICATION_JSON_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity createAccount(@RequestBody String accountHolderName) throws FlagsmithClientError {
        Flags flags = flagsmith.getEnvironmentFlags();
        String featureName = "allow_account_creation";
        Boolean isAllowed = flags.isFeatureEnabled(featureName);
        if (isAllowed) {
            return ResponseEntity.ok(accountService.CreateAccount(accountHolderName));
        } else {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(null);
        }
    }

Similarly, it can be done for withdraw cash as shown below


@PostMapping(path = "/withdraw-money", consumes = MediaType.APPLICATION_JSON_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity withdrawMoney(@RequestBody WithdrawMoneyRequest req) throws FlagsmithClientError {
        Flags flags = flagsmith.getEnvironmentFlags();
        String featureName = "allow_withdraw_money";
        Boolean isAllowed = flags.isFeatureEnabled(featureName);
        if (isAllowed) {
            return ResponseEntity.ok(accountService.WithdrawMoney(req.accountId, req.amount));
        } else {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(null);
        }
    }
   

STEP 8: Testing feature flags

Let’s test the current implementation of the endpoints:

To create a new account:


curl http://localhost:8080/create-account -H 'Content-Type: application/json'  -d "John Doe"

Response:


{"id":"8ab2f165-e11b-465e-842b-02332447b012","accountHolderName":"John Doe","balance":0.0}

Let’s add money to this account:


curl -i http://localhost:8080/create-account -H 'Content-Type: application/json' -d '{"accountId": "8ab2f165-e11b-465e-842b-02332447b012", "amount": 500}'

Let’s now turn the `allow_account_creation  flag off and check the API response - 

Now, on running the create account API, you  get an error:


curl -i http://localhost:8080/create-account -H 'Content-Type: application/json' -d '{"accountHolderName": "Abhishek Agarwal"}'

Response:


HTTP/1.1 403
Content-Length: 0
Date: Sat, 30 Sep 2023 18:56:44 GMT

The response indicates that it is forbidden to make this request and hence not executing the business logic. 

Thus, we could pause new account creation with just the click of a button. This is how Java feature toggles make development easily manageable. 

STEP 9: Advanced Feature Flags

Part 1: Using Feature Value

The above illustration showed Flagsmith Feature flags act more like a Kill Switch. However, there is much more that Spring Boot feature flags can do. They can also:

  • Store a value which you might want to update frequently
  • Implement user segment based feature rollout
  • Control feature values independently for different environments in your system

Here, I will be demonstrating how to store a value in the feature flag on Flagsmith. For that, we will be updating the `GetInterestRate` endpoint, since Interest is one thing which keeps varying with time as per the market scenario.

Let’s create a new feature flag to store the interest rate percentage. I am naming it `interest_rate` for simplicity. While creating the Feature flag, add the desired interest rate in the “value” option. 

Above, I have kept the interest rate as 5.5%.
Let’s add the code inside the `GetInterestRate` endpoint so that it can read the interest rate value from here. 


   // endpoint to get the current interest rate
    @GetMapping("/interest-rate")
    public ResponseEntity<Object> getInterestRate() throws FlagsmithClientError {
        Flags flags = flagsmithClient.getEnvironmentFlags();
        String featureName = "interest_rate";
        Object intRate = flags.getFeatureValue(featureName);
        if (intRate != "") {
            return ResponseEntity.ok(intRate);
        }
        return ResponseEntity.ok(defaultInterestRate);
    }
 

Here, we are simply reading the value in the feature flag and if it is present, we honour that value, else use the default hard coded interest rate. 

The above code changes are present in this Pull Request

Part 2: Local Evaluation Mode

In large scale applications, response latency plays a major role. Currently, in our system, everytime the endpoint is called, it makes an additional call to the Flagsmith server for the feature value. This call adds a few ms to the response latency for every call we get. 

Flagsmith offers local evaluation mode while defining the client. This capability allows us to optimise our APIs. With this, the client can define a configurable time interval, only after which the latest flag value gets fetched. All API calls in between that interval, will be served based on the last fetched value. Based on the use case, this refresh interval can be decided. 

For instance, we define the refresh interval to be 1 minute. The flag value will be fetched only after at least 1 minute and all the API calls during that minute will be served with the value that was last fetched. 

Below is how it can be coded. Only the Flagsmith Configuration needs to be added while initialising the client. 


@Bean
 public FlagsmithClient flagsmithClient() {
        FlagsmithConfig flagsmithConfig = FlagsmithConfig
                .newBuilder()
                .withLocalEvaluation(true)
                .withEnvironmentRefreshIntervalSeconds(60)
                .build();
        return FlagsmithClient
                .newBuilder()
                .setApiKey("ser.****")
                .withConfiguration(flagsmithConfig)
                .build();
    }

As demonstrated in the Java article here, the local evaluation mode brings a huge impact towards latency improvement, thus increasing the throughput.

Why Flagsmith for Spring Boot Feature Flags?

Flagsmith is a powerful feature flag management tool that simplifies the process of implementing and managing feature flags in your applications. It provides a user-friendly interface for creating and controlling feature flags, allowing teams to manage feature releases with ease. It’s easy to deploy and you can get up and running in minutes. 

Other key benefits of using Flagsmith include:

  • Flexible Deployments: Use our hosted API, deploy to your own private cloud, or run feature flags on-premises
  • Centralised Control: Flagsmith provides a central dashboard UI where you can create, manage, and monitor feature flags for multiple applications and users
  • User Segmentation: Easily define user segments to roll out features to specific user groups
  • Integration: Flagsmith integrates seamlessly with various development tools, languages and frameworks, including Spring Boot
  • Open Source: Flagsmith is an Open Source feature flags tool, which makes it more reliable and maintainable. Find Flagsmith on GitHub

Conclusion

In conclusion, this article has demonstrated the practical application of Spring Boot feature flags in a banking application with Java feature toggles. We have learnt how to set up Flagsmith for a project, create a Java Spring Boot application, and implement basic banking features while emphasizing the role of feature flags in providing flexibility and quick issue response. We also explored advanced feature flag usage, such as storing values, as exemplified by the interest rate update feature flag. 

By following this guide, you've gained a solid understanding of feature flags and how to use Flagsmith to enhance your Java Spring Boot applications, empowering you to release new features, conduct A/B testing, and respond to issues confidently, ultimately improving the user experience and streamlining development.

More Reading: Feature Flags and Other Languages/Frameworks

To read about feature flags and other languages/frameworks, check out these other guides:

Quote

Subscribe

Learn more about CI/CD, AB Testing and all that great stuff

Success!
We'll keep you up to date with the latest Flagsmith news.
Must be a valid email
Illustration Letter