This is a follow-up from the spring-boot gitter channel. When upgrading the spring-boot bom from 2.2.5.RELEASE to 2.2.6.RELEASE I get this exception:

Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'aBean' for bean class [com.example.demo.ABean] conflicts with existing, non-compatible bean definition of same name and class [com.example.demo.ABean]
    at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.checkCandidate(ClassPathBeanDefinitionScanner.java:349) ~[spring-context-5.2.5.RELEASE.jar:5.2.5.RELEASE]
    at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:287) ~[spring-context-5.2.5.RELEASE.jar:5.2.5.RELEASE]

The problem remains even when using 2.2.7.BUILD-SNAPSHOT. Here's what I've discovered while debugging it:

  • so what happens is that the classpath scanner detects my test configuration (which has @ComponentScan annotation) and that adds the aBean bean, but it also picks up the DemoApplication (the class with the main method). So it does classpath scanning for the DemoApplication again so the ComponentScanAnnotationParser performs the scan, and finds the aBean again and then calls the isCompatible method here: https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java#L364

This method returns false so an exception is raised.

  • It is important to have the spring-context-indexer dependency in your classpath. If you have it then the beans passed to isCompatible method will get getSource() return null.

  • it is also important to name the bean ABean. If I name it DemoBean then the exception does not happen - I think the component scan finds candidates sorted by beanName. So if ABean comes before DemoApplication then it will fail if I name it DemoBean then it will come after DemoApplication and it will not fail.

Here's a repository that reproduces the problem: https://github.com/ptahchiev/conflicting-bean

Comment From: jhoeller

This looks like a side effect of #24638, it seems we need to adapt the isCompatible check accordingly. We also need a backport here since the original change got backported as well (even if not released yet in the backport branches).

Comment From: jhoeller

@ptahchiev I see why the source would not match here... which now kicks in after the previous fix since both the existing and the new bean definition are of type ScannedGenericBeanDefinition now. However, shouldn't the definitions be equal according to the subsequent equals check in isCompatible? Any idea why they might differ?

Comment From: jhoeller

Also, why isn't the index kicking in for the second scan attempt? @snicoll I wonder whether this has to do with some Boot-specific behavior? This wouldn't have materialized before since the index effectively registered its bean definitions like explicit user definitions which always override... Now a mismatch between index and manual scan would show up here.

Comment From: ptahchiev

@jhoeller They differ because ScannedGenricBeanDefinition newDefinition has no attributes, while ScannedGenericBeanDefinition existingDefinition has 1 attribute in the attributes map:

org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass -> {BeanMetadataAttribute@5436} "metadata attribute 'org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass'"

Comment From: snicoll

@jhoeller there is nothing in Spring Boot that touches the index, it is 100% baked in a single place in the core framework.

Comment From: jhoeller

@ptahchiev Thanks for checking! Our equality check is clearly too rigid then, and we'll have to relax it for 5.2.6. We should also store the source for the index-originated bean definitions there, even if that will not necessarily be identical to the source resouce of an equivalent scan result.

@snicoll I just wonder why a second scan attempt would bypass the index in such a scenario. Is there anything in Boot that could influence this, e.g. custom include filters? The attached repro project looks straightforward enough to me, does it reveal anything out of the ordinary to you?

To be clear, even with a second scan bypassing the index, the outcome should not fail with a conflicting bean definition exception. So this is not immediately relevant for this issue, we'll have to fix in the bean definition compatibility check. It'd just be good to understand how we get there.

Comment From: snicoll

If you configure the scan with an include or exclude filter that the index cannot honour, we will fallback to regular component scan at runtime. But that's not what's happening here, the index is used in both cases.

Comment From: jhoeller

After checking with Stephane here, it turns out that the index is indeed used in both cases... but due to the lack of a source being stored and the difference of the bean definition attributes, it fails with a conflict. For aligned behavior between index and scan, all we need is consistent source comparisons. I'll fix this right away, to be released in 5.2.6 tomorrow.

Thanks for the immediate turnaround to both of you!

Comment From: jhoeller

@snicoll @ptahchiev This should be available in the latest 5.2.6 snapshot now. Please give it a try!

Comment From: snicoll

I did and the test in the sample Petar shared now works. Thank for the quick turnaround Juergen!