Affects: 5.2.5.RELEASE

In my project, I have a bean of type Parametrized<? extends Number>.

A second bean method that takes a Parametrized<S> where <S extends Number> is not autowired correctly, because the wildcard does not match the type variable (even though their upper bounds are identical). The bean is taken into consideration as an autowiring candidate, but is ruled out as the generic parameters are determined to not match by the GenericTypeAwareAutowireCandidateResolver.

I've attached a minimal project that demonstrates the behaviour: typeVariableMismatch.zip

Here are the interesting parts:

@Bean @Primary
public Parametrized<? extends Number> expectedWinner() {
  return new Parametrized<>(7);
}

@Bean @Profile("alternateUniverse")
public <S extends Number> Parametrized<S> alternateWinner() {
  return new Parametrized<>(13);
}

@Bean @Profile("!alternateUniverse")
public Unparametrized42 actualWinner() {
  return new Unparametrized42();
}

@Bean
public <S extends Number> String consumer(Parametrized<S> param) {
  return "The lucky number is: " + param.number;
}
@Autowired
public void printString(String s) {
  Log log = LogFactory.getLog(TypeVariableMismatchApplication.class);
  log.info("The actual winner is: " + s);
  // note that the types are assignable according to the java compiler:
  log.info("The expected winner would have been: " + consumer(expectedWinner()));
}
static class Parametrized<S extends Number> {
  public final Number number;

  public Parametrized(Number number) {
    this.number = number;
  }
}

static class Unparametrized42 extends Parametrized<Integer> {
  public Unparametrized42() {
    super(42);
  }
}

This autowires the actualWinner bean instead of the expectedWinner bean. The alternateWinner works as well.

Note that the Java Compiler assigns the wildcard type just fine (as can seen in the printString method).

Comment From: snicoll

I don't think that's right to expect the container to handle such a situation. You have a candidate bean not carrying generic type information and one carrying it but the injection point determines the bound at the method level.

@Primary is designed to select among a set of type matches, typically beans of the same declared type, not a mix of unresolved generics with resolved generics. In this case it's not even considered due to our unresolved generics handling.