Resolving Ambiguity In Spring Beans

So far in this series, we have learned how we can leverage auto-configuration to enable Spring to create beans and wire them together in a Spring container. We have also learned how we can use JavaConfig to configure Java Beans when Autowiring is not sufficient. It is convenient for a Spring to identify beans to resolve dependencies when there is exactly one matching bean in a container. But generally in an enterprise-level Java Application, it is not the case. We will have multiple components of the same type within an application context for different purposes. When there is more than one matching bean, the ambiguity doesn’t allow Spring to autowire the properties, constructor arguments or method parameters. Let us understand this with the help of an example below.

package com.lifeinhurry.person;
public Interface Subject{
    public void attend();
}
package com.lifeinurry.person;
@Component
public class History implements Subject{
     private String content = "history chapter";
     @Override
     public void attend(){
         System.out.print("Reading :" + content);
     }
}
package com.lifeinurry.person;
@Component
public class Literature implements Subject{
     private String content = "literature chapter";
     @Override
     public void attend(){
         System.out.print("Reading :" + content);
     }
}

In the above code snippet, we have an interface Subject which is implemented by two classes History and Literature . Both of these classes are annotated by @Component which means both of them will be identified during component scanning and will be created as beans in a Spring container. Let us consider a class Student example who has a dependency on the type Subject .

package com.lifeinhurry.person;
public class Student implements Person{
    private Subject subject;
    @Autowired
    public Student (Subject subject) {
         this.subject= subject;
    }
    public void attendSubject() {
         subject.attend();
    }
}

In the above example, we have a class Student which depends on an object of type Subject . When Spring try to autowire the subject parameter in the constructor, it won’t find single unambiguous choice and throws an exception NoUniqueBeanDefinitionException.

nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException:
    No qualifying bean of type [com.lifeinhurry.person.Subject] is defined:
        expected single matching bean but found 2: history,literature

For such issues, Spring offers a couple of options. We can declare one of the beans as the primary choice in such cases or we can use  @Qualifier annotation to help Spring narrow down its choices among the candidate beans. Let us understand these options in more detail now.

1. Using @Primary Annotation

When you have such cases when there are multiple beans, you can resolve ambiguity by designating one of the bean as primary bean using annotation @Primary. Spring will automatically choose which you have annotated as @Primary  in case of ambiguity.

//Option 1
@Component
@Primary
public class History implements Subject { ... }

//Option 2
@Bean
@Primary
public Subject history() {
    return new History();
}

In the above example, we see that we can annotate bean as @Primary alongside @Component in case we are using auto-configuration. Also, we can use it alongside @Bean while using JavaConfig. The important thing to keep in mind is that we should be very careful to not annotate more than one bean of the same type as @Primary .

2. Using @Qualifier Annotation

The big limitation we see in @Primary is that it doesn’t really narrow down the ambiguity but just marks the preferred one. On the other hand, we need to very careful to not annotate more than one bean as @Primary which is error-prone.

Whereas, @Qualifier narrows down the choices and arrives at the single bean which meets the qualifications. We can keep on applying multiple qualifiers until we narrow it down to a single choice. The @Qualifier annotation can be used alongside @Autowired to specify which bean you want Spring to autowire. Let us understand this with the help of an example.

@Autowired
@Qualifier("history")
public void setSubject(Subject subject) {
   this.subject= subject;
}

This is the most simple example of @Qualifier . Spring will search all component scanned beans in the container with the ID  history and tries to wire it in the method parameter. Also, we can give each component a qualifying name along with @Component or @Bean instead of depending on the ID of a bean.

//Specifying Qualifier with @Component
@Component
@Qualifier("customName")
public class History implements Subject{...}

//Specifying Qualifier with @Bean
@Bean
@Qualifier("customName")
public Subject history() {
    return new History();
}

//Wiring the above components using "customName"
@Autowired 
@Qualifier("customName") 
public void setSubject(Subject subject) 
{ 
    this.subject= subject; 
}

In the example, the qualifier customName  is given to the bean created. The autowiring of bean is not tightly coupled with the ID(which usually is a class name in camel case) of the component. So even if you change the class name in the future, the bean autowiring is not affected.

3. Creating Custom @Qualifier Annotation

The above two options were decent for basic resolution of ambiguity. But when you have large enterprise-level Java Application it becomes very difficult to track qualifier’s name which is a plain string and not type-safe. There is another option for this problem which is less error-prone. We can create custom qualifier annotations to represent your bean. Let us understand this with an example.

@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface CustomAnnotation{ }

In the example above, we created an annotation which itself is annotated with @Qualifier  so rather than using @Qualifier("customName") , we can directly use @CustomAnnotation to qualify the bean.

@Component
@CustomAnnotation
public class History implements Subject{...}


//Wiring the above components
@Autowired 
@CustomAnnotation 
public void setSubject(Subject subject) 
{ 
    this.subject= subject; 
}

Conclusion

In this post, we learned how we can resolve ambiguity in a Spring Application when there are more than one beans of the same type. We saw there are different options for this kind of problem and we can choose any one of them depending upon our use-case. I hope we are clear how we can deal with ambiguity in beans when Spring complains about NoUniqueBeanDefinitionException .