Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Regression in 6.2.3: No unique bean available for injection point with unresolvable generics #34541

Closed
ML-Marco opened this issue Mar 5, 2025 · 13 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: regression A bug that is also a regression
Milestone

Comments

@ML-Marco
Copy link

ML-Marco commented Mar 5, 2025

Autowiring does not find a suitable candidate in 6.2.3 any more. Throws an exception on startup. This used to work with all previous versions, including 6.2.2.

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'spring.PaymentCreator<spring.Payment, spring.PaymentCreatorParameter<spring.Payment>>' available: expected single matching bean but found 2: bankTransferCreator,electronicCashPaymentCreator

Reproducer: bug.zip

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Mar 5, 2025
@sbrannen sbrannen added the in: core Issues in core modules (aop, beans, core, context, expression) label Mar 5, 2025
@sbrannen sbrannen self-assigned this Mar 5, 2025
@sbrannen sbrannen added type: regression A bug that is also a regression and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Mar 5, 2025
@sbrannen sbrannen added this to the 6.2.4 milestone Mar 5, 2025
@sbrannen
Copy link
Member

sbrannen commented Mar 5, 2025

Thanks for raising the issue, @ML-Marco.

I have confirmed that your sample application works with 6.1.17 but fails with 6.2.3 and 6.2.4-SNAPSHOT with the following exception.

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'spring.PaymentCreator<spring.Payment, spring.PaymentCreatorParameter<spring.Payment>>' available: expected single matching bean but found 2: bankTransferCreator,electronicCashPaymentCreator
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:218) ~[spring-beans-6.2.4-SNAPSHOT.jar:6.2.4-SNAPSHOT]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1644) ~[spring-beans-6.2.4-SNAPSHOT.jar:6.2.4-SNAPSHOT]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1555) ~[spring-beans-6.2.4-SNAPSHOT.jar:6.2.4-SNAPSHOT]
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:785) ~[spring-beans-6.2.4-SNAPSHOT.jar:6.2.4-SNAPSHOT]
	... 35 more

We'll look into it.

@sbrannen sbrannen removed their assignment Mar 5, 2025
@ML-Marco
Copy link
Author

ML-Marco commented Mar 5, 2025

@sbrannen It doesn't fail with 6.2.2. This might help to find the commit that broke it.

@sbrannen
Copy link
Member

sbrannen commented Mar 5, 2025

@sbrannen It also fails with 6.2.2. This might help to find the commit that broke it.

I apologize: I was unclear.

In my tests, it fails against 6.2.0 through 6.2.4-SNAPSHOT.

So, it appears that the regression was introduced during the 6.2 milestone/RC phases.

@jhoeller jhoeller changed the title Regression in 6.2.3: No qualifying bean of type xxx available Regression in 6.2: No unique bean available for injection point with unresolvable generics Mar 5, 2025
@jhoeller jhoeller self-assigned this Mar 5, 2025
@jhoeller
Copy link
Contributor

jhoeller commented Mar 5, 2025

It's worth noting that the algorithm actually finds two candidates rather than none, so it's a not-unique scenario where it ends up with two candidates which are considered equivalent in terms of the type match. From an initial glance at the repro project, it's not clear to me which one of the two beans is actually meant to be injected there (i.e. which one got injected with the algorithm as it was in 6.1) since both of them look like a match to me. Any insight in terms of the intended design there from your side, @ML-Marco?

@ML-Marco
Copy link
Author

ML-Marco commented Mar 5, 2025

@jhoeller I corrected my statement above. The reproducer does work with 6.2.2 and fails with 6.2.3.
I guess the one that was found in version 6.2.2 which is the base class (PaymentCreator) and not one of its subclasses.

@sbrannen
Copy link
Member

sbrannen commented Mar 5, 2025

@jhoeller I corrected my statement above. The reproducer does work with 6.2.2 and fails with 6.2.3.

Oops. I need to correct my above statement as well. I must have had an issue with caching or something. Here are my findings.

  • 6.1.17: works
  • 6.2.0: fails
  • 6.2.1: works
  • 6.2.2: works
  • 6.2.3: fails
  • 6.2.4-SNAPSHOT: fails

And when I say "works", I simply mean it did not throw a NoUniqueBeanDefinitionException at startup.

@jhoeller jhoeller changed the title Regression in 6.2: No unique bean available for injection point with unresolvable generics Regression in 6.2.3: No unique bean available for injection point with unresolvable generics Mar 5, 2025
@jhoeller
Copy link
Contributor

