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.