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.