Search code examples
spring-bootkotlinkotlin-coroutinesspring-native

How to provide RuntimeHints for Internal Classes in SpringBoot3


I'm trying run a Spring Boot Application with Kotlin Coroutines. When i execute this in JVM Mode everything works as expected, but when i execute as native image i get this error:

Stacktrace

java.lang.NoSuchMethodException: kotlin.internal.jdk8.JDK8PlatformImplementations.<init>()
        at [email protected]/java.lang.Class.getConstructor0(DynamicHub.java:3585) ~[CoroutinesAotExample:na]
        at [email protected]/java.lang.Class.newInstance(DynamicHub.java:626) ~[CoroutinesAotExample:na]
        at kotlin.internal.PlatformImplementationsKt.<clinit>(PlatformImplementations.kt:54) ~[na:na]
        at kotlin.random.Random.<clinit>(Random.kt:271) ~[CoroutinesAotExample:1.7.21-release-272(1.7.21)]
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.<init>(CoroutineScheduler.kt:637) ~[CoroutinesAotExample:na]
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.<init>(CoroutineScheduler.kt:592) ~[CoroutinesAotExample:na]
        at kotlinx.coroutines.scheduling.CoroutineScheduler.createNewWorker(CoroutineScheduler.kt:481) ~[CoroutinesAotExample:na]
        at kotlinx.coroutines.scheduling.CoroutineScheduler.tryCreateWorker(CoroutineScheduler.kt:439) ~[CoroutinesAotExample:na]
        at kotlinx.coroutines.scheduling.CoroutineScheduler.signalBlockingWork(CoroutineScheduler.kt:420) ~[CoroutinesAotExample:na]
        at kotlinx.coroutines.scheduling.CoroutineScheduler.dispatch(CoroutineScheduler.kt:401) ~[CoroutinesAotExample:na]
        at kotlinx.coroutines.scheduling.SchedulerCoroutineDispatcher.dispatchWithContext$kotlinx_coroutines_core(Dispatcher.kt:103) ~[CoroutinesAotExample:na]
        at kotlinx.coroutines.scheduling.UnlimitedIoScheduler.dispatch(Dispatcher.kt:39) ~[na:na]
        at kotlinx.coroutines.internal.LimitedDispatcher.dispatch(LimitedDispatcher.kt:67) ~[na:na]
        at kotlinx.coroutines.scheduling.DefaultIoScheduler.dispatch(Dispatcher.kt:65) ~[na:na]
        at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:322) ~[na:na]
        at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30) ~[na:na]
        at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:25) ~[na:na]
        at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:110) ~[CoroutinesAotExample:na]
        at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126) ~[CoroutinesAotExample:na]
        at kotlinx.coroutines.BuildersKt__Builders_commonKt.async(Builders.common.kt:91) ~[na:na]
        at kotlinx.coroutines.BuildersKt.async(Unknown Source) ~[na:na]
        at kotlinx.coroutines.BuildersKt__Builders_commonKt.async$default(Builders.common.kt:82) ~[na:na]
        at kotlinx.coroutines.BuildersKt.async$default(Unknown Source) ~[na:na]
        at info.novatec.coroutines.business.ClaimNumberService$generateClaimNumber$1$1.invokeSuspend(ClaimNumberService.kt:16) ~[na:na]
        at info.novatec.coroutines.business.ClaimNumberService$generateClaimNumber$1$1.invoke(ClaimNumberService.kt) ~[na:na]
        at info.novatec.coroutines.business.ClaimNumberService$generateClaimNumber$1$1.invoke(ClaimNumberService.kt) ~[na:na]
        at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:89) ~[na:na]
        at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:169) ~[na:na]
        at kotlinx.coroutines.BuildersKt.withContext(Unknown Source) ~[na:na]
        at info.novatec.coroutines.business.ClaimNumberService$generateClaimNumber$1.invokeSuspend(ClaimNumberService.kt:15) ~[na:na]
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) ~[CoroutinesAotExample:1.7.21-release-272(1.7.21)]
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) ~[CoroutinesAotExample:na]
        at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:284) ~[CoroutinesAotExample:na]
        at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85) ~[na:na]
        at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59) ~[na:na]
        at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source) ~[na:na]
        at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38) ~[na:na]
        at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source) ~[na:na]
        at info.novatec.coroutines.business.ClaimNumberService.generateClaimNumber(ClaimNumberService.kt:14) ~[CoroutinesAotExample:na]
        at info.novatec.coroutines.api.ClaimNumberController.isClaimNumberValid(ClaimNumberController.kt:23) ~[CoroutinesAotExample:na]
        at [email protected]/java.lang.reflect.Method.invoke(Method.java:568) ~[CoroutinesAotExample:na]
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:207) ~[CoroutinesAotExample:6.0.2]
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:152) ~[CoroutinesAotExample:6.0.2]
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[CoroutinesAotExample:6.0.2]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884) ~[CoroutinesAotExample:6.0.2]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[CoroutinesAotExample:6.0.2]
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[CoroutinesAotExample:6.0.2]
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1080) ~[CoroutinesAotExample:6.0.2]
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:973) ~[CoroutinesAotExample:6.0.2]
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1003) ~[CoroutinesAotExample:6.0.2]
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:895) ~[CoroutinesAotExample:6.0.2]
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:705) ~[CoroutinesAotExample:6.0]
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:880) ~[CoroutinesAotExample:6.0.2]
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:814) ~[CoroutinesAotExample:6.0]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:223) ~[na:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na]
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[CoroutinesAotExample:10.1.1]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[na:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na]
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[CoroutinesAotExample:6.0.2]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[CoroutinesAotExample:6.0.2]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[na:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na]
        at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[CoroutinesAotExample:6.0.2]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[CoroutinesAotExample:6.0.2]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[na:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na]
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[CoroutinesAotExample:6.0.2]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[CoroutinesAotExample:6.0.2]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[na:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na]
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[na:na]
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[na:na]
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) ~[CoroutinesAotExample:10.1.1]
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:119) ~[na:na]
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[CoroutinesAotExample:10.1.1]
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[na:na]
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) ~[na:na]
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:400) ~[na:na]
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[CoroutinesAotExample:10.1.1]
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861) ~[na:na]
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1739) ~[na:na]
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[CoroutinesAotExample:10.1.1]
        at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[na:na]
        at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[na:na]
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[na:na]
        at [email protected]/java.lang.Thread.run(Thread.java:833) ~[CoroutinesAotExample:na]
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:775) ~[CoroutinesAotExample:na]
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.posix.thread.PosixPlatformThreads.pthreadStartRoutine(PosixPlatformThreads.java:203) ~[na:na]

