Affects: spring-framework 6.1.12

We are trying to manage a fleet of similarly-typed beans to inject. We've found a surprising circumstance where the existence of a dependency on one bean ("A") will cause another bean ("B") to vary how you may inject it.

Below please find a test case showing the problem. The test case is attempting to inject @Named("B") IImpl<String> bStr, a concrete subtype of an interface I<String>. The subtype is desired to access test-stubbing features not present on the interface.

A different component is declaring @Named("A") I<UUID> aUuid. These definitions seem unrelated, since both the name (A vs B) and type (I<String> vs I<UUID>) differ.

The test fails as is:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'BeanInjectTest$IImpl<java.lang.String>' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@jakarta.inject.Inject(), @jakarta.inject.Named("B")}
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1880)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1406)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1353)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:785)
...
  • Changing injection of @Named("B") IImpl<String> to @Named("B") I<String> works, but then you cannot access the subtype's features
  • Commenting out the @Import(MyConfig.ConsumeA.class) causes the injection of B to work, despite only changing the use of A not B!

This behavior seems very surprising to me, and feels like a bug. Thank you for your thoughts.

@SpringJUnitConfig({
    BeanInjectTest.MyConfig.class,
})
public class BeanInjectTest {
    @Inject
    @Named("B")
    IImpl<String> bStr;

    @Test
    public void injectB() {
        assertThat(bStr).isInstanceOf(IImpl.class);
    }

    @Import({
        MyConfig.ConsumeA.class,
    })
    public static class MyConfig {
        @Bean
        @Named("B")
        public I<String> bStr() {
            return new IImpl<>();
        }

        @Named("A")
        @Bean
        public I<UUID> aUuid() {
            return new IImpl<>();
        }

        public static class ConsumeA {
            public ConsumeA(
                    @Named("A")
                    final I<UUID> aUuid) {
                System.err.println("aUuid: " + aUuid);
            }
        }
    }

    public interface I<T> {}
    public static class IImpl<T> implements I<T> {}
}

Comment From: sbrannen

Hi @stevenschlansker,

Thanks for raising the issue and providing the example.

I have confirmed that the test fails on Spring Framework 5.3.x through 6.1.x (with javax.inject annotations on 5.3.x and otherwise with jakarta.inject annotations); however, the test passes when run against 6.2 (main branch).

Thus, the behavior appears to be a longstanding limitation that has been addressed recently.

@jhoeller, thoughts?