skip to content

Reachability Metadata & RuntimeHints

How Spring declares what the native build cannot infer: the registrar API, the hint categories, the declarative annotations, the community metadata repository, and the reflective processor mechanism. Practical knowledge for anyone who has actually shipped a native image.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

explore

questions

page 1 of 2

What is Spring's RuntimeHints, and what are the main categories of hints it exposes?

level: juniorimportance: must knowfreq 55%

answer

  1. Closed-world analysis drops the unseen
  2. RuntimeHints = programmatic native config
  3. reflection/resources/proxies/serialization/jni
  4. Each sub-API → one GraalVM JSON file
  5. Missing hint fails at runtime, not build

basics

~10 s

RuntimeHints is a Spring object that records what a native image needs kept at runtime. It groups hints into sub-APIs: reflection, resources, JDK proxies, serialization, and JNI — each feeding a GraalVM config file.

solid answer

~30 s

When you compile a Spring app to a GraalVM native image, GraalVM does closed-world static analysis and drops anything it can't prove is used. Reflection, resource loading, dynamic proxies, and serialization defeat that analysis, so you must declare them. Spring's `RuntimeHints` class is the programmatic model for those declarations. It exposes sub-APIs via accessor methods: `reflection()` → `ReflectionHints`, `resources()` → `ResourceHints` (which also covers resource bundles), `proxies()` → `ProxyHints` (JDK dynamic proxies), `serialization()` → `SerializationHints`, and `jni()` → `ReflectionHints` for JNI. During AOT processing Spring translates these into GraalVM's native-image config JSON files (reflect-config.json, resource-config.json, proxy-config.json, serialization-config.json, jni-config.json).

code

java · 14 lines
java
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.MemberCategory;

public class MyHints implements RuntimeHintsRegistrar {
    @Override
    public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
        // Each sub-API targets one native-image config file
        hints.reflection().registerType(MyDto.class, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
        hints.resources().registerPattern("templates/*.html");
        hints.proxies().registerJdkProxy(MyCallback.class);
        hints.serialization().registerType(MyDto.class);
    }
}

go deeper

for a junior

Know that native image removes unused code and RuntimeHints tells it what dynamic things to keep, grouped into a few categories.

for a middle

Should name the sub-APIs (reflection/resources/proxies/serialization/jni) and the file each maps to, and know missing hints fail at runtime.

for a senior

Explain the AOT build phase, who contributes hints, and how to verify them with RuntimeHintsPredicates.

for a principal

Discuss closed-world tradeoffs, the reachability-metadata consolidation, and strategy for third-party libraries lacking reachability metadata.

## The problem GraalVM native image performs **closed-world static analysis** at build time: it walks all reachable code from `main`, and anything it cannot statically prove is reachable is removed from the final binary. This makes images small and fast to start, but it breaks any behavior that is decided *dynamically* at runtime rather than visible in the call graph: - **Reflection** — `Class.forName`, `clazz.getDeclaredMethod(...).invoke(...)` - **Resources** — `getResourceAsStream("/messages.properties")` (the file isn't code, so it's not bundled) - **JDK dynamic proxies** — `Proxy.newProxyInstance(...)` for a set of interfaces - **Serialization** — Java serialization needs reflective access to a type's fields - **JNI** — native code calling back into Java via reflection-like lookups GraalVM lets you *declare* these needs via JSON config files placed under `META-INF/native-image/<group>/<artifact>/`. ## Spring's abstraction: `RuntimeHints` Writing raw JSON by hand is tedious and error-prone, and Spring itself needs to contribute thousands of such entries for its own infrastructure. So Spring models the whole thing as an in-memory Java API: `org.springframework.aot.hint.RuntimeHints`. It is a container of typed sub-registries, each reached through an accessor: | Accessor | Returns | GraalVM file | |---|---|---| | `reflection()` | `ReflectionHints` | `reflect-config.json` | | `resources()` | `ResourceHints` (patterns **and** resource bundles) | `resource-config.json` | | `proxies()` | `ProxyHints` (JDK dynamic proxies) | `proxy-config.json` | | `serialization()` | `SerializationHints` | `serialization-config.json` | | `jni()` | `ReflectionHints` (separate instance, JNI-scoped) | `jni-config.json` | (Newer GraalVM consolidates these into a single `reachability-metadata.json`, but the conceptual categories are the same.) ## Who fills it in During Spring's **AOT processing** (which runs at build time — e.g. via the `process-aot` Gradle/Maven goal or the Spring Boot AOT plugin), Spring runs its `BeanFactoryInitializationAotProcessor`s and `BeanRegistrationAotProcessor`s, plus any `RuntimeHintsRegistrar` you register. All of them write into one shared `RuntimeHints`. Finally a `NativeConfigurationWriter` serializes it to the JSON files, which GraalVM's `native-image` tool then consumes. ## When you write hints yourself Spring and its starters cover most of the framework automatically. You add hints when **your own code** does something dynamic that Spring can't see: reflectively loading a class by name, reading a bundled `.properties`/template, proxying your own interface, serializing a DTO, etc. You express these through the matching sub-API rather than editing JSON. ## Gotcha Missing a hint does **not** fail the build — it fails at *runtime* in the native image with errors like `ClassNotFoundException`, `MissingResourceException`, or a proxy/serialization failure. That's why the hints model and testing them (`RuntimeHintsPredicates`) matter.

  • If you forget a required hint, when and how do you find out?
    Not at build time. The native image starts fine but fails at runtime when the dynamic path executes — e.g. ClassNotFoundException, NoSuchMethodException, MissingResourceException, or a serialization/proxy error. You catch these with integration tests, GraalVM's tracing agent, or Spring's RuntimeHintsPredicates in unit tests.
  • Does Spring make you write hints for @Autowired beans or @Configuration classes?
    No. Spring's own AOT processors generate those automatically during process-aot. You only write hints for dynamic behavior in your own code that the framework can't infer — reflective class loading by name, custom resource reads, hand-rolled proxies, etc.

