GraalVM readiness checks
The GraalVM panel surveys the host application for GraalVM native-image readiness and can generate a reachability-metadata.json scaffold from the scan. The active catalogue contains 30 checks: 22 GraalVM checks and 8 Spring AOT checks. This page lists every active check, what it inspects, when it fires, and what to do about it.
Each check is a small class registered in GraalVmCheckRegistry and implemented in GraalVmChecks.java. Adding a check means adding one focused class plus a registry entry, which keeps the list reviewable.
The rule engine — checks, categories, the dependency scanner, and the scaffold generator — is framework-neutral and lives in bootui-engine. Only the controller, the Dockerfile generator, and the source-tree writer are Spring adapter wiring.
This advisor is not applicable on Quarkus, which configures native images during its own build-time augmentation. The panel stays unavailable there with a platform-specific explanation rather than exposing a Spring-oriented scan or its generated files.
October 2026 audit
The third audit re-checked every active rule against the GraalVM for JDK 25 feature releases (25.0.x through 25.4.4.1.1, the current GraalVM Community build on JDK 25), Spring Framework 7.0.9 and Spring Boot 4.1.1 sources and AOT documentation, and Spring Cloud Commons. Each candidate removal, severity change, and new rule was critiqued independently by three reviewer models (GPT-6.1 Sol, Claude Opus 5, and Grok 4.7); a rule was added only with at least two in favour and with their objections resolved.
| ID | Decision | Result |
|---|---|---|
GRAAL-REFLECT-003 | Retire | setAccessible, trySetAccessible, and privateLookupIn need no metadata of their own: the unified schema has no access flag, and the member lookup, invocation, and field access that do need metadata are GRAAL-REFLECT-001 and GRAAL-MH-001. Module-access failures are identical on the JVM. |
GRAAL-REFLECT-004 | Retire | Native Image registers a member's declared, parameter, and type annotations whenever the member itself is registered, including query-only and image-heap members (ReflectionDataBuilder.registerTypesForMethod/Field). A reflected member therefore already carries its annotations; the check mostly fired on supported AOP aspects. |
GRAAL-REFLECT-001 | Fix | Also flags Spring's reflection facades (ReflectionUtils, ClassUtils.forName/getMethod*, BeanUtils.instantiateClass/copyProperties/property descriptors), whose JDK call happens inside Spring on an argument. ClassUtils.isPresent and ReflectionUtils.makeAccessible stay excluded, as does Spring AOT-generated code. |
GRAAL-RES-001 | Fix | Also flags new ClassPathResource(...) and ResourcePatternResolver.getResources(...); names passed through Spring's resource abstraction are resolved later, outside constant-resource registration. |
GRAAL-JMX-001 | Fix | No longer flags ManagementFactory.getPlatformMBeanServer(), which Native Image substitutes with an in-process server that needs no --enable-monitoring. Now flags MBean registration and attribute access, JMX proxies, connector servers, and remote clients, whose ManagementFactory methods silently return null without jmxclient. |
SPRING-AOT-003 | Fix | Adds @ConditionalOnCloudPlatform and @ConditionalOnThreading, which read the deployment environment and freeze at AOT time. @ConditionalOnJndi stays excluded: JNDI is equally unavailable on the build machine and in a standalone native executable. |
GRAAL-CLASSGEN-001, GRAAL-SEC-001, GRAAL-SER-002, GRAAL-REFLECT-005, GRAAL-FFM-001 | Guidance | Experimental run-time class loading in GraalVM 25.1+; runtime provider registration is disallowed by default and only existing provider instances can be reordered; constant ObjectInputFilter patterns register exact classes; unregistered unsafe allocation fails with MissingReflectionRegistrationError only under exact handling; FFM is enabled by default and needs --enable-native-access. |
GRAAL-REFLECT-006 | Add (MEDIUM) | Application types bound with Jackson or Spring's HTTP clients inside a method body, which Spring AOT does not infer binding hints for. 2 of 3 reviewers in favour; the dissent (method-wide class-literal co-occurrence) was resolved by requiring the class literal on the binding call's source line and by skipping types covered by handler signatures, @RegisterReflectionForBinding, or an application RuntimeHintsRegistrar. |
GRAAL-JDK-003 | Add (MEDIUM) | Non-trivial finalize() overrides, which Native Image never invokes. 3 of 3 in favour; two of three reviewers chose MEDIUM because cleanup silently disappears. |
SPRING-AOT-006 | Add (MEDIUM) | getBean(name/type, args...) and ObjectProvider.getObject(args...) bypass AOT instance suppliers. 3 of 3 in favour. |
SPRING-AOT-007 | Add (MEDIUM) | BeanDefinitionRegistryPostProcessor beans run again in the generated context. 3 of 3 in favour; limited to stereotype classes and @Bean return types, excluding both AOT processor interfaces. |
SPRING-AOT-008 | Add (MEDIUM) | @RefreshScope / @Scope("refresh") beans; Spring Cloud does not support context refresh under AOT or native images. 3 of 3 in favour. |
Deliberately not added: URL-protocol enablement (call sites cannot reveal the protocol), JFR custom events (unrecorded events degrade silently rather than fail), --exact-reachability-metadata and -H:Preserve (build flags, not bytecode evidence), circular dependencies (needs the bean graph; Spring Boot already rejects cycles by default), and imprecise @Bean return types (speculative).
The generated scaffold was re-validated against reachability-metadata schema 1.2.0, the newest schema shipped with GraalVM 25.4.
Earlier 2026 readiness audit
The catalogue was audited check by check against current GraalVM Native Image documentation and source, Spring Framework 7 and Spring Boot 4.1 AOT documentation, the GraalVM tracing agent and reachability-metadata repository, and the build-time approaches used by Spring AOT, Quarkus, and Micronaut. A check changed only where the application bytecode or classpath gives BootUI a reliable signal.
| Audited ID | Decision | Result |
|---|---|---|
GRAAL-REFLECT-001 | MODIFY | Cover the current Class reflection lookup surface (arrayType, record, sealed, nest, signer, and nested-class lookups). |
GRAAL-REFLECT-002 | MODIFY | Retain dynamic class loading, but acknowledge constant resolution and experimental run-time class loading. |
GRAAL-REFLECT-003 | MODIFY | Remove legacy allowWrite advice, which is not part of the unified schema. |
GRAAL-REFLECT-004 | KEEP | Member-annotation access remains a precise reflection-metadata signal. |
GRAAL-REFLECT-005 | KEEP | Unsafe.allocateInstance still has an explicit unsafeAllocated metadata requirement. |
GRAAL-PROXY-001 | MODIFY | Keep the call-level signal and document the unified structured proxy type. |
GRAAL-RES-001 | MODIFY | Cover ClassLoader.getResources/resources and Module.getResourceAsStream; document resource: URL semantics. |
GRAAL-RES-002 | MODIFY | Correct bundle guidance to resources[].bundle in the unified schema. |
GRAAL-SERVICE-001 | REMOVE | Native Image's enabled-by-default ServiceLoaderFeature registers reachable service files and providers; a blanket call-site warning is no longer actionable. |
GRAAL-SER-001 | MODIFY | Keep the low-noise INFO inventory and use reflection[].serializable guidance. |
GRAAL-SER-002 | MODIFY | Keep active serialization calls and use the current unified schema. |
GRAAL-INIT-001 | REMOVE | Native Image will not automatically build-time-initialize classes with unsafe side effects; the remaining risk depends on an explicit build flag BootUI cannot observe. |
GRAAL-INIT-002 | REMOVE | Build-machine state is risky only when a class is forced to build-time initialization, which application bytecode does not reveal. |
GRAAL-NATIVE-001 | MODIFY | Limit this check to native-library loading; move Unsafe class-definition calls to class generation. |
GRAAL-NATIVE-002 | MODIFY | Native Image creates Java-to-native wrappers for reachable native declarations automatically; stop inventing JNI metadata for them and explain the native-to-Java callback boundary. |
GRAAL-CLASSGEN-001 | MODIFY | Account for experimental -H:+RuntimeClassLoading and Predefined Classes without presenting either as a transparent compatibility guarantee. |
GRAAL-JDK-001 | KEEP | Runtime javac lookup remains incompatible with the normal closed-world executable. |
GRAAL-JDK-002 | MODIFY | Service discovery is handled automatically, but each script engine's dynamic execution still needs a validated native integration. |
GRAAL-SCAN-001 | KEEP | Runtime classpath scanning remains an explicit Spring Framework AOT limitation. |
SPRING-AOT-001 | MODIFY | Use Spring's exact singleton-transformation boundary and recommend bean-definition registration APIs. |
SPRING-AOT-002 | MODIFY | Exclude classes marked org.springframework.aot.generate.Generated and pin Spring 7 BeanRegistrar suppliers as supported. |
SPRING-AOT-003 | SPLIT | Keep environment-sensitive conditions at MEDIUM; move bean-referencing expressions to unique SPRING-AOT-005 at HIGH. Add @ConditionalOnBooleanProperty and stop flagging classpath-only Boot conditions. |
SPRING-AOT-004 | MODIFY | Programmatic contexts need review, but GenericApplicationContext.refreshForAotProcessing disproves the old absolute wording; exclude generated AOT code. |
GRAAL-SPEL-001 | MODIFY | Keep programmatic parsing and clarify that runtime property access can require reflection metadata. |
GRAAL-MH-001 | MODIFY | Add the omitted MethodHandles.Lookup.findClass lookup. |
GRAAL-SEC-001 | MODIFY | Flag observable Security.addProvider/insertProviderAt calls, not an unused Provider subclass. |
GRAAL-JMX-001 | MODIFY | Document experimental monitoring and the required structured proxy metadata for standard MBean interfaces. |
GRAAL-JMX-002 | KEEP | Dynamic/model MBeans remain explicitly unsupported; the StandardMBean exclusion is correct. |
GRAAL-FFM-001 | MODIFY | Flag actual Linker.downcallHandle/upcallStub calls instead of a passive Linker type reference. |
Primary references:
- GraalVM Reachability Metadata, including reflection, resources, serialization, JNI, FFM, and the unified schema.
- GraalVM class initialization and the current run-time class-loading source documentation.
- GraalVM's enabled-by-default
ServiceLoaderFeature. - Spring Framework AOT and Spring Boot's static-hints location.
The removed initialization checks remain useful review topics for a build that explicitly uses --initialize-at-build-time, but they are documentation concerns rather than reliable bytecode findings.
BootUI cannot recover string argument values or data flow, proxy interface arrays, Unsafe target classes, JNI calls made inside native code, FFM memory layouts, native-image build flags, or the exact profiles and properties used during Spring AOT.
Follow-up precision audit
The follow-up audit in #958 reviewed all 27 active checks, the three retired IDs, and the collection/report/metadata/Docker/install paths. It uses the project's Spring Boot 4.1.1 baseline, Spring Framework 7.0.9, Spring Security 7.1.1, and release-pinned GraalVM 25.0.0 sources. Boot 4.0.8 was consulted only as historical comparison. Java 17 application bytecode remains supported; Boot 4 native compilation requires GraalVM 25+, independently of the JVM on which BootUI runs.
| Active ID | Disposition | Evidence and boundary |
|---|---|---|
GRAAL-REFLECT-001 | Update guidance | Constant analysis and framework hints can cover calls; a Spring-managed bean is not a guarantee that every custom reflective member access is covered. |
GRAAL-REFLECT-002 | Update guidance | Name lookup within the image differs from loading genuinely new bytecode; experimental support is distribution/release-specific. |
GRAAL-REFLECT-003 | Retain | Deep access remains review evidence; metadata and module opens solve different problems. |
GRAAL-REFLECT-004 | Retain | Member-annotation queries remain a low-severity prompt, not complete AnnotatedElement coverage. |
GRAAL-REFLECT-005 | Retain | Actual Unsafe allocation needs target-specific review; no target is invented. |
GRAAL-PROXY-001 | Retain | Preserve constant-array and Spring AOT caveats; metadata needs the actual ordered interface combination. |
GRAAL-RES-001 | Retain | Constant resources may be registered automatically; computed names and final embedding remain unknown. |
GRAAL-RES-002 | Retain | The modern bundle entry is valid; required locales must be verified separately. |
GRAAL-SER-001 | Retain INFO | Serializable is an inventory marker, not proof that an object is serialized or missing hints. |
GRAAL-SER-002 | Retain | Actual object-stream operations need review; Java serialization is distinct from JSON binding. |
GRAAL-NATIVE-001 | Retain | Library loading is supported; packaging, symbols and callbacks need workload-specific validation. |
GRAAL-NATIVE-002 | Retain | Native declarations do not identify native-to-Java callbacks; no inferred JNI registrations. |
GRAAL-CLASSGEN-001 | Update guidance | GraalVM 25.0.0 runtime loading only supports trivial classes without fields or methods; master documentation is not a general workaround. |
GRAAL-JDK-001 | Retain | Runtime javac lookup is not supplied by an ordinary closed-world executable. |
GRAAL-JDK-002 | Retain | A documented engine-specific native integration may work; service discovery alone is not the problem. |
GRAAL-SCAN-001 | Update detection | Match actual ClassGraph/Reflections scan/discovery operations, not configuration or existing-result consumption. Execution phase remains unknown. |
SPRING-AOT-001 | Retain | Singleton objects cannot contribute generated bean construction; supported bean-definition registration remains the remedy. |
SPRING-AOT-002 | Update guidance | Hints alone cannot repair opaque-supplier code-generation rejection; use supported definitions or an AOT contribution. |
SPRING-AOT-003 | Retain | Conditions changing bean presence freeze at AOT time; improved explicit bean-reference classification stays separate in 005. |
SPRING-AOT-005 | Update detection | Recognize quoted names and factory dereferences after @/&, while ignoring quoted text and &&. |
SPRING-AOT-004 | Retain | Secondary contexts need review; build-time AOT harnesses are not proven runtime failures. |
GRAAL-SPEL-001 | Update guidance | Annotation-driven SpEL can also require application-specific hints; neither parsing style establishes native compatibility. |
GRAAL-MH-001 | Retain | Explicit lookups remain useful signals subject to constant analysis. |
GRAAL-SEC-001 | Retain | Actual provider registration is stronger evidence than an unused subclass; migration flags remain version-dependent. |
GRAAL-JMX-001 | Retain | Monitoring and standard-interface metadata remain explicit, experimental configuration. |
GRAAL-JMX-002 | Retain | Dynamic/model MBeans remain unsupported; preserve the supported StandardMBean exclusion. |
GRAAL-FFM-001 | Retain | Actual call-handle creation matters, not passive FFM types; descriptors and native-access permission are separate. |
GRAAL-SERVICE-001, GRAAL-INIT-001, and GRAAL-INIT-002 remain retired for the reasons in the earlier audit. GRAAL-REFLECT-003 and GRAAL-REFLECT-004, listed as retained here, were retired by the October 2026 audit. Retired IDs are never reused. No speculative additions are made: absent handwritten JSON, a metadata-free JAR, an interface-returning bean method, or the current JVM version does not alone establish a native-readiness defect.
Output and collection dispositions
| Surface | Disposition |
|---|---|
| Base-package collection, lazy factory and shared ArchUnit import | Retained, on demand and application-package-scoped; this is not a native reachability graph or a hard class-count/time ceiling. |
| Registry, error findings, sorting, sample masking, DTOs and dismissals | Retained with 27 stable IDs; report wording explicitly distinguishes observations from execution and hint coverage. |
| Passive record/JPA/Serializable candidates and broad member entries | Retained as optional scaffolding, not evidence of need or completeness. Existing AOT/binding hints can overlap. |
| JSON generation, proxy/Unsafe/FFM completion guidance and inferred JNI | Retained modern shapes, escaping and deterministic entries; no fabricated interface arrays, descriptors or callback targets. |
Self-typeReached guards | Retained with an explicit first-use limitation below. Blindly removing guards could retain unused or optional types; a semantic redesign needs isolated native fixtures. |
| Resource defaults | Retained conventional globs, not discovered resource accesses or verified locale coverage. |
| Bundled metadata and repository lookup | Retained bounded, opt-out transport and exact/tested/default-for matching. File presence or repository selection is not proof of validity, build inclusion or compatibility. |
| Nested dependency inspection | Corrected to apply the remaining 500-library budget before opening nested streams; exactly full inputs do not falsely claim omitted entries. |
| Metadata/Docker/combined writes | Retained explicit actions, non-clashing additional-hints path, packaged-run exclusion, confinement and user-file preservation. |
| Docker generation | Retained build variants/images; uses the documented public option spelling and qualifies remaining shared-library requirements. |
| MVC/WebFlux, MCP/CLI, Vue and availability | Retained shared report/actions and command names; MCP scan excludes dependency lookup. Quarkus and already-native remain not applicable. |
| BootUI's own runtime hints | Retained separately; they are not a complete host-application hint inventory. |
Remaining unknowns include dead/unreachable code, actual arguments and execution phases, custom/composed property-driven SpEL, effective AOT configuration, external metadata activation, native initialization policy, and native-library deployment. Repository coverage also relies on heuristic coordinates for ambiguous shaded archives and a conventional metadata-file link. These are not silently promoted to compatibility verdicts.
Version-pinned evidence for the corrections:
- Spring AOT and hint boundaries and opaque supplier rejection.
- SpEL bean references and Spring Security annotation-driven native hints.
- ClassGraph operations and Reflections scanning versus stored metadata.
- GraalVM 25.0.0 reachability semantics, runtime-loading limitations, and mostly-static executable guidance.
What BootUI does
The scanner detects your base packages from the @SpringBootApplication configuration through AutoConfigurationPackages, imports the compiled .class files from them with ArchUnit's ClassFileImporter, and evaluates every registered check against the imported classes. Importing is bounded to your own base packages, never the whole classpath, and runs only when you invoke the scan. The controller caches the last report.
ArchUnit comes in transitively with bootui-spring-boot-starter. The panel is available when ArchUnit is on the classpath and a base package is resolvable. When no classes can be imported, it degrades to a stable empty report with a reason rather than failing.
Beyond the checks, a scan produces four things.
Dependency survey
With Include dependencies on, which is the default, the scan reports which third-party JARs already ship bundled reachability metadata under META-INF/native-image/.
BootUI recognizes the unified reachability-metadata.json name and the canonical legacy files — reflect-config.json, resource-config.json, proxy-config.json, serialization-config.json, jni-config.json, and predefined-classes-config.json. Arbitrary JSON does not count. A JAR carrying only native-image.properties is reported as bundling build arguments, not reachability metadata.
The survey opens classpath JARs only, stops before opening nested libraries beyond its remaining 500-JAR budget, and warns when that cap is hit. A library without bundled metadata may need your own configuration, repository metadata, or the tracing agent. Repository coverage uses an exact metadata or tested-version match first, then the first valid Java regular expression in the repository row's default-for field.
Why one classpath entry can expand into several dependencies
When the application runs as a Spring Boot fat jar, java.class.path contains only the outer launcher jar, because LaunchedURLClassLoader resolves BOOT-INF/lib/*.jar through nested: URLs that never populate that system property. The survey therefore expands the launcher into one inspected dependency per nested library, rather than reporting a single dependency named after your launcher jar.
A shaded jar bundles more than one META-INF/maven/<groupId>/<artifactId>/pom.properties: one for itself and one for each relocated dependency. The survey prefers the descriptor whose artifactId and version match the jar's own file name, which recovers the shaded jar's own coordinates in the common case.
A reachability-metadata.json scaffold
Built from your own classes: unified reflection and serialization registrations, standard configuration and logging resource globs, and explicit proxy, Unsafe, and FFM completion guidance where those calls are detected. Download it from the panel.
A tailored Dockerfile-native
A multi-stage build that detects Maven or Gradle, with or without a wrapper, compiles with the matching command such as ./mvnw -Pnative -DskipTests clean native:compile or ./gradlew nativeCompile, and packages the resulting executable, named after the resolved artifactId, into gcr.io/distroless/base-debian12:nonroot. That base runs as a non-root user and ships glibc but no shell, package manager, curl, perl, or tar. With no wrapper in the project, the build stage installs a pinned Maven or Gradle release, exposed as a Docker ARG, so the image is self-contained. Download it from the panel, or write it into the project root.
Verify linking and health probes yourself
The native image requests mostly static linking with --static-nolibc, but some applications still need libstdc++, libgcc, or dynamically loaded native libraries. Inspect the executable's dependencies and exercise it in the exact runtime image, or use a runtime that carries the libraries it needs.
Distroless has no shell or curl, so there is no Docker HEALTHCHECK. Probe /actuator/health, or the web root, from your orchestrator instead.
Writes into the project
When BootUI detects an exploded build, such as mvn spring-boot:run or an IDE, rather than a packaged jar, it can write both artifacts into the source tree. The accordion's top All files drawer does both in one action and reports each file's outcome.
The scaffold goes to src/main/resources/META-INF/native-image/<groupId>/<artifactId>-additional-hints/reachability-metadata.json, with coordinates resolved from build-info.properties or the project pom.xml, falling back to bootui-generated/additional-hints. Spring Boot reserves <groupId>/<artifactId>/ for AOT-generated output, so the suffix keeps the static scaffold from colliding with Spring AOT. The Dockerfile-native is written to the project root.
Writes are fail-closed. The scaffold write is confined under src/main/resources, and neither write overwrites a file BootUI did not generate.
BootUI's own hints
Separately from the scan, BootUI registers Spring AOT runtime hints for its own needs in BootUiRuntimeHints, covering its runtime-scanned classpath resources, its Jackson DTO records, and the reflective calls the Heap Dump, Security, and Pentesting panels use. BootUiAutoConfiguration contributes them, so applications using the starter do not need to copy BootUI-specific hints into their own configuration.
What BootUI does not do
- It is not a replacement for the GraalVM tracing agent or an actual native-image build. Static analysis cannot see reflection driven by runtime data, so the checks are heuristic review prompts, and the generated metadata is a scaffold to review and complete — not a finished file.
- It does not analyze third-party dependency bytecode for readiness; for dependencies it only reports whether classpath JARs ship bundled reachability metadata JSON or native-image build arguments.
- It does not modify, compile, or instrument application code; it reads already-compiled bytecode.
- Spring AOT covers supported framework contracts, not every reflective operation in a Spring-managed bean. Compare findings with the actual generated/custom/dependency hints before deciding that they are already covered.
- Imported call sites may be dead code or run only during build-time AOT processing. BootUI does not establish their native-image reachability or execution phase.
Detecting missing metadata at development time
These checks are static, build-independent heuristics. They cannot see reflection driven by runtime-only data, such as a class name read from a config file, so a clean scan does not guarantee that a native image runs correctly.
GraalVM's own recommended complement is to make missing metadata fail loudly during development rather than surface as a silent runtime bug:
- Pass
--exact-reachability-metadata(introduced in GraalVM 23 for debugging and still opt-in in GraalVM 25), or, to scope exact handling to specific packages,--exact-reachability-metadata=<comma-separated-package-list>, tonative-imageat build time to opt in to the stricter, more debuggable handling of reflection, resources, JNI and serialization. Use--exact-reachability-metadata-path=<classpath-or-module-path-entry>when exact handling should apply to all types originating from selected path entries. - Run the native image with
-XX:MissingRegistrationReportingMode=Warnto see every place a registration is missing without crashing, or with-XX:MissingRegistrationReportingMode=Exit— recommended for automated tests — to make the application print the error with a full stack trace and exit immediately the first time a missing registration is hit, including ones a broadcatch (Throwable t)would otherwise silently swallow.
The Reachability Metadata reference carries the authoritative flag documentation.
This is a recommended practice to pair with the panel's checks, not a check itself. Every other check on this page fires only when a specific risky construct is present in bytecode, whereas these flags suit essentially every native-image build regardless of the code. There is no bytecode condition to scan for, so an automated check would either fire unconditionally or have to parse pom.xml or build.gradle for existing native-image arguments, a data source no other check uses.
The generated reachability-metadata.json
The scaffold uses the common subset of the GraalVM 25.0.0 schema 1.1.0 and the current schema 1.2.0. Serialization is represented by serializable: true on a reflection registration (not a top-level serialization array). BootUI does not mark Java native declarations jniAccessible: those declarations receive their Java-to-native wrappers automatically, while the native-to-Java callbacks that do need JNI metadata are not observable in Java bytecode. Each named type carries a condition.typeReached guard. Reflection candidates include concrete records and Serializable types plus JPA entities and mapped superclasses, including abstract persistence base types. Resource globs cover application*.properties / application*.yml / application*.yaml, logback-spring.xml, and log4j2-spring.xml.
Review first-use conditions
A class literal does not satisfy typeReached. Reflective name lookup and serialization descriptor lookup can need metadata before the target class is initialized, so a self-guarded entry may be inactive when it is first needed. Use an owning feature reached before that access, or a narrowly justified unconditional entry.
Do not remove every guard blindly: these broad, passive candidates can include unused or optional types. Existing framework hints and build-time initialization can mask the ordering problem, so run an isolated native first-use test before relying on a changed guard policy.
Static bytecode analysis cannot reliably recover runtime-computed proxy interface arrays, the Class argument passed to Unsafe.allocateInstance, or FFM FunctionDescriptor layouts. When those checks fire, the generated file adds explicit review instructions rather than inventing unsafe registrations.
Dynamic proxies use a structured reflection type such as {"type":{"proxy":["com.example.Interface"]}}. FFM findings scaffold the schema-valid foreign object with empty downcalls, upcalls, and directUpcalls arrays, which you or the tracing agent complete with the real memory-layout descriptors.
Review the generated file with the tracing agent, then place it under src/main/resources/META-INF/native-image/<groupId>/<artifactId>-additional-hints/ in your application. Spring Boot writes generated AOT hints to <groupId>/<artifactId>/, so static hints must use a non-clashing directory. The panel substitutes the resolved groupId/artifactId into that hint whenever it can determine them — from build-info.properties (which works even when running from a packaged jar) or the project pom.xml — and keeps the <groupId>/<artifactId> placeholders only when no coordinates can be resolved.
Severity scale
Severity reflects the worst plausible impact if the finding is real, not how likely it is:
| Severity | Meaning | Examples |
|---|---|---|
| CRITICAL | The most severe native-image impact. No active check emits it. | — |
| HIGH | Needs substantial native-image integration, or Spring AOT cannot safely capture it at run time. | Runtime class generation or Java compilation, script-engine discovery without a native integration, runtime classpath scanning, runtime instance suppliers, bean-referencing expression conditions, secondary context creation, dynamic and model MBeans |
| MEDIUM | GraalVM cannot resolve it at build time, or the native image behaves differently, so it usually fails or silently degrades at run time. | Reflection, programmatic data binding, dynamic class loading, unsafe allocation, dynamic proxies, active JDK serialization, finalizers, SpEL, method handles, frozen AOT conditions, explicit-argument bean creation, replayed registry post-processors, refresh scope, runtime security-provider registration, runtime singleton registration |
| LOW | Often needs extra configuration. | Runtime resource loading, resource bundles, native access, native methods, JMX, foreign functions |
| INFO | Matters only if the type is actually used that way. | Serialization |
The scan evaluates every registered check, but the panel lists only checks that found something to review. Findings are ordered by severity, then by the number of occurrences, and include a few sample detail lines.
Filter the list, then jump to a check — the detail below narrows to match.
GRAAL-REFLECT-001MEDIUMReflective API usage may need reflection metadataGRAAL-REFLECT-002MEDIUMDynamic class loading may need reflection metadataGRAAL-REFLECT-005MEDIUMUnsafe.allocateInstance bypasses construction and needs unsafeAllocated metadataGRAAL-REFLECT-006MEDIUMProgrammatically bound application types may need binding hintsGRAAL-PROXY-001MEDIUMDynamic JDK proxies may need proxy metadataGRAAL-RES-001LOWRuntime resource loading may need resource metadataGRAAL-RES-002LOWResource bundle loading may need resource-bundle metadataGRAAL-SER-001INFOSerializable types may need serialization metadataGRAAL-SER-002MEDIUMActive JDK serialization may need serialization metadataGRAAL-NATIVE-001LOWDynamically loaded native libraries need native-image reviewGRAAL-NATIVE-002LOWNative method declarations require a loadable native implementationGRAAL-FFM-001LOWForeign Function downcalls/upcalls may need foreign metadata in native imagesGRAAL-CLASSGEN-001HIGHRuntime class generation needs experimental native-image supportGRAAL-JDK-001HIGHThe system Java compiler is unavailable in native imagesGRAAL-JDK-002HIGHJSR-223 script engines require native-image-specific supportGRAAL-JDK-003MEDIUMfinalize() overrides are never invoked in native imagesGRAAL-SCAN-001HIGHClasspath discovery calls require runtime versus build-time reviewSPRING-AOT-001MEDIUMRuntime bean singleton registration cannot be transformed by Spring AOTSPRING-AOT-002HIGHProgrammatic instance suppliers are not captured by Spring AOTSPRING-AOT-003MEDIUMEnvironment-sensitive bean conditions freeze selection at AOT build timeSPRING-AOT-005HIGHBean-referencing @ConditionalOnExpression can initialize beans too earlySPRING-AOT-004HIGHProgrammatic ApplicationContext creation requires AOT reviewSPRING-AOT-006MEDIUMExplicit-argument bean retrieval may bypass AOT instance suppliersSPRING-AOT-007MEDIUMBean-definition registry post-processors may run again under AOTSPRING-AOT-008MEDIUMRefresh-scoped bean declarations are not supported under Spring AOTGRAAL-SPEL-001MEDIUMProgrammatic SpEL expressions may require application-specific reflection hintsGRAAL-MH-001MEDIUMNon-constant MethodHandle lookups may need reflection metadataGRAAL-SEC-001MEDIUMRuntime security-provider registration needs native-image reviewGRAAL-JMX-001LOWJMX MBeans and connectors need native-image monitoring and metadata reviewGRAAL-JMX-002HIGHDynamic/model MBeans are not supported by native-image JMX
Reflection
GRAAL-REFLECT-001 - Reflective API usage may need reflection metadata
- Severity: MEDIUM
- Inspects: calls to the reflection API (
Class.forName,Class.newInstance,Class.arrayType, method/field/ constructor lookups, record/sealed/nest/signer/nested-class lookups,Method.invoke,Constructor.newInstance, andFieldvalue get/set accessors), and Spring's reflection facades that perform the same lookups on their arguments:ReflectionUtils(findField,findMethod,getField,setField,invokeMethod,doWith*, declared-method lookups,accessibleConstructor),ClassUtils(forName,resolveClassName,getMethod*,getConstructorIfAvailable,getStaticMethod), andBeanUtils(instantiateClass,findMethod,findDeclaredMethod,getPropertyDescriptor(s),copyProperties). - Exclusion: reflective metadata accessors such as
Field.getName(),ClassUtils.isPresent(the optional-dependency probe),ReflectionUtils.makeAccessible, and Spring AOT-generated code, which ships with its own hints. - Fires when: an application class uses those reflection APIs; targets that native-image analysis resolves as constants may be registered automatically, but a facade performs the JDK call inside Spring on an argument, and runtime-computed reflective targets need explicit metadata.
- Recommendation: register the reflectively accessed types in
reachability-metadata.json, or for application code register them with Spring's RuntimeHints (e.g. via@ImportRuntimeHints/RuntimeHintsRegistrar), after reviewing existing hints. Spring AOT covers known framework contracts, not all custom reflective accesses in managed beans.BeanUtils.copyPropertiesand property-descriptor lookups find no accessors on an unregistered type, so a copy can silently do nothing; register those types with@RegisterReflectionor@RegisterReflectionForBinding.
GRAAL-REFLECT-002 - Dynamic class loading may need reflection metadata
- Severity: MEDIUM
- Inspects: calls to
ClassLoader.loadClass. - Fires when: an application class loads a class by name at run time. Native Image can resolve some constant calls; runtime-computed lookups within the image may need metadata. Loading new bytecode is a separate, release-dependent experimental capability, not a general metadata remedy.
- Recommendation: register the dynamically loaded types under
reflectioninreachability-metadata.json, or replaceClassLoader.loadClasswith direct class literals where possible.
Retired: GRAAL-REFLECT-003 and GRAAL-REFLECT-004
GRAAL-REFLECT-003 (deep reflection through setAccessible / privateLookupIn) and GRAAL-REFLECT-004 (reflective annotation access on members) were retired in the October 2026 audit and their IDs are never reused. Neither operation needs metadata of its own: the member lookup and access that do are GRAAL-REFLECT-001 and GRAAL-MH-001, and a registered member carries its annotations.
GRAAL-REFLECT-005 - Unsafe.allocateInstance bypasses construction and needs unsafeAllocated metadata
- Severity: MEDIUM
- Inspects: calls to
allocateInstance(Class)onsun.misc.Unsafeorjdk.internal.misc.Unsafe. - Fires when: a class allocates an instance via
Unsafeinstead of a constructor. Unsafe allocation bypasses the construction path native-image's reachability analysis tracks, so the allocated type needs its own metadata; otherwise the allocation fails at run time — withMissingReflectionRegistrationErrorunder--exact-reachability-metadataor--future-defaults=exact-reflection, and with anIllegalArgumentExceptionby default. - Recommendation: register the allocated type under
reflectioninreachability-metadata.jsonwith"unsafeAllocated": true(in addition to its normal type registration), or replaceUnsafe.allocateInstancewith a public constructor or factory method where possible.
GRAAL-REFLECT-006 - Programmatically bound application types may need binding hints
- Severity: MEDIUM
- Inspects: application types passed as a class literal to a binding call inside a method body: Jackson 2 and 3
ObjectMapper/ObjectReader(readValue,readValues,convertValue,treeToValue,readerFor,forType),RestTemplate/RestOperations(getForObject,getForEntity,postForObject,postForEntity,patchForObject,exchange),RestClient.ResponseSpec(body,toEntity), and WebClient'sResponseSpec/ClientResponse(bodyToMono,bodyToFlux,toEntity,toEntityList,toEntityFlux). Only overloads taking aClasscount, and a type is associated with a call only when its class literal is on the call's source line. - Exclusion: types in the scanned packages that already have an observable binding hint — a parameter or return type (including generic arguments) of an application
@RequestMappingor@HttpExchangemethod, a type named by@RegisterReflectionForBinding, or a class literal referenced by an applicationRuntimeHintsRegistrar— plus JDK and library types and Spring AOT-generated code. - Fires when: an application type is bound programmatically. Spring AOT infers binding hints for handler and HTTP interface signatures, not for these calls, so without reflection metadata for constructors, fields, and accessors a native image can bind an empty object or fail to find a creator.
- Limitations: serializing an instance (
writeValueAsString(order)),ParameterizedTypeReference/TypeReferencetargets, calls split across source lines, and hints in JSON metadata files are not observed. - Recommendation: annotate the calling class or method with
@RegisterReflectionForBinding(Target.class), which also registers the types its properties expose, or register binding hints throughBindingReflectionHintsRegistrarin aRuntimeHintsRegistrar. Exercise the call in the native executable before relying on it.
Dynamic proxies
GRAAL-PROXY-001 - Dynamic JDK proxies may need proxy metadata
- Severity: MEDIUM
- Inspects: calls to
Proxy.newProxyInstanceandProxy.getProxyClass. - Fires when: a class creates or obtains a JDK dynamic proxy whose interface list must be known to native-image. When the interface array is a compile-time constant, native-image may auto-register the proxy (similar to how constant-arg
Class.forNameis auto-folded); runtime-computed interface sets always need explicit registration. - Recommendation: declare the proxied interfaces in
reachability-metadata.json, or for application code register them with Spring's RuntimeHints (RuntimeHints.proxies().registerJdkProxy(...)via@ImportRuntimeHints). Spring's own proxy mechanisms are covered by Spring AOT.
Resources
GRAAL-RES-001 - Runtime resource loading may need resource metadata
- Severity: LOW
- Inspects: calls to
Class/ClassLoadergetResource,getResources/resources, andgetResourceAsStream,Module.getResourceAsStream, Spring classpath resource handles (new ClassPathResource(...)), andResourcePatternResolver.getResources(...)pattern lookups, outside Spring AOT-generated code. - Fires when: a class loads a resource by name that must be embedded in the native image to be available at runtime. Native Image automatically registers
Class.getResource/getResourceAsStreamonly when both the receiver class and resource name are constant; runtime-computed names, and names passed through Spring's resource abstraction (resolved later inside Spring), need registration unless Spring Boot's built-in hints for application configuration, banner, messages, and logging configuration already cover them. Pattern lookups only see resources that were embedded. - Recommendation: register the loaded resource paths (as globs) in
reachability-metadata.json, or for application code register them with Spring's RuntimeHints (RuntimeHints.resources()via@ImportRuntimeHints) so native-image bundles them. Embedded resources useresource:URLs; open a stream instead of treatingURL.getFile()as a filesystem path.
GRAAL-RES-002 - Resource bundle loading may need resource-bundle metadata
- Severity: LOW
- Inspects: calls to
ResourceBundle.getBundle. - Fires when: a class loads a localized resource bundle whose
.propertiesfiles must be embedded in the native image. - Recommendation: add each bundle base name as a
resourcesentry with abundlefield inreachability-metadata.json, and verify the locales required in deployment separately.
Serialization
GRAAL-SER-001 - Serializable types may need serialization metadata
- Severity: INFO
- Inspects: application classes that implement
java.io.Serializable(concrete, non-enum types). - Fires when: an application type implements
Serializable; types that are actually serialized at runtime require serialization metadata. If GRAAL-SER-002 (active JDK serialization) also fires, the listed types are likely serialized at runtime and should be reviewed carefully. Enum types are excluded because GraalVM handles standard enum serialization automatically. - Recommendation: if these types are serialized (e.g. via the JDK serialization protocol), add
reflectionentries withserializable: trueinreachability-metadata.json.
GRAAL-SER-002 - Active JDK serialization may need serialization metadata
- Severity: MEDIUM
- Inspects: calls to
ObjectOutputStream.writeObject/writeUnsharedandObjectInputStream.readObject/readUnshared. - Fires when: a class serializes or deserializes types through the JDK serialization protocol at run time, which native-image must be told about explicitly.
- Recommendation: register every serialized type under
reflectionwithserializable: truein GraalVM 25's unifiedreachability-metadata.jsonschema (or with Spring's RuntimeHints serialization registration), or prefer a serialization format that does not need build-time registration. Native Image also registers the exact classes named in a compile-time-constantObjectInputFilter.Config.createFilter("pkg.SerializableClass;!*;")pattern (package wildcards do not register), which restricts deserialization on the JVM as well.
Native access
GRAAL-NATIVE-001 - Dynamically loaded native libraries need native-image review
- Severity: LOW
- Inspects: native-library loading (
System.loadLibrary,System.load,Runtime.loadLibrary,Runtime.load). - Fires when: a class loads a native library whose file and symbols must be available to the executable. Ordinary Unsafe memory access is intentionally not flagged; allocation and class definition are covered by their precise reflection and class-generation checks.
- Recommendation: link the library into the image or deploy it where the executable can load it. If the native code dynamically looks up Java callbacks, collect and register those targets with the tracing agent.
GRAAL-NATIVE-002 - Native method declarations require a loadable native implementation
- Severity: LOW
- Inspects: application classes that declare
nativemethods. - Fires when: a class declares a
nativemethod. Native Image creates the Java-to-native wrapper for a reachable declaration automatically, but the backing implementation must still be linked or loadable. Dynamic calls in the opposite direction, from native code into Java, require JNI metadata that Java bytecode cannot identify. - Recommendation: ensure the implementation is linked or loadable. Exercise native-to-Java callbacks under the tracing agent and add
jniAccessible: trueonly to the actual Java targets. BootUI does not infer JNI entries from declarations.
GRAAL-FFM-001 - Foreign Function downcalls/upcalls may need foreign metadata in native images
- Severity: LOW
- Inspects: calls to
java.lang.foreign.Linker.downcallHandleandLinker.upcallStub. - Fires when: a class builds native downcall handles or upcall stubs; those down/upcalls reach native symbols that the closed-world analysis cannot see and must be described under
foreigninreachability-metadata.json. Pure heap/off-heapMemorySegment/Arenause, or a passiveLinkerfield that never creates a call handle, does not require this metadata and is not flagged. - Recommendation: register the real native down/upcall descriptors under
foreigninreachability-metadata.json, and pass--enable-native-access=<module-name>(orALL-UNNAMEDfor classpath code) for modules that perform restricted native operations. FFM support is enabled by default starting with GraalVM 25, but metadata and native-access permission solve separate problems. BootUI emits emptyforeignarrays as a safe scaffold; it does not invent function layouts.
Class generation
GRAAL-CLASSGEN-001 - Runtime class generation needs experimental native-image support
- Severity: HIGH
- Inspects: runtime bytecode/class generation (
ClassLoader.defineClass,MethodHandles.Lookup.defineClass/defineHiddenClass/defineHiddenClassWithClassData, UnsafedefineClass/defineAnonymousClass, CGLIBEnhancer, ByteBuddy, Javassist). - Fires when: a class contains generation/definition calls. A default native image cannot define new classes at run time. GraalVM 25.0.x documents experimental runtime loading only for trivial classes without fields or methods; the GraalVM 25.1+ feature releases add experimental run-time class loading (
-H:+RuntimeClassLoading, interpreted, with optional-H:+GraalJITCompileAtRuntimefrom 25.3) with documented limits: no parallel class loading, no reloading of classes already included in the image, and no fallback for members the analysis removed from image classes (often requiring-H:Preserve=package=...). The native-image agent's experimental "Predefined Classes" mode (experimental-class-define-support) can trace and replay a bounded set of previously-seen classes, but it is best-effort: it replays only the exact bytecode traced ahead of time, allows only one class definition per class loader per execution, has no build-time-initialization support, and cannot help when classes are generated with varying names or bytecode (e.g. driven by counters or timestamps) — so it is a narrow escape hatch, not a general fix. - Recommendation: generate classes at build time (e.g. with Spring AOT) or replace them with statically compiled equivalents. If generation cannot be avoided, validate the exact workload with the experimental run-time class loading options of the GraalVM release you ship, or evaluate Predefined Classes for bytecode that is stable across runs; neither is a general compatibility guarantee.
GRAAL-JDK-001 - The system Java compiler is unavailable in native images
- Severity: HIGH
- Inspects: calls to
javax.tools.ToolProvider.getSystemJavaCompiler(). - Fires when: application code requests
javacat run time. A native executable contains application code compiled ahead of time and cannot load newly compiled Java classes into the closed world. - Recommendation: compile or generate code during the application build and include the resulting classes in the native image; do not compile Java source inside the running application.
GRAAL-JDK-002 - JSR-223 script engines require native-image-specific support
- Severity: HIGH
- Inspects: construction of
javax.script.ScriptEngineManager. - Fires when: application code discovers JSR-223 engines at run time. Native Image handles reachable ServiceLoader providers automatically, but engines commonly load or generate executable code dynamically. This is not a blanket claim that every engine is impossible: GraalVM languages can expose documented JSR-223 integrations, but that explicit language/runtime setup must be part of the image.
- Recommendation: remove runtime scripting, replace it with statically compiled application logic, or validate a specific engine's Native Image integration and its resource, reflection, class-loading, and native requirements.
GRAAL-JDK-003 - finalize() overrides are never invoked in native images
- Severity: MEDIUM
- Category: Runtime behavior
- Inspects: application classes, including abstract base classes, that declare a non-static, non-private
void finalize(). - Exclusion: empty bodies (the
finalfinalizer-attack guard) and bodies that only callsuper.finalize(). - Fires when: a finalizer does real work. GraalVM's compatibility guide states that finalizers are not invoked, so that cleanup silently never runs in the native executable although it still runs on the JVM. Generic deprecated-API hygiene for finalizers is not this advisor's concern.
- Recommendation: release resources through an explicit lifecycle (
AutoCloseablewith try-with-resources, or a Spring destroy callback). Usejava.lang.ref.Cleaner, or weak references with a reference queue, only as a safety net for resources a caller may forget to close.
Classpath scanning
GRAAL-SCAN-001 - Classpath discovery calls require runtime versus build-time review
- Severity: HIGH
- Inspects: runtime classpath/component scanning (
ClassPathScanningCandidateComponentProvider.findCandidateComponents, Reflections scanning constructors/scan/static collect, or ClassGraph scan/scanAsync and classpath/module discovery). - Fires when: a class contains a scan/discovery operation. The closed-world native image has no ordinary runtime classpath, but this scan cannot establish whether the call executes at runtime, during AOT, or not at all.
- Exclusion: ClassGraph configuration and existing-result readers; Reflections Store/no-arg construction, queries, and collection from a supplied file/stream. Exclusion here does not prove those operations native-compatible.
- Recommendation: resolve the scanning at build time. For Spring components rely on Spring AOT/component indexing rather than runtime scanning; replace library-based scanning with an explicit, statically known set of types.
Spring AOT
@ImportResource is intentionally not a warning. Spring's AOT refresh invokes configuration parsing and BeanFactoryPostProcessor implementations at build time, so statically declared imported bean definitions are part of the generated context. Runtime-selected or mutable XML is subject to the same fixed-build-input constraint as other bean definitions, but the annotation alone is not a high-confidence readiness problem.
SPRING-AOT-001 - Runtime bean singleton registration cannot be transformed by Spring AOT
- Severity: MEDIUM
- Inspects: calls to
SingletonBeanRegistry.registerSingleton(...). - Fires when: a class registers a singleton instance directly. Spring AOT transforms bean definitions, not singleton instances registered with a BeanFactory, so the instance cannot contribute generated construction code or reachability hints. The singleton still exists at runtime.
- Recommendation: register a bean definition through
@Bean/@Component,BeanDefinitionRegistry,ImportBeanDefinitionRegistrar, or Spring Framework 7's AOT-supportedBeanRegistrar.
SPRING-AOT-002 - Programmatic instance suppliers are not captured by Spring AOT
- Severity: HIGH
- Inspects: bean definitions backed by a programmatic instance supplier (
setInstanceSupplier, or SpringregisterBean/BeanDefinitionBuildermethods with aSupplier). - Fires when: a class registers a bean definition whose instance comes from a supplier lambda; Spring AOT cannot trace through that supplier at build time, so the bean's type and dependencies may be missing from the native image.
- Recommendation: prefer declarative bean definitions (
@Beanmethods / component scanning) whose types Spring AOT can resolve, or use Spring Framework 7'sBeanRegistrar/BeanRegistrarDslfor AOT-friendly programmatic registration. Infrastructure can provide a custom AOT code-generation contribution.RuntimeHintsRegistraralone cannot generate missing bean-instantiation code; add hints separately for remaining dynamic access. - Exclusion: Spring AOT-generated
*__BeanDefinitionsclasses and classes annotatedorg.springframework.aot.generate.Generatedintentionally use suppliers while replaying generated bean definitions. Spring 7BeanRegistraruses aConsumer<BeanRegistry.Spec<?>>whose nested supplier is explicitly AOT-supported and does not match this check.
SPRING-AOT-003 - Environment-sensitive bean conditions freeze selection at AOT build time
- Severity: MEDIUM
- Inspects:
@Profile,@ConditionalOnProperty,@ConditionalOnBooleanProperty,@ConditionalOnCloudPlatform,@ConditionalOnThreading, custom@Conditional, and property-only@ConditionalOnExpressionon application@Configuration/@Component(and stereotype) classes or@Beanmethods, directly or as meta-annotations. - Fires when: a Spring component or
@Beanmethod carries a profile or property condition. Spring AOT evaluates these conditions once at build time; if the active profiles, application properties, platform environment variables, or virtual-thread setting differ between the AOT build and the production runtime, the conditioned beans may be unexpectedly absent or present in the native image. - Exclusion: deliberate
@AutoConfigurationclasses are condition-driven by design and are handled by Spring's AOT processing, so they are not reported. Classpath-only Spring Boot conditions such as@ConditionalOnClassare fixed by the build input and are not reported even on application configuration.@ConditionalOnJndiis not reported either: JNDI is equally unavailable on the build machine and in a standalone native executable. - Recommendation: ensure the profiles, properties, and platform active during the AOT build (native-image compilation) match the intended production configuration — for example set
spring.main.cloud-platformandspring.threads.virtual.enabledexplicitly for the build — or restructure the configuration to use explicit build-time selection rather than runtime conditions.
SPRING-AOT-005 - Bean-referencing @ConditionalOnExpression can initialize beans too early
- Severity: HIGH
- Inspects: Spring components and
@Beanmethods whose@ConditionalOnExpressionvalue contains an explicit SpEL bean reference (@beanName,@'bean.name',&factoryBean, or a quoted factory name). Token whitespace is allowed; unrelated quoted text and the&&operator are ignored. Property-resolved or composed expressions remain unknown. - Fires when: evaluating the condition can initialize a referenced bean before normal post-processing such as configuration-properties binding. This has a distinct stable ID rather than changing the name and severity of
SPRING-AOT-003. - Recommendation: replace bean-referencing SpEL with property/class conditions. If the expression is unavoidable, keep it free of bean references and use build-time-stable inputs.
SPRING-AOT-004 - Programmatic ApplicationContext creation requires AOT review
- Severity: HIGH
- Inspects: constructor calls to
AnnotationConfigApplicationContextorGenericApplicationContext, andSpringApplicationBuilder.child(...)calls outside classes marked as Spring AOT-generated. - Fires when: a class programmatically creates a context. Contexts created at application run time do not use the main context's generated initializer. This is a review rather than an absolute verdict:
GenericApplicationContext.refreshForAotProcessingintentionally creates a context during build-time AOT processing. - Recommendation: consolidate configuration into the main AOT-processed context or use
@Import/@ImportResource. If this is build tooling, callrefreshForAotProcessingand keep it out of runtime paths.
SPRING-AOT-006 - Explicit-argument bean retrieval may bypass AOT instance suppliers
- Severity: MEDIUM
- Inspects:
BeanFactory.getBean(String, Object...),BeanFactory.getBean(Class, Object...), andObjectProvider.getObject(Object...)calls outside Spring AOT-generated code.getBean(name),getBean(name, Class), andgetObject()are not reported. - Fires when: a bean is retrieved with explicit creation arguments. Spring AOT translates bean creation into generated instance suppliers; custom arguments bypass the supplier, so the matching constructor or factory method is introspected reflectively (hints AOT cannot infer) and the autowiring on fields and methods that the supplier performs is skipped.
- Recommendation: replace prototype beans created with custom arguments by a manual factory pattern: a regular bean whose method creates the instance with
new, taking the runtime arguments and its injected collaborators. If explicit-argument retrieval must stay, use constructor injection only on the target bean and register invocation hints for the constructor or factory method Spring selects.
SPRING-AOT-007 - Bean-definition registry post-processors may run again under AOT
- Severity: MEDIUM
- Inspects:
BeanDefinitionRegistryPostProcessorbeans declared by the application — a concrete Spring stereotype class, or a@Beanmethod whose return type is one. - Exclusion: processors that also implement
BeanFactoryInitializationAotProcessororBeanRegistrationAotProcessor, which Spring AOT implicitly excludes from the generated context; Spring AOT-generated code. Processors registered programmatically are not observed. - Fires when: Spring AOT invokes the processor at build time and, as Spring's AOT best practices warn, the generated context invokes it again at run time. At best it repeats work already captured in the generated bean definitions; at worst re-registering a definition fails startup with
BeanDefinitionOverrideExceptionunder Spring Boot's defaultspring.main.allow-bean-definition-overriding=false. - Recommendation: prefer an
ImportBeanDefinitionRegistrarimported with@Import, or Spring Framework 7'sBeanRegistrar, which Spring AOT processes during configuration parsing. If the post-processor must stay a bean, also implementingBeanFactoryInitializationAotProcessorexcludes it from the runtime context; Spring then initializes it and its dependencies during AOT processing, and its contribution must reproduce any effect the generated bean definitions do not already capture (returnnullonly when none remains).
SPRING-AOT-008 - Refresh-scoped bean declarations are not supported under Spring AOT
- Severity: MEDIUM
- Inspects: Spring components and
@Beanmethods annotated or meta-annotated with Spring Cloud's@RefreshScope, or declared with@Scope("refresh"). - Fires when: a refresh-scoped bean is declared. Spring Cloud does not support context refresh for Spring AOT transformations and native images and requires
spring.cloud.refresh.enabled=falsefor them; that property also removes the auto-configured refresh scope these declarations rely on, so in-process refresh does not exist in the native executable. Whether a declaration is active in the AOT build is not observable statically. - Recommendation: for the native build, set
spring.cloud.refresh.enabled=falseand do not ship refresh-scoped beans: bind their configuration at startup and restart the native executable to apply changes. Refresh scope can remain in JVM deployments that still rely on it, including CRaC refresh-on-restore, which the CRaC advisor covers.
GRAAL-SPEL-001 - Programmatic SpEL expressions may require application-specific reflection hints
- Severity: MEDIUM
- Category: this check moved from
ReflectiontoSpring AOT— SpEL reachability is a Spring-library-specific concern (the SpEL bytecode compiler and reflective property access are part of Spring's own AOT story), not a general-purpose reflection construct. The check ID is unchanged (GRAAL-SPEL-001, not renumbered into theSPRING-AOT-*sequence) because check IDs are stable identifiers persisted in user dismissals. - Inspects: calls to
ExpressionParser.parseExpressionandparseRaw(the SpEL programmatic parsing API). - Fires when: a class parses a SpEL expression; property/member access may require reflection hints. Not every expression needs reflection, and the SpEL bytecode compiler is unsupported in native images.
- Recommendation: prefer direct Java where practical. Otherwise review actual target members and existing hints, register missing access, and exercise expressions in the native executable. Annotation-driven
@PreAuthorize,@Value, and@Cacheableexpressions can also require application-specific hints.
Method handles
GRAAL-MH-001 - Non-constant MethodHandle lookups may need reflection metadata
- Severity: MEDIUM
- Category: Reflection
- Inspects: calls to
MethodHandles.Lookuplookup methods:findClass,findVirtual,findStatic,findConstructor,findSpecial,findGetter/findSettervariants,unreflectandunreflect*variants, andfindVarHandle/findStaticVarHandle. - Fires when: a class performs a
MethodHandles.Lookuplookup; non-constant method handles require reflection metadata for the target members that is not visible to the existing REFLECT checks. For compile-time-constant handles, native-image may fold the lookup automatically. - Recommendation: register the target members under
reflectioninreachability-metadata.jsonso native-image retains the necessary member descriptors.
Security providers
GRAAL-SEC-001 - Runtime security-provider registration needs native-image review
- Severity: MEDIUM
- Inspects: calls to
Security.addProvider/Security.insertProviderAt. A class that merely extendsjava.security.Provideris intentionally ignored. - Fires when: code adds a provider at run time. Native Image captures the provider list and order at build time, and by default new security providers cannot be registered at run time; only provider instances already present in the image can be reordered.
- Recommendation: configure the provider statically in the build-time provider list (
java.security) and follow the provider's Native Image integration guide; re-inserting an instance obtained fromSecurity.getProviderto change the order is supported. With--future-defaults=run-time-initialize-security-providers(orall/run-time-initialize-jdk) the provider list is constructed at run time; validate that mode before relying on runtime registration.
JMX
GRAAL-JMX-001 - JMX MBeans and connectors need native-image monitoring and metadata review
- Severity: LOW
- Inspects:
MBeanServer.registerMBean;getAttribute,getAttributes,setAttribute, andinvokeon anMBeanServerConnection;JMX.newMBeanProxy/newMXBeanProxy;JMXConnectorServerFactory.newJMXConnectorServer;JMXConnectorFactory.connect/newJMXConnector;ManagementFactory.newPlatformMXBeanProxyand theMBeanServerConnectionoverloads ofgetPlatformMXBean(s). - Exclusion:
ManagementFactory.getPlatformMBeanServer()and the localgetPlatformMXBean(s)overloads. Native Image substitutes an in-process MBeanServer carrying the platform MXBeans, which works without--enable-monitoring. - Fires when: a class registers or reflectively reads MBeans, creates JMX proxies, or opens remote connectors. Standard MBean introspection and JMX proxies are reflective, platform-bean attributes are only readable through the MBeanServer when their interface methods are registered, connector servers need
jmxserver, and withoutjmxclientthe remote-clientManagementFactorymethods silently returnnullor an empty list instead of failing. - Recommendation: add
--enable-monitoring=jmxserverfor remote management andjmxclientfor outgoing connections (jvmstatfor discovery). Register each standard MBean interface for reflection and as a structured proxy type such as{"type":{"proxy":["com.example.FooMBean"]}}; notification-emitting proxies need the ordered interfaces["com.example.FooMBean","javax.management.NotificationEmitter"]. Register the interface methods of any platform MXBean whose attributes you read through the MBeanServer.
GRAAL-JMX-002 - Dynamic/model MBeans are not supported by native-image JMX
- Severity: HIGH
- Inspects: application classes assignable to
javax.management.DynamicMBean(this also covers Model MBeans, sinceModelMBeanextendsDynamicMBean), other than classes based on the JDK'sStandardMBeanwrapper (StandardMBeanitself implementsDynamicMBean, so a naive assignability check would otherwise misflag the JDK's own supported "standard MBean via subclassing" pattern). - Fires when: a class implements or extends a dynamic/model MBean type. GraalVM's native-image JMX support only covers MXBeans and standard (interface-naming-convention) MBeans; dynamic and model MBeans define their management interface at run time, which is unsupported because there is no metadata registration that makes a dynamic or model MBean work in a native image.
- Recommendation: replace the dynamic/model MBean with a standard MBean (a
FooMBeaninterface plus aFooimplementation, orjavax.management.StandardMBeancomposition/subclassing) or an MXBean; both work with--enable-monitoring=jmxserver.