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.

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.

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");
  }
}
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);
  }
}

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").

@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.

The code examples are available in the GitHub code repository. The Junit test cases are here and examples classes are here.