Partner – Jmix-Haulmont – NPI (cat= Spring Boot)
announcement - icon

Whether you're just starting out or have years of experience, Spring Boot is a great choice for building new applications with ease.

Jmix enhances the capabilities of Spring Boot developers, allowing them to build and deliver full-stack web applications without getting their hands dirty with frontend techs. It empowers you to create everything from simple web GUI CRUD applications to complex enterprise solutions, eliminating frontend/backend separation and related security concerns.

Jmix Platform includes a framework built on top of Spring Boot, JPA, and Vaadin, and comes with Jmix Studio, an IntelliJ IDEA plugin equipped with a suite of developer productivity tools. The platform also offers out-of-the-box add-ons for report generation, BPM, maps, and more that you can use in your Jmix application or as a separate service. All technologies are interconnected to empower a single Java developer to perform at the level of a whole team, with minimal knowledge required to get started.

Plus! Jmix can instantly generate a CRUD web application, complete with its JPA data model and UI, directly from an existing database. Then, continue developing with the help of Jmix Studio.

Develop smart, not hard!

>> Become a full-stack developer with Jmix

Course – RWSB – NPI (cat=REST/Spring Boot)
announcement - icon

Now that the new version of REST With Spring - “REST With Spring Boot” is finally out, the current price will be available until the 22nd of June, after which it will permanently increase by 50$

>> GET ACCESS NOW

Course – LS – All
announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Introduction

In this article, we’ll explore autowiring an interface with multiple implementations in Spring Boot, ways to do that, and some use cases. This is a powerful feature that allows developers to inject different implementations of the interface into the application dynamically.

2. Default Behavior

Usually, when we have multiple interface implementations and try to autowire that interface into the component, we’ll get an error – “required a single bean, but X were found”. The reason is simple: Spring doesn’t know which implementation we want to see in that component. Fortunately, Spring provides multiple tools to be more specific.

3. Introducing Qualifiers

With the @Qualifier annotation, we specify which bean we want to autowire among multiple candidates. We can apply it to the component itself to give it a custom qualifier name:

@Service
@Qualifier("goodServiceA-custom-name")
public class GoodServiceA implements GoodService {
    // implemantation
}

After that, we annotate parameters with @Qualifier to specify which implementation we want:

@Autowired
public SimpleQualifierController(
    @Qualifier("goodServiceA-custom-name") GoodService niceServiceA,
    @Qualifier("goodServiceB") GoodService niceServiceB,
    GoodService goodServiceC
) {
        this.goodServiceA = niceServiceA;
        this.goodServiceB = niceServiceB;
        this.goodServiceC = goodServiceC;
}

In the example above, we can see that we used our custom qualifier to autowire GoodServiceA. At the same time, for GoodServiceB, we do not have a custom qualifier:

@Service
public class GoodServiceB implements GoodService {
    // implementation
}

In this case, we autowired the component by class name. The qualifier for such autowiring should be in the camel case, for example “myAwesomeClass” is a valid qualifier if the class name was “MyAwesomeClass.

The third parameter in the above code is even more interesting. We didn’t even need to annotate it with @Qualifier, because Spring will try to autowire the component by parameter name by default, and if GoodServiceC exists we’ll avoid the error:

@Service 
public class GoodServiceC implements GoodService { 
    // implementation 
}

4. Primary Component

Furthermore, we can annotate one of the implementations with @Primary. Spring will use this implementation if there are multiple candidates and autowiring by parameter name or a qualifier is not applicable:

@Primary
@Service
public class GoodServiceC implements GoodService {
    // implementation
}

It is useful when we frequently use one of the implementations and helps to avoid the “required a single bean” error.

5. Profiles

It is possible to use Spring profiles to decide which component to autowire. For example, we may have a FileStorage interface with two implementations – S3FileStorage and AzureFileStorage. We can make S3FileStorage active only on the prod profile and AzureFileStorage only for the dev profile.

@Service
@Profile("dev")
public class AzureFileStorage implements FileStorage {
    // implementation
}

@Service
@Profile("prod")
public class S3FileStorage implements FileStorage {
    // implementation
}

6. Autowire Implementations Into a Collection

Spring allows us to inject all available beans of a specific type into a collection. Here is how we autowire all implementations of the GoodService into a list:

@Autowired
public SimpleCollectionController(List<GoodService> goodServices) {
    this.goodServices = goodServices;
}

Also, we can autowire implementations into a set, a map, or an array. When using a map, the format typically is Map<String, GoodService>, where the keys are the names of the beans, and the values are the bean instances themselves:

@Autowired
public SimpleCollectionController(Map<String, GoodService> goodServiceMap) {
        this.goodServiceMap = goodServiceMap;
}

public void printAllHellos() {
    String messageA = goodServiceMap.get("goodServiceA").getHelloMessage();
    String messageB = goodServiceMap.get("goodServiceB").getHelloMessage();

    // print messages
}

Important note: Spring will autowire all candidate beans into a collection regardless of qualifiers or parameter names, as long as they are active. It ignores beans annotated with @Profile that do not match the current profile. Similarly, Spring includes beans annotated with @Conditional only if the conditions are met (more details in the next section).

7. Advanced Control

Spring allows us to have additional control over which candidates are selected for autowiring.

For more precise conditions on which bean becomes a candidate for autowiring, we can annotate them with @Conditional. It should have a parameter with a class that implements the Condition (it is a functional interface). For example, here is the Condition that checks if the operating system is Windows:

public class OnWindowsCondition implements Condition {
    @Override 
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return context.getEnvironment().getProperty("os.name").toLowerCase().contains("windows");
    } 
}

Here is how we annotate our component with @Conditional:

@Component 
@Conditional(OnWindowsCondition.class) 
public class WindowsFileService implements FileService {
    @Override 
    public void readFile() {
        // implementation
    } 
}

In this example, WindowsFileService will become a candidate for autowiring only if matches() in OnWindowsCondition returns true.

We should be careful with @Conditional annotations for non-collection autowiring since multiple beans that match the condition will cause an error.

Also, we will get an error if no candidates are found. Because of this, when integrating @Conditional with autowiring, it makes sense to set an optional injection. This ensures that the application can still proceed without throwing an error if it does not find a suitable bean. There are two approaches to achieve this:

@Autowired(required = false)
private GoodService goodService; // not very safe, we should check this for null

@Autowired
private Optional<GoodService> goodService; // safer way

When we autowire into the collection, we can specify the order of the components by using @Order annotation:

@Order(2) 
public class GoodServiceA implements GoodService { 
    // implementation
 } 

@Order(1) 
public class GoodServiceB implements GoodService {
    // implementation 
}

If we try to autowire List<GoodService>, GoodServiceB will be placed before GoodServiceA. Important note: @Order doesn’t work when we are autowiring into the Set.

8. Conclusion

In this article, we discussed the tools Spring provides for the management of the multiple implementations of the interface during autowiring. These tools and techniques enable a more dynamic approach when designing a Spring Boot application. However, like with every instrument, we should ensure their necessity, as careless use can introduce bugs and complicate long-term support.

As always, the examples are available over on GitHub.

Course – RWSB – NPI (cat=REST/Spring/Spring Boot)
announcement - icon

Now that the new version of REST With Spring - “REST With Spring Boot” is finally out, the current price will be available until the 22nd of June, after which it will permanently increase by 50$

>> GET ACCESS NOW

Course – LS – All
announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

res – REST with Spring (eBook) (everywhere)
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments