1. OVERVIEW

Features toggles help developers to make smaller, faster and less risky changes to software. Let’s say a requirement that will take a significant amount of effort to complete is going to be implemented. Two popular approaches are:

  • Branch the source code and develop for the next quarter or so. Once the implementation is completed you might likely face problems merging back this long-lived branch. You might end up asking your peers to stop committing changes or the SCM might get locked for the next 2-3 weeks until all the conflicts are resolved. Possibly delaying bug fixes and new features from being released to clients.

  • Implement the requirement in trunk / main / master branch, hiding incomplete functionality behind a development or release toogle. This allows the development team to work in the same code base, avoids the complexity of maintaining multiple branches, reduces the release cycle while getting a timely feedback when paired with a CI / CD pipeline.

Adding Feature Toggles to Spring Boot applications using Togglz

This tutorial guides you to add feature flags in Spring Boot applications using Togglz and togglz-spring-boot-starter Spring Boot starter. How to configure and use them, some tradeoffs, and a word of caution.

2. REQUIREMENTS

  • Java 7+.
  • Maven 3.2+.
  • Familiarity with Spring Framework.

3. APPLICATION DEPENDENCIES

pom.xml:

...
<properties>
  <togglz.version>2.5.0.Final</togglz.version>
...
</properties>
...
<dependency>
  <groupId>org.togglz</groupId>
  <artifactId>togglz-spring-boot-starter</artifactId>
  <version>${togglz.version}</version>
</dependency>
...

togglz-spring-boot-starter autoconfigures Togglz beans such as FeatureManager, FeatureProvider, StateRepository, UserProvider, ActivationStrategy, etc. (when a developer hasn’t provided them) using @ConditionalOn…… annotations and configuration properties.

4. CONFIGURING FEATURE TOGGLES

FeatureToggles.java:

package com.asimio.demo.togglz.config;
...
public enum FeatureToggles implements Feature {

  @Label("New Elastic Search Backend replacing RDBMS for text-based searches.")
  TEXT_BASED_SEARCH_VIA_ELASTIC_SEARCH,

  @Label("New content retrieval via CMS instead of text files.")
  CONTENT_RETRIEVAL_VIA_CMS,
...
}

application.yml:

...
togglz:
  features:
    TEXT_BASED_SEARCH_VIA_ELASTIC_SEARCH:
      enabled: true
    CONTENT_RETRIEVAL_VIA_CMS:
      enabled: false
...

Togglz is enabled by default unless togglz.enabled is set to false. The configuration keys TEXT_BASED_SEARCH_VIA_ELASTIC_SEARCH and CONTENT_RETRIEVAL_VIA_CMS map to the values set in FeatureToggles.java enum.

A disadvantage of configuring the toggles default value in an application’s properties file is when they need to be updated in an environment where there might be multiple instances of the same application running, but more on this later.

5. USING FEATURE TOGGLES

DemoResource.java:

package com.asimio.demo.togglz.web;
...
@RestController
@RequestMapping(value = "/api")
public class DemoResource {
...
  @GetMapping(value = "/demo-es/{id}")
  public String getDemoEs(@PathVariable("id") String id) {
    if (FeatureToggles.TEXT_BASED_SEARCH_VIA_ELASTIC_SEARCH.isActive()) {
      return "New ElasticSearch Backend is active";
    }
    return "Still relying in existing / legacy RDBMS backend implementation";
  }

  @GetMapping(value = "/demo-cms/{id}")
  public String getDemoCms(@PathVariable("id") String id) {
    if (FeatureToggles.CONTENT_RETRIEVAL_VIA_CMS.isActive()) {
      return "New CMS Backend is active";
    }
    return "Still relying in existing / legacy cms backend implementation";
  }
...

Using the feature flags is as easy as checking for FeatureToggles.<the enum type field>.isActive() value.

5.1. RUNNING THE DEMO

curl "http://localhost:8080/api/demo-es/0"
New ElasticSearch Backend is active
curl "http://localhost:8080/api/demo-cms/0"
Still relying in existing / legacy cms backend implementation

The output matches TEXT_BASED_SEARCH_VIA_ELASTIC_SEARCH flag set to active and CONTENT_RETRIEVAL_VIA_CMS set to false as configured in application.yml.

6. USING FeatureProxyFactoryBean

Let’s see an example where you wouldn’t need to check for FeatureToggles.<the enum type field>.isActive() value.

Let’s assume a legacy and slow service class implementation is getting replaced with an improved one where they both implement the same Java interface, SomeService.java:

SomeService.java:

package com.asimio.demo.togglz.service;

public interface SomeService {

  String getSomeValue();
}

OldSomeServiceImpl.java:

package com.asimio.demo.togglz.service.impl;
...
public class OldSomeServiceImpl implements SomeService {

