We may not need the Spring Primary annotation on Beans of the same class, primarily when we use the @Bean annotation. When we have Java classes that implement the same interface, we will surely get the NoUniqueBeanDefinitionException exception. It happens when we annotate both classes with Component (or its derivatives) or create objects via the @Bean annotation. However, when we create two objects of the same type using @Bean, Spring may never throw the Exception, and we don’t need the Primary annotation.
We can try it! If you’re trying this out, you can review this post first to have a local working project.
NoUniqueBeanDefinitionException Example With Primary and Bean
For the first part, we have a Java interface and two classes that implement it. We use the Component annotation (or its derivatives) or create objects using the Bean annotation within a configuration file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | @Configuration @ComponentScan(basePackages = "com.turreta.learnspring") public class AppConfig { ... public House condoHouse() { return new CondoHouseImpl(); } public House landedHouse() { return new LandedHouseImpl(); } @Bean public HouseOwnerService ownerService(House house) { return new HouseOwnerService(house); } ... } |
We will get the following error when we run our codes with this configuration.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'ownerService' defined in com.turreta.learnspring.AppConfig: Unsatisfied dependency expressed through method 'ownerService' parameter 0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.turreta.learnspring.House' available: expected single matching bean but found 2: condoHouse,landedHouse at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800) at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:541) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1352) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1195) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:955) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:93) at com.turreta.learnspring.BareMinimumIoCApplication.main(BareMinimumIoCApplication.java:13) Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.turreta.learnspring.House' available: expected single matching bean but found 2: condoHouse,landedHouse at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:220) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1369) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1311) at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887) at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) ... 14 more |
To resolve this issue, we need to annotate one of the Beans using the Spring Primary annotation. For instance, we annotate the following method with the Primary annotation.
1 2 3 4 5 6 7 | ... @Bean @Primary public House landedHouse() { return new LandedHouseImpl(); } ... |
No NoUniqueBeanDefinitionException Spring Configuration With Beans of the same Class
The following code snippet does not throw the exception. It works; therefore, we don’t need to use the Spring Primary annotation with the Bean annotation for the objects of the same class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | @Configuration @ComponentScan(basePackages = "com.turreta.learnspring") public class AppConfig { ... @Bean public Dao anotherDao() { return new Dao("Second"); } @Bean public Dao dao() { return new Dao("First"); } @Bean public Service service(Dao dao) { return new Service(dao); } ... } |
Why does this work? When we run the codes, we get the following chunk of the output.
1 | In Dao First |
Why not “In Dao Second”? There is a simple explanation for that. But it can be tricky if we are unfamiliar with Spring’s basics.
When Spring generates an instance of classes, it provides them with bean names (or IDs). In this case, wherein we use the Bean annotation, Spring uses the method names from which it creates the objects. Hence, we have two objects of the same class with the names “dao” and anotherDao”. Then, the method that makes the Service object has a parameter named “dao.” As a result, Spring injects the bean with “dao” name.
When we change the last method’s parameter’s name, as shown in the following, we will get the NoUniqueBeanDefinitionException exception.
1 2 3 4 5 6 | ... @Bean public Service service(Dao anyDao) { return new Service(anyDao); } ... |
For more information, please consult the Spring documentation.