-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Description
Describe the issue
After upgrading to GraalVM for JDK 25, a native-image built with the tracing agent (-agentlib:native-image-agent / -Pagent) fails at runtime with a MissingReflectionRegistrationError for an internal Ktor CIO class, even though the agent has been run with extensive tests that exercise the failing code path.
The problematic type is:
io.ktor.network.selector.InterestSuspensionsMap
At runtime, GraalVM 25 expects field-level reachability metadata for fields like readHandlerReference (accessed via AtomicReferenceFieldUpdater / Unsafe.objectFieldOffset), but the agent-generated reachability-metadata.json only contains a bare "type" entry for this class and no "fields" / "allDeclaredFields" information.
The same application and the same use of the tracing agent work on GraalVM 21 and GraalVM 24, where the generated metadata correctly includes the fields. On GraalVM 25, the fields are no longer emitted, which suggests a regression or behaviour change in the tracing agent / exact reachability handling in 25.
There is already some discussion about agent issues on JDK 25 in #10352 which I suspect may be related but this report is specifically about the agent not emitting field-level metadata for InterestSuspensionsMap even when the code path is exercised under the agent.
Steps to reproduce the issue
Please include both build steps as well as run steps
-
Clone the reproducer:
git clone --depth 1 https://github.com/rotilho/node-reproducer.git cd node-reproducer -
Run tests with the tracing agent enabled to generate reachability metadata:
./gradlew test -Pagent -
Build the native image:
./gradlew nativeCompile
-
Run the native binary (path as produced by the Gradle native-image plugin, e.g.):
./build/native/nativeCompile/node-reproducer
-
The application fails at startup with a
MissingReflectionRegistrationErrorpointing toio.ktor.network.selector.InterestSuspensionsMap.readHandlerReference.
Example run (just the metadata generation):
https://github.com/rotilho/node-reproducer/actions/runs/19893636797
Describe GraalVM and your environment:
-
GraalVM version: GraalVM Runtime Environment Oracle GraalVM 25.0.1+8.1 (build 25.0.1+8-LTS-jvmci-b01)
-
JDK major version: 25
-
OS: Ubuntu (GitHub Actions
ubuntu-latestrunner) -
Architecture: AMD64
-
Frameworks / libraries:
- Spring Boot 3.5.6
- Kotlin 2.3.0-RC
- Ktor client CIO engine (
io.ktor:ktor-client-cio) org.graalvm.buildtools.nativeGradle plugin
More details
At runtime, the native image fails during Spring context initialization with an UnsatisfiedDependencyException caused by a MissingReflectionRegistrationError. The interesting part of the log is:
{
"type": "io.ktor.network.selector.InterestSuspensionsMap",
"fields": [
{
"name": "readHandlerReference"
}
]
}
{}
The 'reachability-metadata.json' file should be located in 'META-INF/native-image/<group-id>/<artifact-id>/' of your project. For further help, see https://www.graalvm.org/latest/reference-manual/native-image/metadata/#reflection
at com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils.reportAccessedField(MissingReflectionRegistrationUtils.java:79)
at com.oracle.svm.core.reflect.UnsafeFieldUtil.getFieldOffset(UnsafeFieldUtil.java:46)
at jdk.internal.misc.Unsafe.objectFieldOffset(Unsafe.java:44)
at java.util.concurrent.atomic.AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl.<init>(AtomicReferenceFieldUpdater.java:111)
at java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater(AtomicReferenceFieldUpdater.java:112)
at io.ktor.network.selector.InterestSuspensionsMap.<clinit>(InterestSuspensionsMap.kt:66)
at io.ktor.network.selector.SelectableBase.<init>(SelectableBase.kt:15)
at io.ktor.network.sockets.SocketBase.<init>(SocketBase.kt:15)
at io.ktor.network.sockets.NIOSocketImpl.<init>(NIOSocketImpl.kt:15)
at io.ktor.network.sockets.SocketImpl.<init>(SocketImpl.kt:10)
at io.ktor.network.sockets.ConnectUtilsJvmKt.tcpConnect(ConnectUtilsJvm.kt:20)
at io.ktor.network.sockets.TcpSocketBuilder.connect(TcpSocketBuilder.kt:48)
at io.ktor.client.engine.cio.ConnectionFactory.connect(ConnectionFactory.kt:30)
...
However, the agent-generated metadata differs across GraalVM versions for the exact same project and agent flags:
GraalVM 21 — reflect-config.json
The agent produces a proper reflect-config.json with field-level metadata:
{
"name": "io.ktor.network.selector.InterestSuspensionsMap",
"fields": [
{ "name": "acceptHandlerReference" },
{ "name": "connectHandlerReference" },
{ "name": "readHandlerReference" },
{ "name": "writeHandlerReference" }
]
},GraalVM 24 — reachability-metadata.json
On GraalVM 24, the new reachability-metadata.json is generated and still correctly includes the fields:
{
"type": "io.ktor.network.selector.InterestSuspensionsMap",
"fields": [
{
"name": "acceptHandlerReference"
},
{
"name": "connectHandlerReference"
},
{
"name": "readHandlerReference"
},
{
"name": "writeHandlerReference"
}
]
},With this metadata, the native image runs successfully.
GraalVM 25 — reachability-metadata.json
On GraalVM 25, with the same application code and the same agent setup, the generated reachability-metadata.json only contains a bare type entry and no field information:
{
"type": "io.ktor.network.selector.InterestSuspensionsMap"
},There is no "fields" and no "allDeclaredFields" for this type. At runtime, GraalVM 25 then reports that it needs field-level metadata for readHandlerReference (and the other handler fields) when AtomicReferenceFieldUpdater / Unsafe.objectFieldOffset is used, and throws a MissingReflectionRegistrationError.