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.