Affects: 5.3.x

For background, this problem was identified while looking at https://stackoverflow.com/questions/77233335/how-to-register-new-collection-factories-in-spring-boot.

With a Converter implemented in Kotlin for a List to Guava's ImmutableList conversion:

class ListToImmutableListConverter : Converter<List<Any>, ImmutableList<Any>> {
    override fun convert(source: List<Any>): ImmutableList<Any> = ImmutableList.copyOf(source)
}

ResolvableType resolves the first Any to ? and the second to Object:

val type: ResolvableType = ResolvableType.forClass(ListToImmutableListConverter::class.java).`as`(Converter::class.java)
println(type)
org.springframework.core.convert.converter.Converter<java.util.List<?>, com.google.common.collect.ImmutableList<java.lang.Object>>

Contrastingly, using Java and ? results in both being resolved to ?:

static class ListToImmutableList implements Converter<List<?>, ImmutableList<?>>  {

    @Override
    public ImmutableList<?> convert(List<?> source) {
        return ImmutableList.copyOf(source);
    }

}
ResolvableType type = ResolvableType.forClass(ListToImmutableList.class).as(Converter.class);
System.out.println(type);
org.springframework.core.convert.converter.Converter<java.util.List<?>, com.google.common.collect.ImmutableList<?>>

Resolving the target type to ImmutableList<Object> rather than ImmutableList<?> is problematic as it prevents the converter from matching a List<String> to ImmutableList<String> conversion.

Comment From: sdeleuze

I suspect this kind of behavior is caused by Kotlin declaration site variance. When you declare List<Any> in Kotlin, List is not java.util.List but kotlin.collections.List which is declared as List<out E> so conceptually the equivalent of java.util.List<? extends E>, so to get compatible generics I would suggest to try to use ImmutableList<out Any> (usage site variance as the type is a Java type that we just use) in order to have the assignability to Kotlin List<E>:

class ListToImmutableListConverter : Converter<List<Any>, ImmutableList<out Any>> {
    override fun convert(source: List<Any>): ImmutableList<out Any> = ImmutableList.copyOf(source)
}

I know this is pretty subtle but this is how Kotlin deals with lists and when mixing with Java list, there is a need to be aware of the declaration type variance to get type comparison working as expected. I am not sure there is something to fix on Spring Framework side for this specific use case, but please let me know what you think about it.

Comment From: wilkinsona

Thanks very much, @sdeleuze. That fixes the problem.

If there are other areas in Framework where type comparison may not work when using Any, I wonder if this could be documented within https://docs.spring.io/spring-framework/reference/languages/kotlin.html? It's rather low-level but it would have been a long time, if ever, before I figured it out without your guidance.

Comment From: sdeleuze

Sure, that's a good idea, and that will give me an opportunity to document some guidance for injection related to #22313. As a consequence, I turn this issue into a documentation one.

Comment From: wilkinsona

An update from the question on Stack Overflow: using *, for example ImmutableList<*>, also works.