skip to content

What is the GraalVM native-image-agent and what problem does it solve?

level: juniorimportance: must knowfreq 55%

answer

  1. JVMTI agent on a normal JVM run
  2. records reflection/resource/proxy/JNI usage
  3. emits reachability-metadata JSON
  4. closed-world can't see dynamic calls
  5. META-INF/native-image/

basics

~20 s

It is a JVM agent (enabled with -agentlib:native-image-agent) that watches an app running on the normal JVM and records dynamic behavior like reflection, resource loading and proxies, writing JSON metadata that native-image needs to compile those parts.

solid answer

~40 s

GraalVM's native-image compiler does static ahead-of-time analysis (closed-world), so anything discovered only at runtime — reflection, resource lookups, dynamic proxies, JNI, serialization — is invisible to it and gets stripped. The native-image-agent is a JVMTI agent you attach to a *normal JVM run* with -agentlib:native-image-agent. As the app executes, it intercepts those dynamic calls and emits reachability-metadata JSON (reflect-config.json, resource-config.json, proxy-config.json, etc.). You place that metadata under META-INF/native-image/ so the next native build registers the classes/resources/proxies and they work in the native executable. It bootstraps the metadata you'd otherwise have to hand-write, and is mainly needed for third-party libraries that don't ship their own hints.

code

java · 11 lines
java
// Run the app on a NORMAL JVM with the agent attached.
// The agent records dynamic usage and writes JSON metadata.
//
//   java -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image \
//        -jar app.jar
//
// Example dynamic call the static compiler CANNOT see, but the agent CAN record:
Class<?> handler = Class.forName("com.example.PluginHandler"); // reflection
Object instance = handler.getDeclaredConstructor().newInstance();

// Produces reflect-config.json entry so native-image keeps PluginHandler.

go deeper

for a junior

Know it records reflection/resource/proxy usage on the JVM into JSON so native-image keeps those elements.

for a middle

Explain the closed-world assumption and name the concrete config files and the META-INF location.

for a senior

Frame it as observation-only and stress that it captures only executed paths; relate to Spring's own hint generation.

for a principal

Position the agent as a fallback for third-party libraries and reason about metadata completeness/maintenance strategy.

**Background: the closed-world assumption.** GraalVM `native-image` compiles a Java app ahead-of-time into a standalone native executable. To do this it performs *static reachability analysis*: starting from `main`, it follows method calls to decide which classes, methods and fields are reachable, and it discards everything else. This is the **closed-world assumption** — the whole program must be known at build time. There is no dynamic class loading at runtime. **The problem.** Java has dynamic features that a static analyzer cannot follow: - **Reflection** — `Class.forName("...")`, `clazz.getMethod(...)`, `field.setAccessible(true)`. - **Resources** — `getResourceAsStream("application.properties")`. - **Dynamic proxies** — `java.lang.reflect.Proxy.newProxyInstance(...)`. - **JNI**, and **serialization**. Because the target class/method/resource often comes from a runtime string or config, the compiler can't see it, so it strips it. At runtime the native image then throws errors like `MissingReflectionRegistrationError`, `ClassNotFoundException`, a null resource stream, or a missing-proxy error. **What the agent is.** The `native-image-agent` is a **JVMTI (JVM Tool Interface) native agent** shipped inside GraalVM. You attach it to an ordinary JVM run (NOT a native build) via `-agentlib:native-image-agent=...`. While the app runs on the JVM, it instruments those dynamic APIs and records every actual usage. **What it produces.** It writes **reachability metadata** as JSON into a directory you choose: - `reflect-config.json` — classes/methods/fields accessed reflectively - `resource-config.json` — resource patterns and bundles loaded - `proxy-config.json` — dynamic proxy interface combinations - `jni-config.json`, `serialization-config.json` (Newer GraalVM versions consolidate these into a single `reachability-metadata.json`.) You typically drop these files under `src/main/resources/META-INF/native-image/<groupId>/<artifactId>/`, where `native-image` picks them up automatically on the next build and registers the elements so they survive into the native executable. **Key mental model:** the agent does *observation → recording*, not compilation. It only records what the running program **actually executes**. This is its biggest strength (accurate, real usage) and its biggest limitation (unexercised paths produce no metadata). **Where it fits with Spring.** Spring Boot has its own AOT engine that generates most `RuntimeHints` for your Spring beans/config at build time, so the agent is usually **not** needed for your own Spring code. The agent's real value is capturing metadata for **third-party libraries** that neither provide bundled metadata nor are covered by Spring's hints.

  • Why can't native-image just figure out reflection on its own?
    Because native-image relies on the closed-world assumption and static reachability analysis. Reflective targets are usually derived from runtime strings/config, so there is no static call edge for the analyzer to follow — it must be told via metadata.
  • Is the agent run during the native build or before it?
    Before — you attach it to a *regular JVM* execution (often your test suite) to collect metadata, then feed that metadata into the separate native-image build.

saying these in an interview costs you the question

  • Thinking the agent runs during native compilation rather than on a normal JVM
  • Believing it makes reflection work automatically at native runtime without producing metadata files
  • Confusing it with a code coverage or profiling tool

context