How do you run the native-image-agent and manage its output across multiple runs?
answer
- config-output-dir overwrites
- config-merge-dir accumulates
- META-INF/native-image/<group>/<artifact>
- caller-filter/access-filter trim noise
- exercise all paths (run tests)
basics
~10 sAdd -agentlib:native-image-agent=config-output-dir=<dir> to the JVM command and exercise the app. Use config-merge-dir instead of config-output-dir to accumulate metadata from several runs, and put the final JSON under META-INF/native-image/.
solid answer
~40 sYou attach the agent with `-agentlib:native-image-agent=config-output-dir=<dir>` on a plain `java` command and then drive the app through its features. `config-output-dir` overwrites the directory each run, so for multiple scenarios you switch to `config-merge-dir=<dir>`, which merges new observations into existing files without losing prior ones — a common pattern is one run with output-dir to seed, then merge subsequent runs. The generated `reflect-config.json`, `resource-config.json`, `proxy-config.json`, etc. go under `src/main/resources/META-INF/native-image/<group>/<artifact>/` so `native-image` auto-discovers them. You can also add `caller-filter-file`/`access-filter-file` to exclude noisy or irrelevant classes, and there are periodic-flush options. Because only executed paths are recorded, you typically run the agent over the test suite or a scripted exercise of all endpoints.
code
java · 18 lines// 1) Seed metadata from a first scenario (overwrites the dir):
// java -agentlib:native-image-agent=config-output-dir=out -jar app.jar
// 2) Add more scenarios WITHOUT losing the first (merges):
// java -agentlib:native-image-agent=config-merge-dir=out -jar app.jar
// 3) Trim JDK/test-framework noise with a caller filter file:
// -agentlib:native-image-agent=config-merge-dir=out,caller-filter-file=filter.json
//
// filter.json:
// {
// "rules": [
// { "excludeClasses": "org.junit.**" },
// { "excludeClasses": "jdk.internal.**" }
// ]
// }
//
// 4) Copy 'out' to src/main/resources/META-INF/native-image/<group>/<artifact>/go deeper
Know the -agentlib flag and that JSON lands under META-INF/native-image.
Distinguish output-dir vs merge-dir and know the filter files and per-artifact folder convention.
Design a run strategy (over tests) and use filters to keep metadata clean and reviewable.
Automate collection in the build/CI, decide whom the metadata belongs to, and keep it namespaced and diffable.
**Invocation.** The agent is attached to an ordinary JVM launch: ``` java -agentlib:native-image-agent=config-output-dir=META-INF/native-image -jar app.jar ``` The part after `=` is a comma-separated option string. **Output vs merge — the crucial distinction:** - `config-output-dir=<dir>` — writes the metadata files, **overwriting** whatever was there. Good for the first run; bad if you have multiple scenarios, because each run clobbers the last. - `config-merge-dir=<dir>` — reads the existing files in `<dir>` and **merges** newly observed entries into them. This is how you accumulate metadata across many runs (different endpoints, different profiles). Typical flow: seed once with `config-output-dir`, then run every additional scenario with `config-merge-dir` pointing at the same directory. **Where the files go.** Native-image automatically scans `META-INF/native-image/**` on the classpath. The convention is a per-library subfolder: `src/main/resources/META-INF/native-image/<groupId>/<artifactId>/`, keeping metadata namespaced so it doesn't collide with other jars. **Generated files:** `reflect-config.json`, `resource-config.json`, `proxy-config.json`, `jni-config.json`, `serialization-config.json`, plus `predefined-classes-config.json` for bytecode defined at runtime. Recent GraalVM merges these into one `reachability-metadata.json`. **Filtering the noise.** Runs pick up reflection from the JDK, the app server, test frameworks, etc. Two filter options trim this: - `caller-filter-file=<json>` — ignore accesses *made by* matching classes (e.g. don't record what JUnit itself reflects on). - `access-filter-file=<json>` — exclude accesses *to* matching classes from the output. Each is a small JSON file with `includes`/`excludes` glob rules. This keeps the metadata focused on your code and target libraries. **Other useful options:** `config-write-period-secs` and `config-write-initial-delay-secs` for long-running apps that never cleanly exit (so metadata is flushed periodically); `experimental-conditional-config-*` for conditional metadata (see the conditional-config question). **Exercising the app.** Because the agent records only executed code, incomplete exercise = incomplete metadata = runtime failures in the native image. So you run it against something that touches every path — most commonly the **integration/functional test suite**, or a scripted client hitting every endpoint. Build tools (GraalVM Native Build Tools) formalize this with an `-Pagent` mode that runs `test` under the agent and a `metadataCopy` task to move the result into `META-INF/native-image`.
- What is the difference between config-output-dir and config-merge-dir?config-output-dir overwrites the target directory on each run; config-merge-dir reads existing metadata and merges new observations in, so you can accumulate across multiple runs without losing earlier results.
- Why might you use config-write-period-secs?For long-running or non-terminating apps (e.g. a server you stop with SIGKILL) that never reach a clean shutdown — periodic flushing writes metadata to disk incrementally instead of only on exit.
saying these in an interview costs you the question
- Using config-output-dir for every scenario and wondering why only the last run's metadata survives
- Putting metadata somewhere native-image doesn't scan (not under META-INF/native-image)
- Assuming a single happy-path run captures everything