I am trying to get started with Ktor and GraalVM. I am trying to run a very dummy Ktor app (IntelliJ's default one) with GraalVM.
I've followed https://ktor.io/docs/graalvm.html#prepare-for-graalvm steps and it is working fine because it configures routing directly from the embeddedServer
call.
fun main() {
embeddedServer(CIO, port = 8080, host = "0.0.0.0") {
configureRouting()
}.start(wait = true)
}
However, when I refactored this code to:
fun main() {
embeddedServer(CIO, port = 8080, host = "0.0.0.0", module = Application::module)
.start(wait = true)
}
fun Application.module() {
configureRouting()
}
it stopped working. The error is a reflection issue:
Exception in thread "main" kotlin.reflect.jvm.internal.KotlinReflectionInternalError: Could not compute caller for function: public fun io.ktor.server.application.Application.module(): kotlin.Unit defined in xxxxx[DeserializedSimpleFunctionDescriptor@2ab47469] (member = null)
I've tried a lot of different things in my reflect-config.json
file, that actually looks like:
[
{
"name": "kotlin.reflect.jvm.internal.ReflectionFactoryImpl",
"allDeclaredConstructors": true
},
{
"name": "mypackage.Application",
"methods": [
{
"name": "module",
"parameterTypes": [],
"returnType": "kotlin.Unit"
}
]
},
{
"name": "io.ktor.server.application.Application",
"methods": [
{
"name": "module",
"parameterTypes": [],
"returnType": "kotlin.Unit"
}
]
},
{
"name": "mypackage.plugins.Routing",
"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
}
]
my file structure for the project looks like
src/main
├── kotlin
│ └── mypackage
│ ├── Application.kt
│ └── plugins
│ └── Routing.kt
└── resources
├── META-INF
│ └── native-image
│ └── reflect-config.json
└── logback.xml
where Application.kt
looks like the previous code and Routing.kt
is just:
fun Application.configureRouting() {
routing {
get("/") {
call.respondText("Hello World!")
}
}
}
I know that the code works when configuring the routing directly on the embeddedServer
but delegating in the Application.module()
function is way more convenient and mantainable.
If anyone can provide any feedback, it would be great. Thanks in advance!
In order to build ktor with graalvm you need to run agent like below
This will generate reflect json and other files under graalcnf folder, then you need to provide those in native-image.properties as argument like below
I did this couple of years ago for my test framework which is comparing multiple microservice frameworks like spring quarkus etc. You can reach the example code here and you can see some comparison at here