saying these in an interview costs you the question

  • Saying hints are needed for every class in the app
  • Claiming a missing hint fails the native build (it fails at runtime)
  • Confusing RuntimeHints with runtime configuration/properties

context

open as a page

What does the @ImportRuntimeHints annotation do in a Spring application?

level: juniorimportance: must knowfreq 45%

basics

~20 s

@ImportRuntimeHints is placed on a @Configuration class or bean to register one or more RuntimeHintsRegistrar classes. During AOT/native-image build, Spring calls those registrars so they can declare reflection, resource, and proxy hints the GraalVM compiler needs.

open as a page

What is the GraalVM reachability-metadata repository and why does a native Spring Boot build need it?

level: juniorimportance: must knowfreq 60%

basics

~20 s

It is a community-maintained collection of GraalVM native-image config (reflection, resources, proxies) for popular third-party libraries. The build tool pulls the right config so those libraries work in a native image without you writing hints by hand.

open as a page

What is a RuntimeHintsRegistrar in Spring, and why do you need one when building a GraalVM native image?

level: juniorimportance: must knowfreq 55%

basics

~20 s

It is an interface where you tell the native-image build which classes, resources, or proxies your app uses via reflection. GraalVM closes the world at build time, so anything discovered dynamically must be declared, or it fails at runtime.

open as a page

How do you register reflection hints with ReflectionHints, and what does MemberCategory control?

level: middleimportance: must knowfreq 60%

basics

~10 s

Use hints.reflection().registerType(MyType.class, category...). MemberCategory selects which members become reflectively accessible — e.g. declared constructors, public/declared methods, or fields. It maps to reflect-config.json.

open as a page

How do you implement and register a RuntimeHintsRegistrar with @ImportRuntimeHints, and what kinds of hints can it declare?

level: middleimportance: must knowfreq 40%

basics

~10 s

Write a class implementing RuntimeHintsRegistrar with a no-arg constructor, override registerHints(RuntimeHints, ClassLoader), and call hints.reflection(), hints.resources(), hints.proxies(), or hints.serialization(). Then put @ImportRuntimeHints(YourRegistrar.class) on a @Configuration class.

open as a page

How does the reachability-metadata repository relate to Spring's RuntimeHints mechanism, and when do you still need to write your own hints?

level: seniorimportance: must knowfreq 40%

basics

~10 s

The repository supplies metadata for third-party libraries; Spring's RuntimeHints (RuntimeHintsRegistrar, @ImportRuntimeHints, @RegisterReflectionForBinding) supplies hints for your own and framework code. You still hand-write hints for application-specific reflection the repository can't know about.

open as a page

How does @RegisterReflectionForBinding build on the @Reflective mechanism, and when would you use it?

level: seniorimportance: must knowfreq 30%

basics

~10 s

@RegisterReflectionForBinding is meta-annotated with @Reflective(RegisterReflectionForBindingProcessor.class). For the classes you list, its processor uses BindingReflectionHintsRegistrar to register the reflection hints needed to serialize/deserialize them (e.g. with Jackson) in a native image.

open as a page

What is the @Reflective meta-annotation and what problem does it solve for GraalVM native images?

