How would you integrate agent-based metadata collection into a Spring Boot build/CI pipeline, and what are the conditional-config options?
answer
- Native Build Tools: -Pagent test + metadataCopy
- Maven native:metadata-copy
- check in metadata, regenerate deliberately
- gate CI on nativeTest
- conditional-config = typeReachable-guarded entries
basics
~20 sUse GraalVM Native Build Tools' agent mode to run tests under the agent (Gradle -Pagent or Maven -Pnative -DskipNativeTests with the agent), then a metadataCopy step moves the JSON into src/main/resources/META-INF/native-image. Conditional-config records metadata gated on a class being reachable so it applies only when relevant.
solid answer
~40 sRather than invoking -agentlib by hand, I'd use GraalVM Native Build Tools. In Gradle, `./gradlew -Pagent test` runs the test task with the agent attached, writing metadata under build/native/agent-output; then `./gradlew metadataCopy --task test --dir src/main/resources/META-INF/native-image` promotes it into the app. Maven has the equivalent `native:metadata-copy`. I'd run this deliberately (not on every CI build) to refresh metadata when dependencies change, review the JSON diff in PRs, and gate the pipeline on an actual `nativeTest` compile-and-run. For metadata that should apply only when a type is present, the agent's experimental conditional-config mode emits entries guarded by a `condition`/`typeReachable` clause (via experimental-conditional-config-*), so unrelated apps sharing the library don't pull in irrelevant reflection. I'd prefer library-provided metadata and RuntimeHintsRegistrars where possible, treating the agent as a controlled bootstrap.
code
java · 27 lines// Gradle (GraalVM Native Build Tools) — collect + promote metadata:
//
// ./gradlew -Pagent test
// ./gradlew metadataCopy --task test \
// --dir src/main/resources/META-INF/native-image
//
// # Safety net in CI: actually compile & run natively
// ./gradlew nativeTest
//
// build.gradle DSL to tune the agent:
//
// graalvmNative {
// agent {
// defaultMode = "conditional" // emit typeReachable-guarded metadata
// modes { conditional { userCodeFilterPath = "user-filter.json" } }
// metadataCopy {
// inputTaskNames.add("test")
// outputDirectories.add("src/main/resources/META-INF/native-image/com.example/app")
// mergeWithExisting = true
// }
// }
// }
//
// Resulting conditional entry (reflect metadata):
// { "condition": { "typeReachable": "com.lib.Feature" },
// "name": "com.lib.FeatureImpl",
// "allDeclaredConstructors": true }go deeper
Know the tooling exists (Native Build Tools) rather than raw flags.
Run -Pagent test + metadataCopy and know metadata is checked in.
Design the collection cadence, review diffs, and gate on nativeTest.
Own the whole strategy: upstream metadata first, controlled agent bootstrap, conditional-config for shared metadata, CI native-test gating, and promotion into registrars.
**Don't hand-wire -agentlib in CI — use Native Build Tools.** The GraalVM **Native Build Tools** (the `org.graalvm.buildtools.native` Gradle plugin / Maven plugin) provide first-class agent support so you don't manage raw JVM flags. **Gradle flow:** 1. `./gradlew -Pagent test` — runs the `test` task with the agent attached; metadata lands under `build/native/agent-output/<task>/`. 2. `./gradlew metadataCopy --task test --dir src/main/resources/META-INF/native-image` — copies the collected metadata into the project so it's checked in and picked up by future native builds. The plugin exposes a `graalvmNative { agent { ... } }` DSL to configure modes, filters, and merge behavior. **Maven flow:** analogous — run tests with the agent via the `-Pnative`/agent profile, then `mvn native:metadata-copy` to promote the output. **CI strategy (the principal-level judgment):** - **Do not** regenerate metadata on every build — it's non-deterministic w.r.t. coverage and would create noisy diffs. Instead, **check the metadata into source control** and regenerate deliberately (a manual or scheduled job) when dependencies or reflective usage change. - **Review the JSON diff** in PRs like any other source. - **Gate on real native tests**: run `nativeTest` (compile + run the test suite natively) in CI so missing metadata fails the build, not production. - **Prefer upstream metadata**: enable the GraalVM Reachability Metadata Repository so library metadata is pulled automatically; only fall back to the agent for genuine gaps. - For Spring-owned reflection, **promote agent findings into a `RuntimeHintsRegistrar`** (reviewable, conditional) instead of leaving raw JSON. **Conditional configuration.** A subtle problem: if you drop unconditional metadata for library X into a shared module, every app registers that reflection even when it never uses that code path — bloating the image and analysis. The agent's **experimental conditional-config** solves this: it records entries with a **condition** (a `typeReachable` clause) so the metadata is only applied *when a specified type is actually reachable* in that build. You enable it with the agent's `experimental-conditional-config-output-dir` / `...-part-file` options (Native Build Tools surface this through the agent DSL). The result is metadata like: ```json { "condition": { "typeReachable": "com.lib.Feature" }, "name": "com.lib.FeatureImpl", "methods": [ ... ] } ``` meaning: only register `FeatureImpl`'s reflection **if** `com.lib.Feature` is reachable. This makes library metadata composable and avoids over-registration. **Summary posture:** agent collection is a *controlled, occasional* build activity feeding checked-in metadata; the safety net is native tests in CI; conditional-config keeps shared metadata lean; and Spring registrars / upstream metadata are preferred where they apply.
- Why not regenerate agent metadata on every CI run?Coverage is non-deterministic run-to-run, producing noisy, unreviewable diffs and risking silently dropped entries. Better to check metadata in, regenerate deliberately when deps change, and gate CI on nativeTest to catch gaps.
- What does conditional-config's typeReachable condition buy you?It scopes metadata so it's applied only when a given type is reachable in that build. Shared library metadata then doesn't force every consuming app to register reflection for features it never uses, keeping the native image lean.
- What is the safety net that catches metadata gaps before production?Running nativeTest in CI — it compiles and runs the test suite as a native image, so any missing reflection/resource/proxy metadata fails the build rather than surfacing at runtime in production.
saying these in an interview costs you the question
- Regenerating metadata on every build and committing churny diffs
- Trusting agent metadata without a nativeTest gate
- Shipping unconditional metadata in shared libraries, bloating consumers' images
- Managing raw -agentlib flags instead of using Native Build Tools