With continuous integration (CI) and continuous deployment (CD) your changes and features are usually deployed to production whenever your pipeline passes. There might be features you don't want to use until a specific date or only enable them for specific users. For such use cases, you can make use of so-called feature toggles aka. feature flags. These are usually boolean flags to indicate whether to expose functionality or not. You can achieve this with a static variable in Java but then you need to redeploy the application whenever you want to enable a specific feature. Fortunately, there is a nice library for the Java ecosystem to create more advanced feature toggles: Togglz. This library comes with a good Spring Boot integration and is easy to setup.
I'll use the following technologies for this blog post: Java 11, Spring Boot 2.3, and Togglz 2.6.1.
Spring Boot 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 | <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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.3.0.RELEASE</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>11</java.version> <togglz.version>2.6.1.Final</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 you need to start. In addition, I've added the togglz-console
dependency to enable and disable feature toggles later on with a simple web UI.
Hide features in your application using Togglz
For a sample use case to utilize feature toggles, I've created two REST endpoints to serve information 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 | @RestController @RequestMapping("/books") public class BookController { @Autowired private FeatureManager featureManager; @Autowired private 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 which 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 } |
Enable/disable feature toggles with the Togglz console
To enable or disable feature toggles once your application is running, you can make use of the web console. The default path is /togglz-console
but can be configured in your 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 your feature toggles and their current state:
On this overview page, you can change the status of a toggle by clicking on its current status button. In addition, and if you need a more fine-grained feature en-/disabling, you can click on the button in the Actions column. The following page offers you a set of pre-defined strategies for changing the state of your feature toggle (e.g. date based, IP based, etc.):
Further thoughts on using Togglz alongside 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> |
You 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 state of a feature toggle with an in-memory based solution. If your application runs multiple instances you should switch to a different approach. There are out-of-the-box integrations for feature state management with a JDBC data source and MongoDB already available.
Also, be aware that the Togglz library is currently looking for a new maintainer and no active development is happening.
For further information about feature toggles in general, take a look at the following blog post on Martin Fowler's homepage.
You can find the code for this blog post on GitHub.
Have fun using Togglz,
Phil