What is Spring's RuntimeHints, and what are the main categories of hints it exposes?
answer
- Closed-world analysis drops the unseen
- RuntimeHints = programmatic native config
- reflection/resources/proxies/serialization/jni
- Each sub-API → one GraalVM JSON file
- Missing hint fails at runtime, not build
basics
~10 sRuntimeHints 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 sWhen 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 linesimport 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
Know that native image removes unused code and RuntimeHints tells it what dynamic things to keep, grouped into a few categories.
Should name the sub-APIs (reflection/resources/proxies/serialization/jni) and the file each maps to, and know missing hints fail at runtime.
Explain the AOT build phase, who contributes hints, and how to verify them with RuntimeHintsPredicates.
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