With continuous integration (CI) and continuous deployment (CD), our changes and features are usually deployed to production whenever our pipeline passes. There might be features we don't want to activate until a specific date or only enable them for particular users. For such use cases, we can use so-called feature toggles aka. feature flags.
These are usually boolean flags to indicate whether to expose functionality or not. We can achieve this with a static variable in Java, but then we need to redeploy the application whenever we want to enable a specific feature. Fortunately, there is a convenient library for the Java ecosystem to create more advanced feature toggles: Togglz. This library comes with an excellent Spring Boot integration and is easy to set up.
We're going to use the following technologies for this blog post: Java 17, Spring Boot 2.7, and Togglz 3.2.1.
Spring Boot Maven Project Setup
The Maven project for this application looks like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | <?xml version="1.0" encoding="UTF-8"?> <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.0</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>de.rieckpil.blog</groupId> <artifactId>spring-boot-feature-toggles-with-togglz</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-boot-feature-toggles-with-togglz</name> <description>Demo project for Spring Boot with Togglz</description> <properties> <java.version>17</java.version> <togglz.version>3.2.1</togglz.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- Togglz dependencies--> <dependency> <groupId>org.togglz</groupId> <artifactId>togglz-spring-boot-starter</artifactId> <version>${togglz.version}</version> </dependency> <dependency> <groupId>org.togglz</groupId> <artifactId>togglz-console</artifactId> <version>${togglz.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> |
The Togglz project offers a dedicated Spring Boot library to autoconfigure everything we need to start.
In addition, we include the togglz-console
dependency to enable and disable feature toggles later on with a simple web UI.
Guard Features With Togglz
For a sample use case to utilize feature toggles, we plan to toggle specific features for two REST endpoints that serve data about books.
Depending on whether or not a feature toggle is active, the application will expose additional information and content for the second endpoint.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | @RestController @RequestMapping("/books") public class BookController { private final FeatureManager featureManager; private final ObjectMapper objectMapper; public BookController(FeatureManager featureManager, ObjectMapper objectMapper) { this.featureManager = featureManager; this.objectMapper = objectMapper; } @GetMapping public ResponseEntity<JsonNode> getBook() { ObjectNode book = objectMapper.createObjectNode(); book.put("title", "Spring Boot 2.1"); book.put("author", "Duke"); book.put("pages", 42); if (featureManager.isActive(BookstoreFeatures.EXTENDED_INFORMATION)) { book.put("description", "Book about using Spring Boot"); book.put("publishedAt", LocalDate.now().toString()); } if (featureManager.isActive(BookstoreFeatures.PUBLIC_PRICES)) { book.put("price", 89.50); } return ResponseEntity.ok(book); } @GetMapping @RequestMapping("/wishlist") public ResponseEntity<ArrayNode> getBookWishlist() { if (featureManager.isActive(new NamedFeature("BOOK_WISHLIST"))) { ArrayNode array = objectMapper.createArrayNode(); array.add("Spring Boot 2.2"); array.add("Spring Framework 6.0"); return ResponseEntity.ok(array); } return ResponseEntity.noContent().build(); } } |
The main interaction with the Togglz library is done using the FeatureManager
class, which is injectable and configured automatically.
A feature toggle is defined either in the application.properties
and accessed with new NamedFeature("NAME")
or using a typesafe enum that implements the Feature
interface:
1 2 | togglz.features.BOOK_WISHLIST.enabled=true togglz.feature-enums=de.rieckpil.blog.BookstoreFeatures |
1 2 3 4 5 6 7 8 9 | public enum BookstoreFeatures implements Feature { @EnabledByDefault @Label("Provide extended information about a book") EXTENDED_INFORMATION, @Label("Enable public prices endpoint for books") PUBLIC_PRICES } |
Toggle Features with the Togglz Web Console
To enable or disable feature toggles during runtime (no need to restart our application), we can make use of the web console.
The default path is /togglz-console
but can be configured in our application.properties
files:
1 2 3 | togglz.console.enabled=true togglz.console.path=/togglz-console togglz.console.secured=false |
The web console offers a visual representation of all of our feature toggles and their current state:
On this overview page, we can change the status of a toggle by clicking on its current status button.
In addition, we can have more fine-grained feature en-/disabling rules. Therefore, we can click on the button in the Actions column. The following page displays a set of pre-defined strategies for changing the state of our feature toggle (e.g., date-based, IP based, etc.):
Further Thoughts on Using Togglz With Spring Boot
The Togglz console can also be seamlessly integrated and secured with Spring Security, using the following dependency:
1 2 3 4 5 | <dependency> <groupId>org.togglz</groupId> <artifactId>togglz-spring-security</artifactId> <version>${togglz.version}</version> </dependency> |
We can configure the Togglz console to use Spring Boot's management port and apply further security rules for this (e.g., blocked by a firewall internet access):
1 2 | togglz.console.use-management-port=true management.server.port=8082 |
Using the default configuration, Togglz manages the feature toggle state with an in-memory-based solution. If we run multiple instances of our application (scaling out), we should switch to a different approach with a shared persistence.
There are out-of-the-box integrations for feature state management with a JDBC data source and MongoDB available.
For further information about feature toggles in general, take a look at the following blog post on Martin Fowler's homepage.
The source code for this example is available on GitHub.
Joyful toggling,
Philip