level: juniorimportance: should knowfreq 25%

basics

~20 s

GraalVM native image drops reflection metadata it can't see at build time. @Reflective is a Spring meta-annotation you put on another annotation so that, during AOT, Spring automatically registers reflection hints for every element marked with it.

open as a page

Why do you need @RegisterReflectionForBinding when compiling a Spring app to a GraalVM native image?

level: juniorimportance: should knowfreq 30%

basics

~20 s

GraalVM native images strip out code that isn't reachable at build time, and reflection is invisible to that analysis. @RegisterReflectionForBinding tells the build to keep reflection metadata for DTOs so libraries like Jackson can still serialize/deserialize them at runtime.

open as a page

How do ResourceHints handle static resources versus resource bundles in a native image?

level: middleimportance: should knowfreq 45%

basics

~10 s

hints.resources().registerPattern("...") bundles matching files (templates, properties) into the image. hints.resources().registerResourceBundle("messages") includes an i18n ResourceBundle base name. Both go into resource-config.json.

open as a page

Why does GraalVM native image need runtime hints at all, and what category of failures does @ImportRuntimeHints prevent?

level: middleimportance: should knowfreq 28%

basics

~20 s

GraalVM native image uses closed-world static analysis and strips metadata for dynamic features like reflection, proxies, resources, and serialization. Hints tell the compiler to keep them, preventing native-runtime errors such as ClassNotFoundException, NoSuchMethodException, or missing-resource failures that never occur on the JVM.

open as a page

How do you enable and configure the metadata repository in GraalVM Native Build Tools (Gradle and Maven)?

level: middleimportance: should knowfreq 45%

basics

~10 s

In the native-build-tools plugin you turn on the metadataRepository feature. In Gradle set graalvmNative { metadataRepository { enabled = true } }; in Maven set <metadataRepository><enabled>true</enabled></metadataRepository>. Spring Boot turns it on by default.

open as a page

What is the ReflectiveProcessor interface, and what does the default SimpleReflectiveProcessor register?

level: middleimportance: should knowfreq 15%

basics

~10 s

ReflectiveProcessor is an interface with one method, registerReflectionHints(ReflectionHints, AnnotatedElement), that decides which hints to emit. SimpleReflectiveProcessor is the default: it registers the type, or the specific constructor/method for reflective invocation.

open as a page

How do you use @RegisterReflectionForBinding, and what do its 'classes' and 'classNames' attributes do?

level: middleimportance: should knowfreq 28%

basics

~20 s

Put @RegisterReflectionForBinding on a bean or config class and list the DTO types to keep for serialization. Use classes for types available at build time; use classNames (string names) for types you can't reference directly, e.g. package-private or not on the config's classpath.

open as a page

How do you register a RuntimeHintsRegistrar so Spring picks it up during AOT processing, and what are the trade-offs between the approaches?

level: middleimportance: should knowfreq 40%

basics

~10 s

Annotate a configuration class with @ImportRuntimeHints(MyRegistrar.class). For libraries that must contribute hints without a config class, list the registrar under the RuntimeHintsRegistrar key in META-INF/spring/aot.factories.

open as a page

Beyond reflection, what do the resources, proxies, and serialization sub-registrars of RuntimeHints do, and when would you use each?

level: middleimportance: should knowfreq 30%

basics

~10 s

hints.resources() keeps classpath files/bundles you load by name; hints.proxies() declares JDK dynamic proxies for interfaces; hints.serialization() keeps types you (de)serialize with Java serialization. Use each when native analysis can't see that dynamic usage.

open as a page

When and how do you register JDK dynamic proxy hints with ProxyHints?

level: seniorimportance: should knowfreq 42%

basics

~10 s

Use hints.proxies().registerJdkProxy(A.class, B.class...), passing the exact ordered set of interfaces the proxy implements. Needed when code creates a JDK Proxy at runtime. It maps to proxy-config.json.

open as a page

What do SerializationHints and JNI hints do, and how do they differ from ordinary reflection hints?

level: seniorimportance: should knowfreq 30%

basics

~10 s

hints.serialization().registerType(T.class) enables Java serialization of a type in native (→ serialization-config.json). hints.jni() returns a ReflectionHints whose entries go to jni-config.json, for native code calling back into Java. Both are separate from ordinary reflect-config.json reflection.

open as a page

When should you register a RuntimeHintsRegistrar via @ImportRuntimeHints versus via spring/aot.factories?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Use @ImportRuntimeHints when the hints belong to a specific @Configuration/bean and should only apply when that component is part of the context. Use spring/aot.factories when you're a library/starter author and the registrar should run globally and unconditionally during any app's AOT processing.