I want to add a runtime hint as explained in the spring docs. Unfortunately the class is internal and I don't know how to provide a Hint for graalvm now

RuntimeHintRegistry

not compiling because kotlin.internal.jdk8.JDK8PlatformImplementations is internal

import org.springframework.aot.hint.MemberCategory
import org.springframework.aot.hint.RuntimeHints
import org.springframework.aot.hint.RuntimeHintsRegistrar
import kotlin.internal.jdk8.JDK8PlatformImplementations

class CoroutinesAsyncRuntimeHints: RuntimeHintsRegistrar {

    override fun registerHints(hints: RuntimeHints, classLoader: ClassLoader?) {
        hints.reflection().registerType(JDK8PlatformImplementations::class.java, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)
    }
}

you can find a minimal example here CoroutinesAotExample

do you have any ideas how to add a manuall runtime hint for graalvm?


Solution

  • The issue is already addressed:

    https://github.com/spring-projects-experimental/spring-native/issues/1646

    https://youtrack.jetbrains.com/issue/KT-51579

    The current workaround is:

    create file reflect-config.json under META-INF/native-image

    Content of reflect-config.json

    [
      {
        "name": "kotlin.reflect.jvm.internal.ReflectionFactoryImpl",
        "allDeclaredConstructors":true
      },
      {
        "name": "kotlin.KotlinVersion",
        "allPublicMethods": true,
        "allDeclaredFields":true,
        "allDeclaredMethods":true,
        "allDeclaredConstructors":true
      },
      {
        "name": "kotlin.KotlinVersion[]"
      },
      {
        "name": "kotlin.KotlinVersion$Companion"
      },
      {
        "name": "kotlin.KotlinVersion$Companion[]"
      },
      {
        "name": "kotlin.internal.jdk8.JDK8PlatformImplementations",
        "allPublicMethods": true,
        "allDeclaredFields":true,
        "allDeclaredMethods":true,
        "allDeclaredConstructors":true
      }
    ]