Introduction
Autowiring by type can lead to ambiguity when we have multiple beans of the same base class type. Because there are multiple beans of the same type, it is important to have control over the bean selection process.
Spring provides @Primary
annotation which declares a specific bean as primary, which means primary bean will be given higher preferences when autowiring to a single-valued dependency.
On the other hand, Spring also provides @Qualifier
annotation in which the specific bean name is mentioned in its argument. Based on the argument, a specific bean is selected.
If you are new to Spring Framework, check out other articles related to dependency injection and autowiring.
- Inversion of Control and Dependency Injection in Spring
- Dependency Injection: @Autowired, @Resource and @Inject
1. Spring @Primary annotation – bean preference
When there are multiple beans of the same type, @Primary
annotation can be used to give a higher preference to a specific bean. We will explore the beans registration in Spring in 2 ways, Java-based configurations and annotation-based.
a). Use @Primary with @Component annotation
Let us say we have a base class FileReader
and two concrete classes PdfFileReader
and WordFileReader
implements the base class. When we register these concrete classes in spring, we get 2 beans of the type FileReader
but of different names pdfFileReader
and wordFileReader
.
If we try to Autowire FileReader
we will get Exception as there are multiple beans of the same type. To solve this we will assign higher preference to one of the bean class sayPdfFileReader
by annotating it with @Primary
. The PdfFileReader
class is annotated with @Primary
, indicating that it is the primary bean to be injected when there are multiple candidate beans that match a given injection point. Let’s break down the usage of @Primary
and how it interacts with @Qualifier
.
package basic.ioc.wiring.finetune;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
@Primary
@Component
public class PdfFileReader implements FileReader {
@Override
public void print() {
System.out.println("Inside PdfFileReader");
}
}
The class WordFileReader
is not using @Primary annotation. The FileReader interface and the configs are shown below.
package basic.ioc.wiring.finetune;
import org.springframework.stereotype.Component;
@Component
public class WordFileReader implements FileReader {
@Override
public void print() {
System.out.println("Inside WordFileReader");
}
}
package basic.ioc.wiring.finetune;
public interface FileReader {
void print();
}
package basic.ioc.wiring.finetune;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@ComponentScan("basic.ioc.wiring.finetune")
@Configuration
public class ConfigWiring {
//extra configs goes here
}
Since the PdfFileReader
is annotated with @Primary
, when we autowire a field of type FileReader
, the Spring IoC will inject the PdfFileReader
bean. This is shown in the code below.
package basic.ioc.wiring.finetune;
import org.junit.Assert;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@SpringJUnitConfig(ConfigWiring.class)
public class FineTuneAutowiring {
@Autowired
private FileReader fileReader;
@Test
public void testFileReader() {
Assert.assertNotNull(fileReader);
Assert.assertEquals(fileReader.getClass(), PdfFileReader.class);
}
}
However, if we want to inject WordFileReader
for some reason, we need to make use of the @Qualifier
annotation. This is explained in details below.
b). Bean ambiguity – NoUniqueBeanDefinitionException
If you do not specify the @Primary
annotation, there will be ambiguity as there are 2 beans of the same type FileReader
. When you try to inject such a bean, spring will throw an exception as below.
SEVERE: Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@506ae4d4] to prepare test instance [basic.ioc.wiring.finetune.FineTuneAutowiring@5b202a3a] org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'basic.ioc.wiring.finetune.FineTuneAutowiring': Unsatisfied dependency expressed through field 'fileReader'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'basic.ioc.wiring.finetune.FileReader' available: expected single matching bean but found 2: pdfFileReader,wordFileReader
c). Use @Primary annotation with @Bean
The @Primary
annotation can also be used in Java-based bean configurations declared using @Bean
. In the below example, there are 3 insurance instances with the bean names insurance1
, insurance2
and insurance3
. The bean insurance1
is annotated with @Primary
, so that this bean preferred when there is ambiguity.
package basic.ioc.wiring.finetune;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@ComponentScan("basic.ioc.wiring.finetune")
@Configuration
public class ConfigWiring {
//bean name = insurance1
@Bean
@Primary
public Insurance insurance1(){
Insurance healthInsurance = new Insurance();
healthInsurance.setType("Health Insurance");
return healthInsurance;
}
//bean name = insurance2
@Bean
public Insurance insurance2(){
Insurance termInsurance = new Insurance();
termInsurance.setType("Term Insurance");
return termInsurance;
}
//bean name = insurance3
@Bean
public Insurance insurance3() {
Insurance generalInsurance = new Insurance();
generalInsurance.setType("General Insurance");
return generalInsurance;
}
}
package basic.ioc.wiring.finetune;
public class Insurance {
private String type;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
Since there are multiple insurance
beans, it can end up causing Exception related to bean ambiguity. However, we have instance1 annotated with @Primary, so by default instance1
bean will be injected unless a different bean name is mentioned.
package basic.ioc.wiring.finetune;
import org.junit.Assert;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@SpringJUnitConfig(ConfigWiring.class)
public class FineTuneAutowiring {
@Autowired
private Insurance insurance;
@Test
public void testInsuranceBean(){
Assert.assertNotNull(insurance);
Assert.assertEquals(insurance.getType(), "Health Insurance");
}
}
d). The source code of @Primary annotation
From the code below, it is clear that @Primary annotation is used on Class, interface (including annotation type), or enum declaration and methods (when used with bean).
package org.springframework.context.annotation;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Primary {
}
2. Spring @Qualifier annotation – select bean
The @Autowired annotation does a great job of resolving the dependency. But it throws NoUniqueBeanDefinitionException
when it can not resolve a specific bean due to ambiguity. This exception occurs when there is more than one bean of the same type is available in the container.
The @Primary annotation does a great job of giving a higher preference to a bean when there are multiple beans of the same types registered in the container. But it does not allow you to select a specific bean.
When you need more control over the bean selection process, you need to use Spring’s @Qualifier
annotation. Irrespective of whether any of the beans is annotated with @Primary or not, the @Qualifier annotation will help you select the specific bean you want to inject.
@SpringJUnitConfig(ConfigWiring.class)
public class FineTuneAutowiring {
@Autowired
@Qualifier("wordFileReader")
private FileReader fileReader2;
@Test
public void testWordFileReader() {
Assert.assertNotNull(fileReader2);
Assert.assertEquals(fileReader2.getClass(), WordFileReader.class);
}
@Autowired
@Qualifier("insurance3")
private Insurance myInsurance;
@Test
public void testInsuranceWithQualifier(){
Assert.assertNotNull(myInsurance);
Assert.assertEquals("General Insurance", myInsurance.getType());
}
}
I have used the previous code examples in this code snippets to demonstrates how to pass the bean name in the @Qualifier
annotation. As you can see in the above example, @Qualifier("wordFileReader")
injects the wordFileReader
and @Qualifier("insurance3")
tells the Spring to inject insurance3
bean.
a). Use @Qualifier with @Component
You can also use the @Qualifier
with @Component
level to assign an extra name to the bean. A simple example is shown below.
@Component
@Qualifier("restData")
public class RestData implements Endpoint {
//...
}
@Component
@Qualifier("graphData")
public class GraphQlData implements Endpoint {
//...
}
@Autowired
@Qualifier("restData")
private Endpoint endpoint;
b). Use @Qualifier with @Bean
You can similarly combine @Qualifier
annotation with the @Bean
annotation to assign an extra name to the bean. In the below code example, it is possible to use either @Qualifier("insurance3")
or @Qualifier("generalInsurance")
because of the presence of @Primary annotation.
@ComponentScan("basic.ioc.wiringfinetune")
@Configuration
public class ConfigWiring {
//bean name = insurance3
@Bean
@Qualifier("generalInsurance")
public Insurance insurance3() {
Insurance generalInsurance = new Insurance();
generalInsurance.setType("General Insurance");
return generalInsurance;
}
}
@SpringJUnitConfig(ConfigWiring.class)
public class FineTuneAutowiring {
@Autowired
//@Qualifier("insurance3") - this will work
@Qualifier("generalInsurance")
private Insurance myInsurance;
@Test
public void testInsuranceWithQualifier(){
Assert.assertNotNull(myInsurance);
Assert.assertEquals("General Insurance", myInsurance.getType());
}
}
c). Source code of @Qualifier annotation
As shown below, @Qualifier can be used on property, method arguments, methods, classes, interface, enums, custom annotations, etc.
package org.springframework.beans.factory.annotation;
@Target({ElementType.FIELD, ElementType.METHOD,
ElementType.PARAMETER, ElementType.TYPE,
ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {
String value() default "";
}
Conclusion
Both the annotations @Primary
and @Qualifier
in Spring play a great role in resolving ambiguity, but they are for a different purpose. The @Primary
annotation sets the bean preference and it is used with the @Bean
or @Component
etc stereotype annotations. On the other hand, @Qualifier
is usually used with @Autowired
or @Inject
etc annotations.
@Primary
: This annotation is used to indicate the primary bean to be injected when there are multiple candidates of the same type. It sets the default bean preference. It’s typically used in conjunction with@Bean
or@Component
annotations to mark a bean as the primary candidate for autowiring. When multiple beans of the same type are present in the application context, the one annotated with@Primary
will be chosen by default for injection.@Qualifier
: This annotation is used to specify a particular bean to be injected when there are multiple beans of the same type. It allows for more fine-grained control over bean injection by providing a qualifier to disambiguate between multiple beans.@Qualifier
is typically used in conjunction with@Autowired
or@Inject
annotations to specify the name or qualifier value of the desired bean to be injected.
In summary, while both @Primary
and @Qualifier
annotations help resolve ambiguity in bean injection, they serve different purposes and are used in different contexts within the Spring framework. @Primary
sets the default preference for a bean, while @Qualifier
allows for explicit selection of a bean based on its qualifier value. Understanding when and how to use these annotations is essential for effective bean wiring and dependency injection in Spring applications.
The code examples are available in the GitHub code repository. The Junit test cases are here and examples classes are here.