Spring DI. Multiple Interface Implementations with Strategy Pattern

Paul Ravvich
Spring Boot Tips and Tricks
2 min readApr 6, 2024

--

One of the remarkable features of the Spring Framework is its ability to manage multiple implementations of the same interface, selecting the necessary implementation based on the execution context. Let’s see how this can be effectively applied in practice.

Spring: Multiple Interface Implementations with Strategy Pattern

Hi, this is Paul, and welcome to my Spring guide. Today we will discuss how Dependency Injection in the Spring Framework works for multiple implementations of one interface.

Example: Data Conversion Service

Imagine we have a service for converting data from one format to another, for example, from JSON to PDF or DOC formats. We implement this using the strategy pattern, where each strategy corresponds to a specific output format.

Dependency Autowiring with @Component and @Autowired

The Spring Framework offers a convenient way to manage dependencies through autowiring. For each conversion strategy, we define a separate component:

public interface ReportConversionStrategy {
byte[] convertReport(ReportDto report);
}

@Component("PDF")
public class PdfConversionStrategy implements ReportConversionStrategy {
@Override
public byte[] convertReport(ReportDto report) {
return new byte[0];
}
}

@Component("DOC")
public class DocConversionStrategy implements ReportConversionStrategy {
@Override
public byte[] convertReport(ReportDto report) {
return new byte[0];
}
}

Using the @Autowired annotation, Spring automatically injects all available implementations of ReportConversionStrategy into our service, allowing us to dynamically select the necessary strategy based on input parameters.

Implementation of the Conversion Service

The conversion service integrates strategies and provides a universal interface for report conversion:

@Service
public class ReportConversionService {

private final Map<String, ReportConversionStrategy> strategies = new HashMap<>();

@Autowired
public ReportConversionService(List<ReportConversionStrategy> strategies) {
strategies.forEach(strategy ->
this.strategies.put(strategy.getClass().getAnnotation(Component.class).value(), strategy));
}

public byte[] convertReport(String format, ReportDto report) {
ReportConversionStrategy strategy = strategies.get(format.toUpperCase());
if (strategy == null) {
throw new UnsupportedOperationException("Unsupported format: " + format);
}
return strategy.convertReport(report);
}
}

In this service, we use a map to link the format with the corresponding conversion strategy, allowing us to easily expand supported formats and implementations without altering the core logic of the service.

Conclusion

The application of the dependency autowiring mechanism in Spring, combined with the strategy pattern, demonstrates how to flexibly and efficiently manage different implementations of interfaces. This approach not only simplifies the addition of new features and code maintenance but also enhances its readability and improves the overall architecture of the application, making it more adaptive and extensible.

Thank you for reading until the end. Before you go:

Paul Ravvich

--

--

Paul Ravvich
Spring Boot Tips and Tricks

Software Engineer with over 10 years of XP. Join me for tips on Programming, System Design, and productivity in tech! New articles every Tuesday and Thursday!