What kinds of failures does running tests as a native image catch that JVM tests miss?
answer
- closed-world = everything reachable at build time
- reflection / proxies / resources / serialization need hints
- JVM tolerates, native binary throws
- validates AOT test contexts + RuntimeHints coverage
- failure = missing metadata, not logic bug
basics
~20 sNative tests catch missing reachability metadata: unregistered reflection, JDK proxies, resources, or serialization that the JVM allows at runtime but the closed-world native image forbids. Those show up as native test failures instead of production crashes.
solid answer
~40 sGraalVM native images run under the closed-world assumption — all reflection, dynamic proxies, resource loading, and serialization must be declared at build time via reachability metadata. On the JVM these happen dynamically and always work, so a JVM-green suite can hide gaps. `nativeTest` compiles and runs the suite as a native binary, so any code path that reflectively loads a class, creates a proxy, reads a classpath resource, or deserializes without a registered hint fails the test rather than the production binary. It also validates that Spring's AOT-processed test contexts start correctly and that your `RuntimeHintsRegistrar` / `@ImportRuntimeHints` declarations actually cover the paths your tests exercise. It does not catch new logical bugs — a failure almost always means missing metadata, and the fix is registering a hint, not changing behavior.
go deeper
Know that native images need reflection/resources pre-registered and native tests catch what's missing.
Name the closed-world assumption and the categories (reflection, proxies, resources, serialization).
Connect failures to RuntimeHints/RuntimeHintsRegistrar fixes and AOT test-context validation.
Position native tests as the cheap early gate for metadata gaps before a native release, and scope out Mockito noise.
## The closed-world assumption A GraalVM **native image** is built by static analysis: the builder starts from your entry points and computes everything reachable, then compiles only that into the executable. Anything the analysis cannot see statically is *absent at runtime*. This is the **closed-world assumption**. Dynamic JVM features therefore need explicit **reachability metadata** (also called *hints*) so the builder includes them: - **Reflection** — `Class.forName(...)`, reflective field/method/constructor access. - **JDK dynamic proxies** — `Proxy.newProxyInstance(...)`, common in Spring for `@Configuration` proxying and interface-based proxies. - **Resources** — `getResourceAsStream(...)`, classpath resource bundles, i18n messages. - **Serialization** — Java serialization needs classes registered. On the JVM these all resolve dynamically at runtime, so a JVM test suite never notices missing metadata. In a native image, an unregistered reflective call throws (e.g., a `ClassNotFoundException`, `MissingReflectionRegistrationError`, or a null/absent resource). ## What nativeTest surfaces Because `nativeTest` runs your tests *inside a native binary*, it exercises real code paths under those constraints. It catches: 1. **Missing reflection/resource/proxy hints** for code your tests actually hit — libraries or your own code that reflect without a registered hint. 2. **Broken AOT-processed test contexts** — Spring ahead-of-time generates an `ApplicationContextInitializer` per distinct test context (via `processTestAot`); nativeTest confirms those contexts refresh correctly natively. 3. **Gaps in your own hints** — if you wrote a `RuntimeHintsRegistrar` (registered with `@ImportRuntimeHints` or `RuntimeHintsRegistrar` on the classpath), nativeTest verifies the paths your tests cover are actually registered. ## What it does NOT catch - It is **not** a way to find ordinary logic bugs — those already fail on the JVM. A native-only failure typically means *metadata is missing*, not that behavior differs. - It won't magically make **Mockito** or other bytecode-generating mocking libraries work; those generally are unsupported natively, so such tests may fail for reasons unrelated to your production code. ## Typical fix workflow When a native test fails with a reflection/resource error: 1. Identify the type/resource from the error. 2. Add a hint — a `RuntimeHintsRegistrar` implementation registered via `@ImportRuntimeHints`, or rely on Spring/library-provided hints, or supply GraalVM reachability metadata (`reflect-config.json`, etc.). 3. Re-run `nativeTest`. ## When to lean on it Run native tests before shipping a native binary, especially after adding libraries that use reflection (JSON mappers, ORMs, dynamic clients). It is the cheapest place to discover a missing hint — cheaper than a production native crash.
- A native test fails with a reflection error on a JSON DTO. What's the fix?Register a reflection hint for that type — e.g., a `RuntimeHintsRegistrar` added via `@ImportRuntimeHints` (or `@RegisterReflectionForBinding` for binding types), or rely on the JSON library's Spring-provided hints. The fix is metadata, not a behavior change.
- Can native tests catch a race condition in your business logic?Not specifically — those already reproduce on the JVM. nativeTest targets native-only failure modes: missing reachability metadata and AOT context issues, not general concurrency bugs.
saying these in an interview costs you the question
- Claiming native tests find logic bugs the JVM suite misses
- Thinking reflection/resources 'just work' in native images without hints
- Assuming a native test failure means the application code is wrong (usually it's metadata)