Closed
Description
This test works fine in Spring 6.2.2 but fails in 6.2.3.
It is probably related to this fix: #34298.
package com.example
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.springframework.core.convert.TypeDescriptor
import org.springframework.core.convert.converter.Converter
import org.springframework.core.convert.support.GenericConversionService
class GenericConversionServiceTest {
private val conversionService = GenericConversionService()
@Test
fun stringToListOfStringToAnyMapsConverterTest() {
conversionService.addConverter(StringToListOfStringToAnyMapsConverter)
val result = conversionService.convert(
"foo",
TypeDescriptor.valueOf(String::class.java),
TypeDescriptor.collection(List::class.java, TypeDescriptor.valueOf(Map::class.java))
) as List<Map<String, Any>>
assertEquals("foo", result.first()["bar"])
}
}
object StringToListOfStringToAnyMapsConverter : Converter<String, List<Map<String, Any>>> {
override fun convert(source: String): List<Map<String, Any>> {
return listOf(mapOf("bar" to source))
}
}
Exception:
No converter found capable of converting from type [java.lang.String] to type [java.util.List<java.util.Map<?, ?>>]
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [java.util.List<java.util.Map<?, ?>>]
at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:294)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:185)
at com.medisafe.chp.client.service.GenericConversionServiceTest.stringToListOfStringToAnyMapsConverterTest(GenericConversionServiceTest.kt:17)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
After a brief investigation, we found that this issue may be related to Kotlin declaration-site variance. Kotlin's List
is declared with <out E>
:
public actual interface List<out E> : Collection<E>
As a result, Converter<String, List<Map<String, Any>>>
compiles to something like Converter<String, List<? extends Map<String, ?>>>
. The fix introduced in #34298 makes this converter incompatible with the target type List<String, Map<?, ?>>
.
As a workaround we replaced Kotlin's List
with Java equivalent in the converter declaration. The following converter works correctly:
object StringToListOfStringToAnyMapsConverter : Converter<String, java.util.List<Map<String, Any>>> {
override fun convert(source: String): java.util.List<Map<String, Any>> {
return listOf(mapOf("bar" to source)) as java.util.List<Map<String, Any>>
}
}