jhoeller commented Mar 5, 2025

Given the actual regression in 6.2.3, I have a suspicion that it's caused by #34300 where the handling of nested variable bounds became stricter. I'll try to reproduce this in a unit test.

@jhoeller
Copy link
Contributor

jhoeller commented Mar 5, 2025

A quick update before I resume tomorrow: This turns out to be rather puzzling. Of the 4 PaymentCreator beans, we used to consider 3 as assignable (BankTransferCreator, ElectronicCashPaymentCreator, PaymentCreator - choosing the latter based on the bean name match) and now only consider 2 as assignable (BankTransferCreator, ElectronicCashPaymentCreator but not plain PaymentCreator with its unresolved generics anymore). However, in actual Java source code, none of those seems to be actually assignable without forcing an unchecked assignment. Something is odd about the generic declaration there with its interdependent type variables.

The good news is that I can reproduce this with plain ResolvableType.isAssignableFrom calls. The effect is rather obvious, just the cause of the regression and - more importantly - the expected correct behavior is unclear. The following reproduces the effect above when testing against 6.2.3 versus 6.2.2. And alternatively, try to assign an instance of one of those classes to the field in Java code...

PaymentCreator<? extends Payment, PaymentCreatorParameter<? extends Payment>> paymentCreator;

@Test
void testResolvableType() throws Exception {
	ResolvableType field = ResolvableType.forField(getClass().getDeclaredField("paymentCreator"));
	System.out.println("BankTransferCreator: " + field.isAssignableFrom(BankTransferCreator.class));
	System.out.println("DirectDebitCreator: " + field.isAssignableFrom(DirectDebitCreator.class));
	System.out.println("ElectronicCashPaymentCreator: " + field.isAssignableFrom(ElectronicCashPaymentCreator.class));
	System.out.println("PaymentCreator: " + field.isAssignableFrom(PaymentCreator.class));
}

@sbrannen
Copy link
Member

sbrannen commented Mar 6, 2025

However, in actual Java source code, none of those seems to be actually assignable without forcing an unchecked assignment. Something is odd about the generic declaration there with its interdependent type variables.

Indeed, in Java source code only the concrete PaymentCreator is assignable to the paymentCreator field in TestController (with an unchecked warning as you mentioned).

The other 3 beans would only be assignable if the declaration of that field were changed to use ? extends PaymentCreatorParameter in order to match on any of the subtypes of PaymentCreatorParameter.

For example, the following 4 assignments compile for me.

PaymentCreator<? extends Payment, PaymentCreatorParameter<? extends Payment>> paymentCreator = new PaymentCreator();

PaymentCreator<? extends Payment, ? extends PaymentCreatorParameter<? extends Payment>> directDebitCreator = new DirectDebitCreator();

PaymentCreator<? extends Payment, ? extends PaymentCreatorParameter<? extends Payment>> bankTransferCreator = new BankTransferCreator();

PaymentCreator<? extends Payment, ? extends PaymentCreatorParameter<? extends Payment>> electronicCashPaymentCreator = new ElectronicCashPaymentCreator();

@jhoeller
Copy link
Contributor

jhoeller commented Mar 6, 2025

As far as I see now, we tolerate a direct PaymentCreatorParameter declaration versus an ? extends PaymentCreatorParameter clause in the generic signature when it affects the bound type directly - whereas for a subclass like DirectDebitCreatorParameter as declared in DirectDebitCreator, we insist on the original Java language semantics of ? extends PaymentCreatorParameter to match (which is why it is the odd one out there). This lenient matching of a bound type is a bit unorthodox but worth strategically retaining since a lot of application code potentially relies on it for many years already.

So for some reason, we lost that tolerance for the plain PaymentCreator case in 6.2.3 which we need to restore. I'll revise this for 6.2.4, addressing that immediate regression.

@jhoeller
Copy link
Contributor

jhoeller commented Mar 6, 2025

The revision is available in the latest 6.2.4 snapshot now. Please give it an early try if you have the chance...

@ML-Marco
Copy link
Author

ML-Marco commented Mar 6, 2025

Tried it, works! Thanks a lot for the quick response.

@jhoeller
Copy link
Contributor

jhoeller commented Mar 6, 2025

Good to hear! Thanks for the immediate feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: regression A bug that is also a regression
Projects
None yet
Development

No branches or pull requests

4 participants