Having a simple Kotlin Spring Boot application (link), importing any auto-configuration with a non-bean method (e.g. a private auxiliary method) throws ClassNotFoundException
:
@SpringBootApplication(proxyBeanMethods = false)
// @ImportAutoConfiguration(MyJavaConfiguration::class)
@ImportAutoConfiguration(MyKotlinConfiguration::class)
@ImportRuntimeHints(NativeRuntimeHints::class)
class Application
fun main(args: Array<String>) {
runApplication<Application>(*args)
}
@AutoConfiguration
class MyKotlinConfiguration {
@Bean
fun foo() = Foo(bar())
private fun bar() = Bar()
}
class Foo(private val bar: Bar) {
fun foo() = "Hello ${bar.bar()}"
}
class Bar {
fun bar(): String = "bar"
}
Stacktrace:
java.lang.ClassNotFoundException: com.example.demo.Bar
at java.base@19.0.1/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:52) ~[test-kotlin-native:na]
at java.base@19.0.1/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) ~[na:na]
at java.base@19.0.1/java.lang.ClassLoader.loadClass(ClassLoader.java:132) ~[test-kotlin-native:na]
at kotlin.reflect.jvm.internal.KDeclarationContainerImpl.parseType(KDeclarationContainerImpl.kt:273) ~[test-kotlin-native:1.7.20-release-201(1.7.20)]
at kotlin.reflect.jvm.internal.KDeclarationContainerImpl.loadReturnType(KDeclarationContainerImpl.kt:288) ~[test-kotlin-native:1.7.20-release-201(1.7.20)]
at kotlin.reflect.jvm.internal.KDeclarationContainerImpl.findMethodBySignature(KDeclarationContainerImpl.kt:198) ~[test-kotlin-native:1.7.20-release-201(1.7.20)]
at kotlin.reflect.jvm.internal.KFunctionImpl$caller$2.invoke(KFunctionImpl.kt:68) ~[na:na]
at kotlin.reflect.jvm.internal.KFunctionImpl$caller$2.invoke(KFunctionImpl.kt:61) ~[na:na]
at kotlin.reflect.jvm.internal.ReflectProperties$LazyVal.invoke(ReflectProperties.java:63) ~[na:na]
at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32) ~[test-kotlin-native:1.7.20-release-201(1.7.20)]
at kotlin.reflect.jvm.internal.KFunctionImpl.getCaller(KFunctionImpl.kt:61) ~[na:na]
at kotlin.reflect.jvm.ReflectJvmMapping.getJavaMethod(ReflectJvmMapping.kt:63) ~[na:na]
at kotlin.reflect.jvm.ReflectJvmMapping.getKotlinFunction(ReflectJvmMapping.kt:136) ~[na:na]
at org.springframework.core.MethodParameter$KotlinDelegate.getGenericReturnType(MethodParameter.java:914) ~[na:na]
at org.springframework.core.MethodParameter.getGenericParameterType(MethodParameter.java:510) ~[test-kotlin-native:6.0.0-RC4]
at org.springframework.core.SerializableTypeWrapper$MethodParameterTypeProvider.getType(SerializableTypeWrapper.java:291) ~[na:na]
at org.springframework.core.SerializableTypeWrapper.forTypeProvider(SerializableTypeWrapper.java:107) ~[na:na]
at org.springframework.core.ResolvableType.forType(ResolvableType.java:1413) ~[test-kotlin-native:6.0.0-RC4]
at org.springframework.core.ResolvableType.forMethodParameter(ResolvableType.java:1334) ~[test-kotlin-native:6.0.0-RC4]
at org.springframework.core.ResolvableType.forMethodParameter(ResolvableType.java:1316) ~[test-kotlin-native:6.0.0-RC4]
at org.springframework.core.ResolvableType.forMethodParameter(ResolvableType.java:1283) ~[test-kotlin-native:6.0.0-RC4]
at org.springframework.core.ResolvableType.forMethodReturnType(ResolvableType.java:1228) ~[test-kotlin-native:6.0.0-RC4]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getTypeForFactoryMethod(AbstractAutowireCapableBeanFactory.java:814) ~[test-kotlin-native:6.0.0-RC4]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.determineTargetType(AbstractAutowireCapableBeanFactory.java:681) ~[test-kotlin-native:6.0.0-RC4]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.predictBeanType(AbstractAutowireCapableBeanFactory.java:652) ~[test-kotlin-native:6.0.0-RC4]
at org.springframework.beans.factory.support.AbstractBeanFactory.isFactoryBean(AbstractBeanFactory.java:1632) ~[test-kotlin-native:6.0.0-RC4]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doGetBeanNamesForType(DefaultListableBeanFactory.java:559) ~[test-kotlin-native:6.0.0-RC4]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForType(DefaultListableBeanFactory.java:531) ~[test-kotlin-native:6.0.0-RC4]
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:106) ~[na:na]
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:745) ~[test-kotlin-native:6.0.0-RC4]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:565) ~[test-kotlin-native:6.0.0-RC4]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[test-kotlin-native:3.0.0-RC2]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:730) ~[test-kotlin-native:3.0.0-RC2]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:432) ~[test-kotlin-native:3.0.0-RC2]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) ~[test-kotlin-native:3.0.0-RC2]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1302) ~[test-kotlin-native:3.0.0-RC2]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1291) ~[test-kotlin-native:3.0.0-RC2]
at com.example.demo.ApplicationKt.main(Application.kt:25) ~[test-kotlin-native:na]
The root cause seems to be the way java.lang.reflect.Method#kotlinFunction
getter is implemented. Adding the below hint resolve the issue:
class NativeRuntimeHints : RuntimeHintsRegistrar {
override fun registerHints(hints: RuntimeHints, classLoader: ClassLoader?) {
val t = MyKotlinConfiguration::class.java
hints.reflection().registerType(t, MemberCategory.INTROSPECT_DECLARED_METHODS)
}
}
Needless to say, there's no such issue with Java auto-configuration files.
Comment From: snicoll
Thanks for the report. I don't really understand why you think this is related to auto-configuration. Looking at the call stack, there's nothing Spring Boot specific so I expect a regular @Configuration
to have the same problem. Moving to framework.
Comment From: akefirad
You're right, apologies.
Comment From: akefirad
This java.lang.reflect.Method#kotlinFunction
implementation is messing in other places as well (Let me know if I need to create another ticket). For example I've got a simple class:
data class Name(val value: String) {
companion object {
@JvmStatic
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
fun from(value: String) = Name(value.trim())
}
}
The class is used in some HTTP request body, thus has to be deserialized at some point and when it does, it fails with the following error:
kotlin.reflect.jvm.internal.KotlinReflectionInternalError: Could not compute caller for function: public final fun from(value: kotlin.String): com.example.demo.Name defined in com.example.demo.Name.Companion[DeserializedSimpleFunctionDescriptor@233e514a] (member = null)
at kotlin.reflect.jvm.internal.KFunctionImpl$caller$2.invoke(KFunctionImpl.kt:88)
at kotlin.reflect.jvm.internal.KFunctionImpl$caller$2.invoke(KFunctionImpl.kt:61)
at kotlin.reflect.jvm.internal.ReflectProperties$LazyVal.invoke(ReflectProperties.java:63)
at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32)
at kotlin.reflect.jvm.internal.KFunctionImpl.getCaller(KFunctionImpl.kt:61)
at kotlin.reflect.jvm.ReflectJvmMapping.getJavaMethod(ReflectJvmMapping.kt:63)
at kotlin.reflect.jvm.ReflectJvmMapping.getKotlinFunction(ReflectJvmMapping.kt:129)
at com.fasterxml.jackson.module.kotlin.KotlinAnnotationIntrospector.hasRequiredMarker(KotlinAnnotationIntrospector.kt:187)
at com.fasterxml.jackson.module.kotlin.KotlinAnnotationIntrospector.access$hasRequiredMarker(KotlinAnnotationIntrospector.kt:26)
at com.fasterxml.jackson.module.kotlin.KotlinAnnotationIntrospector$hasRequiredMarker$hasRequired$1.invoke(KotlinAnnotationIntrospector.kt:44)
at com.fasterxml.jackson.module.kotlin.KotlinAnnotationIntrospector$hasRequiredMarker$hasRequired$1.invoke(KotlinAnnotationIntrospector.kt:36)
at com.fasterxml.jackson.module.kotlin.ReflectionCache.javaMemberIsRequired(ReflectionCache.kt:88)
at com.fasterxml.jackson.module.kotlin.KotlinAnnotationIntrospector.hasRequiredMarker(KotlinAnnotationIntrospector.kt:36)
at com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair.hasRequiredMarker(AnnotationIntrospectorPair.java:319)
at com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair.hasRequiredMarker(AnnotationIntrospectorPair.java:319)
at com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair.hasRequiredMarker(AnnotationIntrospectorPair.java:320)
at com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder.getMetadata(POJOPropertyBuilder.java:230)
at com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector._anyIndexed(POJOPropertiesCollector.java:1240)
at com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector._sortProperties(POJOPropertiesCollector.java:1142)
at com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector.collectAll(POJOPropertiesCollector.java:470)
at com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector.getPropertyMap(POJOPropertiesCollector.java:386)
at com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector.getProperties(POJOPropertiesCollector.java:233)
at com.fasterxml.jackson.databind.introspect.BasicBeanDescription._properties(BasicBeanDescription.java:164)
at com.fasterxml.jackson.databind.introspect.BasicBeanDescription.findProperties(BasicBeanDescription.java:239)
at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._findCreatorsFromProperties(BasicDeserializerFactory.java:328)
at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._constructDefaultValueInstantiator(BasicDeserializerFactory.java:272)
at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory.findValueInstantiator(BasicDeserializerFactory.java:223)
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.buildBeanDeserializer(BeanDeserializerFactory.java:262)
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:151)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer2(DeserializerCache.java:415)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:350)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:264)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:142)
at com.fasterxml.jackson.databind.DeserializationContext.findNonContextualValueDeserializer(DeserializationContext.java:638)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.resolve(BeanDeserializerBase.java:539)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:294)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:142)
at com.fasterxml.jackson.databind.DeserializationContext.findNonContextualValueDeserializer(DeserializationContext.java:638)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.resolve(BeanDeserializerBase.java:539)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:294)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:142)
at com.fasterxml.jackson.databind.DeserializationContext.findNonContextualValueDeserializer(DeserializationContext.java:638)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.resolve(BeanDeserializerBase.java:539)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:294)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
at com.fasterxml.jackson.databind.deser.DeserializerCache.hasValueDeserializerFor(DeserializerCache.java:191)
at com.fasterxml.jackson.databind.DeserializationContext.hasValueDeserializerFor(DeserializationContext.java:593)
at com.fasterxml.jackson.databind.ObjectMapper.canDeserialize(ObjectMapper.java:3515)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.canRead(AbstractJackson2HttpMessageConverter.java:265)
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:177)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:163)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:136)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:181)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:148)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:891)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:804)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1080)
... 53 common frames omitted
Adding the following runtime hint resolve the issue (Most probably not all MemboerCategory
values are needed.):
h.registerType(Name.Companion.class, MemberCategory.values());
Please note that Spring AoT adds a hint for the from
method like for class Name
and not its companion:
{
"name": "from",
"parameterTypes": [
"java.lang.String"
]
}
I'm not entirely sure what is the correct way to add the hint for a companion-object method of a Kotlin class. But what is clear is that current hints are not enough.
I'm discovering more cases that involves Method#kotlinFunction
one way or another and all of them break the native image. The way it's working right now, it seems any non-trivial Kotlin application requires a lot of hint. Not sure if there's any workaround.
Let me know if you need a sample project or anything else.
Comment From: sdeleuze
Looks like a duplicate of #29663.