Contents

How to avoid switch statement in Spring Boot

In Java we often encounter the need to handle different types of objects in different ways.

Scenario

Let’s imagine a scenario where we have to handle different token types:

 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
public enum TokenType {
    FOO, BAR
}

public interface TokenStrategy {
    TokenType getTokenType();
    void handleToken();
}

public class FooTokenStrategy implements TokenStrategy {
    @Override
    public TokenType getTokenType() {
        return TokenType.FOO;
    }

    @Override
    public void handleToken() {
        // ...
    }
}

public class BarTokenStrategy implements TokenStrategy {
    @Override
    public TokenType getTokenType() {
        return TokenType.BAR;
    }

    @Override
    public void handleToken() {
        // ...
    }
}

@Configuration
class TokenStrategyConfig {
    @Bean
    FooTokenStrategy fooTokenStrategy() {
        return new FooTokenStrategy();
    }

    @Bean
    BarTokenStrategy barTokenStrategy() {
        return new BarTokenStrategy();
    }
}

Simple solution witch switch

Java provides us with switch statement which allows us to handle such cases:

 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
class TokenHandler {
    private final FooTokenStrategy fooTokenStrategy;
    private final BarTokenStrategy barTokenStrategy;

    public TokenHandler(FooTokenStrategy fooTokenStrategy, BarTokenStrategy barTokenStrategy) {
        this.fooTokenStrategy = fooTokenStrategy;
        this.barTokenStrategy = barTokenStrategy;
    }

    void handleToken(TokenType tokenType) {
        switch (tokenType) {
            case FOO -> fooTokenStrategy.handleToken();
            case BAR -> barTokenStrategy.handleToken();
        }
    }
}

@Configuration
@Profile("switch")
class TokenConfig {
    @Bean
    TokenHandler tokenHandler(FooTokenStrategy fooTokenStrategy, BarTokenStrategy barTokenStrategy) {
        return new TokenHandler(fooTokenStrategy, barTokenStrategy);
    }
}

Problem with Switch

Unfortunately this approach is not the best - it violates open-closed principle, because for each new token type we need to modify TokenHandler#handleToken method.

First solution

To avoid it we should use polymorphism:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class TokenHandler {
    private final Map<TokenType, TokenStrategy> strategies;

    public TokenHandler(Map<TokenType, TokenStrategy> strategies) {
        this.strategies = strategies;
    }

    void handleToken(TokenType tokenType) {
        strategies.get(tokenType).handleToken();
    }
}

@Configuration
@Profile("map")
class TokenConfig {
    @Bean
    TokenHandler tokenHandler(FooTokenStrategy fooTokenStrategy, BarTokenStrategy barTokenStrategy) {
        Map<TokenType, TokenStrategy> strategies = new HashMap<>();
        strategies.put(TokenType.FOO, fooTokenStrategy);
        strategies.put(TokenType.BAR, barTokenStrategy);
        return new TokenHandler(strategies);
    }
}

Now handling new token type is done by introducing new type without the need of modifying switch statement any more. This solution is pretty good, but we still need to manually inject new strategy in TokenConfig#tokenHandler bean creation method.

Improvement

It turns out there is a way for Spring to do it automatically for us:

 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
class TokenHandler {
    private final Map<TokenType, TokenStrategy> strategies;

    public TokenHandler(Map<TokenType, TokenStrategy> strategies) {
        this.strategies = strategies;
    }

    void handleToken(TokenType tokenType) {
        strategies.get(tokenType).handleToken();
    }
}

@Configuration
@Profile("autowired")
class TokenConfig {
    @Autowired
    List<TokenStrategy> strategies;

    @Bean
    TokenHandler tokenHandler() {
        Map<TokenType, TokenStrategy> strategyMap = new HashMap<>();
        for (TokenStrategy strategy : strategies) {
            strategyMap.put(strategy.getTokenType(), strategy);
        }
        return new TokenHandler(strategyMap);
    }
}

Code

All the code is available at: https://bitbucket.org/tusinski/how-to-avoid-switch-statement-in-spring