  @Override
  public String getSomeValue() {
    return "Value from old service implementation";
  }
}

NewSomeServiceImpl.java:

package com.asimio.demo.togglz.service.impl;
...
public class NewSomeServiceImpl implements SomeService {

  @Override
  public String getSomeValue() {
    return "Value from new service implementation";
  }
}

Let’s configure some @Bean-annotated beans in an attempt to get rid of the if statements which not only reduces the cyclomatic complexity but makes the code more readable:

AppConfiguration.java:

package com.asimio.demo.togglz.config;
...
@Configuration
public class AppConfiguration {

  @Bean
  public SomeService oldSomeService() {
    return new OldSomeServiceImpl();
  }

  @Bean
  public SomeService newSomeService() {
    return new NewSomeServiceImpl();
  }

  @Bean
  public FeatureProxyFactoryBean proxiedSomeService() {
    FeatureProxyFactoryBean proxyFactoryBean = new FeatureProxyFactoryBean();
    proxyFactoryBean.setFeature(FeatureToggles.USE_NEW_SOMESERVICE.name());
    proxyFactoryBean.setProxyType(SomeService.class);
    proxyFactoryBean.setActive(this.newSomeService());
    proxyFactoryBean.setInactive(this.oldSomeService());
    return proxyFactoryBean;
  }

  @Bean
  @Primary
  public SomeService someService(@Autowired FeatureProxyFactoryBean proxiedSomeService) throws Exception {
    return (SomeService) proxiedSomeService.getObject();
  }
}

The interesting part is Togglz’s FeatureProxyFactoryBean proxying between oldSomeService and newSomeService beans, both implementing a common Java interface, based on a toogle’s state, FeatureToggles.USE_NEW_SOMESERVICE in this case.

Using SomeService’s proxied bean is simple. someService() method is annotated with @Primary, meaning any autowiring of the SomeService.java interface will use this bean, for instance:

DemoResource.java (Contd):

...
+  @Autowired
+  private SomeService someService;
...
+  @GetMapping(value = "/demo-someservice/{id}")
+  public String getDemoSomeService(@PathVariable("id") String id) {
+    return this.someService.getSomeValue();
+  }
...

Notice no if statement was included.

curl "http://localhost:8080/api/demo-someservice/0"
Value from new service implementation

It’s proxing to the new implementation, NewSomeServiceImpl.java, as a result of the following toggle settings:

FeatureToggles.java (Contd):

+  @Label("New some service.")
+  USE_NEW_SOMESERVICE;

application.yml (Contd):

togglz:
  features:
    TEXT_BASED_SEARCH_VIA_ELASTIC_SEARCH:
      enabled: true
    CONTENT_RETRIEVAL_VIA_CMS:
      enabled: false
+    USE_NEW_SOMESERVICE:
+      enabled: true

Another advantage of the FeatureProxyFactoryBean usage is maintainability, when it’s time to retire the old development toggle there is only one place that would have to change, the removal of oldSomeService() and proxiedSomeService() methods in AppConfiguration.java instead of removing a number of if statements across the application code base. At that point an instance NewSomeServiceImpl.java will be injected when autowiring SomeService.java interface.

7. TOGGLES IN A MULTIPLE INSTANCES ENVIRONMENT

When running multiples instances of an application using feature flags, it would be advisible to store the toggles state in a centralized location where all the instances can read the flags from.

Togglz provides support for a number of implementations:

ArchaiusStateRepository CachingStateRepository CassandraStateRepository
CompositeStateRepository DatastoreStateRepository DynamoDBStateRepository
FileBasedStateRepository FixedNamespaceStateRepository GoogleCloudDatastoreStateRepository
HazelcastStateRepository InMemoryStateRepository JDBCStateRepository
MemcacheStateRepository MongoStateRepository PropertyBasedStateRepository
RedisStateRepository S3StateRepository SlackStateRepository
ZookeeperStateRepository    

You could also provide your own state repository implementing the StateRepository interface.

In any case, you would then define a StateRepository bean which would take precedence over the one autoconfigured by Togglz Spring Boot starter.

  @Bean
  public StateRepository s3StateRepository() {
    ...
  }

8. USE WITH CAUTION

Implementing feature flags using Togglz and Spring Boot can be easily accomplished specially when using togglz-spring-boot-starter custom Spring Boot starter.

Use them with caution though, misusing or abusing development or release feature toggles can lead to a significant technical debt. Toggles might end up in the code and never retired or activated, making the code more complex and fragile, difficult to maintain and requiring more conditional branches to be tested.

Thanks for reading and as always, feedback is very much appreciated. If you found this post helpful and would like to receive updates when content like this gets published, sign up to the newsletter.

9. SOURCE CODE

Accompanying source code for this blog post can be found at:

10. REFERENCES