In this example, we will learn about the strategy pattern in Spring. We’ll cover different ways to inject strategies, starting with a simple list-based approach to a more efficient map-based method. To illustrate the concept, we’ll use three unforgivable curses from the Harry Potter series—the Avada Kedavra, the Crucio, and the Imperio.
What is a strategic pattern?
A strategy pattern is a design principle that allows you to switch between different algorithms or behaviors at runtime. It helps make your code flexible and adaptable by allowing you to incorporate different strategies without changing the core logic of your application.
This approach is useful in scenarios where you have different implementations for a particular functionality task and want to make your system more adaptable to change. It promotes a more modular code structure by separating algorithmic details from the main logic of your application.
Step 1: Implementation of the strategy
Imagine yourself as a dark wizard seeking to master the power of the Unforgivable Curses with Spring. Our mission is to implement all three curses — Avada Kedavra, Crucio and Imperio. After that we will switch between curses (strategies) at runtime.
Let’s start with our strategic interface:
public interface CurseStrategy
String useCurse();
String curseName();
In the next step we need to implement all the Unforgivable Curses:
@Component
public class CruciatusCurseStrategy implements CurseStrategy
@Override
public String useCurse()
return "Attack with Crucio!";
@Override
public String curseName()
return "Crucio";
@Component
public class ImperiusCurseStrategy implements CurseStrategy
@Override
public String useCurse()
return "Attack with Imperio!";
@Override
public String curseName()
return "Imperio";
@Component
public class KillingCurseStrategy implements CurseStrategy
@Override
public String useCurse()
return "Attack with Avada Kedavra!";
@Override
public String curseName()
return "Avada Kedavra";
Step 2: Insert Curses as a list
Spring brings a touch of magic that allows us to inject multiple interface implementations as a list so we can use it to inject strategies and switch between them.
But first, let’s create the basics: the wizard interface.
public interface Wizard
String castCurse(String name);
And we can insert our curses (strategies) into the Wizard and filter the desired one.
@Service
public class DarkArtsWizard implements Wizard
private final List<CurseStrategy> curses;
public DarkArtsListWizard(List<CurseStrategy> curses)
this.curses = curses;
@Override
public String castCurse(String name)
return curses.stream()
.filter(s -> name.equals(s.curseName()))
.findFirst()
.orElseThrow(UnsupportedCurseException::new)
.useCurse();
UnsupportedCurseException
it is also created if the requested curse does not exist.
public class UnsupportedCurseException extends RuntimeException
And we can confirm that casting a curse works:
@SpringBootTest
class DarkArtsWizardTest
@Autowired
private DarkArtsWizard wizard;
@Test
public void castCurseCrucio()
assertEquals("Attack with Crucio!", wizard.castCurse("Crucio"));
@Test
public void castCurseImperio()
assertEquals("Attack with Imperio!", wizard.castCurse("Imperio"));
@Test
public void castCurseAvadaKedavra()
assertEquals("Attack with Avada Kedavra!", wizard.castCurse("Avada Kedavra"));
@Test
public void castCurseExpelliarmus()
assertThrows(UnsupportedCurseException.class, () -> wizard.castCurse("Abrakadabra"));
Another popular approach is to define canUse
method instead curseName
. This will come back boolean
and allows us to use more complex filtering such as:
public interface CurseStrategy
String useCurse();
boolean canUse(String name, String wizardType);
@Component
public class CruciatusCurseStrategy implements CurseStrategy
@Override
public String useCurse()
return "Attack with Crucio!";
@Override
public boolean canUse(String name, String wizardType)
return "Crucio".equals(name) && "Dark".equals(wizardType);
@Service
public class DarkArtstWizard implements Wizard
private final List<CurseStrategy> curses;
public DarkArtsListWizard(List<CurseStrategy> curses)
this.curses = curses;
@Override
public String castCurse(String name)
return curses.stream()
.filter(s -> s.canUse(name, "Dark")))
.findFirst()
.orElseThrow(UnsupportedCurseException::new)
.useCurse();
Advantages: Easy to implement.
Against: It goes through the loop every time, which can lead to slower execution time and increased processing load.
Step 3: Insert the strategies as a map
We can easily address the drawbacks from the previous section. Spring allows us to inject a folder with bean names and instances. It simplifies the code and improves its efficiency.
@Service
public class DarkArtsWizard implements Wizard
private final Map<String, CurseStrategy> curses;
public DarkArtsMapWizard(Map<String, CurseStrategy> curses)
this.curses = curses;
@Override
public String castCurse(String name)
CurseStrategy curse = curses.get(name);
if (curse == null)
throw new UnsupportedCurseException();
return curse.useCurse();
This approach has a downside: Spring injects the bean name as a key to Map
so the strategy names are the same as the bean names like cruciatusCurseStrategy
. This dependency on Spring’s internal bean names can cause problems if Spring’s code or our class names change without notice.
Let’s see if we are still able to cast those curses:
@SpringBootTest
class DarkArtsWizardTest
@Autowired
private DarkArtsWizard wizard;
@Test
public void castCurseCrucio()
assertEquals("Attack with Crucio!", wizard.castCurse("cruciatusCurseStrategy"));
@Test
public void castCurseImperio()
assertEquals("Attack with Imperio!", wizard.castCurse("imperiusCurseStrategy"));
@Test
public void castCurseAvadaKedavra()
assertEquals("Attack with Avada Kedavra!", wizard.castCurse("killingCurseStrategy"));
@Test
public void castCurseExpelliarmus()
assertThrows(UnsupportedCurseException.class, () -> wizard.castCurse("Crucio"));
Advantages: No loops.
Against: Dependency on bean names, which makes the code less maintainable and more error-prone if names are changed or refactored.
Step 4: Insert a list and convert to a map
The disadvantages of map injection can be easily removed if we inject a List and convert it to a Map:
@Service
public class DarkArtsWizard implements Wizard
private final Map<String, CurseStrategy> curses;
public DarkArtsMapWizard(List<CurseStrategy> curses)
this.curses = curses.stream()
.collect(Collectors.toMap(CurseStrategy::curseName, Function.identity()));
@Override
public String castCurse(String name)
CurseStrategy curse = curses.get(name);
if (curse == null)
throw new UnsupportedCurseException();
return curse.useCurse();
With this approach we can return to use curseName
instead of Spring bean names for Map
keys (strategy names).
Step 5: @Autowire in the interface
Spring supports automatic wiring into methods. A simple example of automatic wiring into methods is via setter injection. This feature allows us to use @Autowired
in default
interface method so we can register each CurseStrategy
in Wizard
interface without the need to implement a registration method in each strategy implementation.
Let’s update Wizard
interface by adding a registerCurse
method:
public interface Wizard
String castCurse(String name);
void registerCurse(String curseName, CurseStrategy curse)
This is Wizard
implementation:
@Service
public class DarkArtsWizard implements Wizard
private final Map<String, CurseStrategy> curses = new HashMap<>();
@Override
public String castCurse(String name)
CurseStrategy curse = curses.get(name);
if (curse == null)
throw new UnsupportedCurseException();
return curse.useCurse();
@Override
public void registerCurse(String curseName, CurseStrategy curse)
curses.put(curseName, curse);
Now, let’s update CurseStrategy
interfaces by adding method s @Autowired
comment:
public interface CurseStrategy
String useCurse();
String curseName();
@Autowired
default void registerMe(Wizard wizard)
wizard.registerCurse(curseName(), this);
At the moment of injecting addiction, we register our curse in Wizard
.
Advantages: No loops and no reliance on internal Spring bean names.
Against: No flaws, pure dark magic.
Conclusion
In this article, we explored the strategy pattern in the context of spring. We evaluated different strategy injection approaches and demonstrated an optimized solution using Spring capabilities.
The full source code for this article can be found on GitHub.