I have a sample spring-boot kotlin app that uses RuntimeHintsRegistrar
to register a few classes (Kotlin Data class and Java record) which are initialized reflectively inside a controller. Everything is working fine in JIT mode and fails with the following error in native mode. Is kotlin reflection supported out of the box or is this some other configuration missing from the application side?
2023-01-12T11:10:59.273-08:00 ERROR 61294 --- [nio-8080-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed: kotlin.reflect.jvm.internal.KotlinReflectionInternalError: Could not compute caller for function: private open inline fun <reified T> initReflectively(): kotlin.String defined in dev.suresh.controller.GreetingsController[DeserializedSimpleFunctionDescriptor@47c5c599] (member = null)] with root cause
kotlin.reflect.jvm.internal.KotlinReflectionInternalError: Could not compute caller for function: private open inline fun <reified T> initReflectively(): kotlin.String defined in dev.suresh.controller.GreetingsController[DeserializedSimpleFunctionDescriptor@47c5c599] (member = null)
at kotlin.reflect.jvm.internal.KFunctionImpl$caller$2.invoke(KFunctionImpl.kt:88) ~[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) ~[spring-native:1.8.0-release-345(1.8.0)]
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]
Env
$ java --version
openjdk 19.0.1 2022-10-18
OpenJDK Runtime Environment GraalVM CE 22.3.0 (build 19.0.1+10-jvmci-22.3-b08)
OpenJDK 64-Bit Server VM GraalVM CE 22.3.0 (build 19.0.1+10-jvmci-22.3-b08, mixed mode, sharing)
plugins {
id("org.springframework.boot") version "3.0.1"
id("io.spring.dependency-management") version "1.1.0"
id("org.graalvm.buildtools.native") version "0.9.19"
kotlin("jvm") version "1.8.0"
kotlin("plugin.spring") version "1.8.0"
...
}
AOT Hints
@Configuration
@ImportRuntimeHints(Hints::class)
class AotConfig
class Hints : RuntimeHintsRegistrar {
override fun registerHints(hints: RuntimeHints, classLoader: ClassLoader?) {
hints.reflection().apply {
registerType(JavaRec::class.java, *MemberCategory.values())
registerType(KotlinData::class.java, *MemberCategory.values())
}
}
}
Controller (Using reflection)
@GetMapping("/greet/{type}")
fun reflectGreet(@PathVariable type: String) =
when (type) {
"java" -> initReflectively<JavaRec>()
"kotlin" -> initReflectively<KotlinData>()
else -> "Hello, Spring Native!"
}
private inline fun <reified T> initReflectively() =
T::class.java.getConstructor().newInstance().toString()
I even tried to enable GraalVM metadata repo in Gradle config and still having the same issue.
graalvmNative { metadataRepository { enabled.set(true) } }
Here is the sample repo which you can reproduce this issue by running
$./gradlew nativeCompile
$ build/native/nativeCompile/spring-native &
$ open http://localhost:8080/greet/kotlin
Comment From: scottfrederick
Thanks for the report and the sample. The problem isn't that the JavaRec
and KotlinData
classes can't be found via reflection, it's that the GreetingsController::initReflectively
method can't be found. There are two ways to make your sample work:
- Add a reflection hint in
AotConfig
:
registerType(GreetingsController::class.java, MemberCategory.INVOKE_DECLARED_METHODS)
- Collapse the calls to the constructors in
GreetingsController
:
fun reflectGreet(@PathVariable type: String) =
when (type) {
"java" -> JavaRec::class.java.getConstructor().newInstance().toString()
"kotlin" -> KotlinData::class.java.getConstructor().newInstance().toString()
else -> "Hello, Spring Native!"
}
I'm not sure if there's a way to make this work better. If there is, the change would need to be in the Spring Framework project, not in Spring Boot. We'll transfer the issue so the Framework team can take a look and give advice.
Comment From: sureshg
@scottfrederick thanks, adding the reflection hints for declared methods worked fine 👍🏼 . But the surprising thing is, this is a kotlin inline function, which will anyway get inlined into the call site during kotlin compilation (something like the second option you mentioned above).
Comment From: sdeleuze
Looks like #29663 fixed it, so I will close this one as duplicate.