open as a page

How is repository metadata matched to a dependency, and what happens when your library version has no matching metadata?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Metadata is looked up by the dependency's group:artifact:version coordinates via the repository's index. If your exact version isn't covered, no metadata is contributed for that jar and you may hit reflection/resource failures unless you add your own hints.

open as a page

What is the difference between @RegisterReflection and @RegisterReflectionForBinding?

level: seniorimportance: should knowfreq 25%

basics

~20 s

@RegisterReflection is the general form: you pick exactly which member categories (constructors, fields, methods) to expose on the listed classes. @RegisterReflectionForBinding is a preset specialization that registers everything needed for serialization/deserialization and follows the type's property graph.

open as a page

When does Spring auto-detect binding types for native images, and when must you register them explicitly? What are the alternatives to @RegisterReflectionForBinding?

level: seniorimportance: should knowfreq 22%

basics

~20 s

Spring's AOT engine auto-detects DTOs it can see statically, like controller @RequestBody/@ResponseBody types. You register manually when Spring can't infer the type — e.g. bodies passed to RestClient/WebClient at runtime or resolved via generics. Alternatives: a RuntimeHintsRegistrar with @ImportRuntimeHints, or hand-written reflect-config.json.

open as a page

Inside registerHints, how do you declare reflection metadata for a type, and what do MemberCategory values control?

level: seniorimportance: should knowfreq 38%

basics

~10 s

Call hints.reflection().registerType(MyClass.class, category...). Each MemberCategory enum value opts a specific kind of member into native metadata — e.g. INVOKE_PUBLIC_METHODS keeps public methods callable via reflection, INVOKE_DECLARED_CONSTRUCTORS keeps all constructors instantiable.

open as a page

How are RuntimeHints contributed to a Spring AOT build, and how do the sub-APIs map to GraalVM config files?

level: principalimportance: should knowfreq 38%

basics

~10 s

Implement RuntimeHintsRegistrar and register it via @ImportRuntimeHints or META-INF/spring/aot.factories. During AOT processing Spring collects all hints and a config writer emits reflect-/resource-/proxy-/serialization-/jni-config.json for GraalVM.

open as a page

How do you verify a RuntimeHintsRegistrar produces the right hints, and how do @Conditional configs interact with @ImportRuntimeHints at AOT time?

level: principalimportance: should knowfreq 18%

basics

~20 s

Unit-test the registrar by calling registerHints on a fresh RuntimeHints and asserting with RuntimeHintsPredicates (e.g. reflection().onType(...)). Remember AOT evaluates @Conditional/@Profile at build time, so a config excluded during the AOT build contributes no hints — align build-time conditions with intended native behavior.

open as a page

How would you create a custom annotation that contributes your own reflection hints via a ReflectiveProcessor?

level: seniorimportance: nice to knowfreq 10%

basics

~10 s

Write a ReflectiveProcessor implementing registerReflectionHints, then create your own annotation meta-annotated with @Reflective(YourProcessor.class). Put your annotation on beans/members; AOT scanning invokes the processor and emits the hints.

open as a page

As a principal engineer, how would you govern reliance on the community metadata repository for production native images — reproducibility, coverage gaps, and supply-chain concerns?

level: principalimportance: nice to knowfreq 18%

basics

~20 s

Pin the repository version for reproducible builds, run native integration tests in CI so coverage gaps fail early, keep a curated local override for missing/wrong metadata, and treat the repository as a vetted, versioned input rather than an implicit auto-fetch.

open as a page

When and over what scope does @Reflective processing run, and what are the consequences of that design?

level: principalimportance: nice to knowfreq 8%

basics

~20 s

It runs at AOT build time, not at runtime. A BeanFactoryInitializationAotProcessor scans registered bean classes and their members for @Reflective-meta-annotated annotations. So only beans (and their reachable members) are covered; arbitrary classpath types are not.

open as a page

Explain how @RegisterReflectionForBinding is processed at AOT time — the @Reflective meta-annotation, ReflectiveProcessor, and BindingReflectionHintsRegistrar. What edge cases can still trip you up?

level: principalimportance: nice to knowfreq 14%

basics

~20 s

@RegisterReflectionForBinding is meta-annotated with @Reflective, which ties it to a ReflectiveProcessor that runs during Spring AOT. The processor uses BindingReflectionHintsRegistrar to register the serialization reflection for each listed type and recursively walk its property graph, emitting GraalVM reachability metadata.

open as a page

showing 1–30 of 31