skip to content

RuntimeHintsRegistrar

A RuntimeHintsRegistrar is the one place you declare the reflection, resource, proxy and serialization metadata your code needs. Interviewers ask how you fix a native failure, and writing or importing a registrar is the answer.

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

explore

questions

5

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

level: juniorimportance: must knowfreq 55%

answer

  1. Closed-world build strips unseen code
  2. registerHints(RuntimeHints, ClassLoader)
  3. reflection/resources/proxies/serialization
  4. @ImportRuntimeHints wires it
  5. runs at AOT processing, fails at runtime

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.

solid answer

~40 s

GraalVM native image does closed-world analysis: it only keeps code it can prove is reachable at build time. Reflection, resource loading, JDK proxies and serialization are dynamic, so the compiler can't see them and strips them — causing ClassNotFoundException, missing-method or missing-resource errors at runtime. A RuntimeHintsRegistrar is Spring's programmatic hook to declare that metadata. You implement registerHints(RuntimeHints, ClassLoader) and call the sub-registrars (reflection, resources, proxies, serialization) to record what the build must retain. Spring runs it during AOT processing (processAot), translating your hints into GraalVM's reachability metadata. You wire it up with @ImportRuntimeHints on a @Configuration class. It's how you cover reflective access that Spring's automatic AOT contributions don't already detect.

code

java · 22 lines
java
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportRuntimeHints;

@Configuration
@ImportRuntimeHints(AppHints.MyHints.class)
public class AppHints {

    static class MyHints implements RuntimeHintsRegistrar {
        @Override
        public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
            // I load this type reflectively at runtime, so declare it.
            hints.reflection().registerType(com.example.Widget.class,
                    MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
                    MemberCategory.INVOKE_PUBLIC_METHODS);
            // A config file I read by name from the classpath.
            hints.resources().registerPattern("config/widgets.json");
        }
    }
}

go deeper

for a junior

Should grasp that native image removes code it can't see, and hints re-declare reflective/resource usage.

for a middle

Should name the four hint categories and how the registrar is wired with @ImportRuntimeHints.

for a senior

Should explain closed-world analysis, AOT-processing timing, and why Spring can't auto-detect app-level reflection.

for a principal

Should discuss the aot.factories contribution path for libraries and testing hints with RuntimeHintsPredicates.

**The problem.** GraalVM native image performs *closed-world* (ahead-of-time) analysis. At build time it walks all reachable code starting from `main`, compiles only that into a standalone binary, and discards everything else. There is no JIT and no dynamic class loading at runtime. Anything the static analyzer cannot *see* — code reached via reflection (`Class.forName`, `Method.invoke`), classpath resources loaded by name, dynamically generated JDK proxies, or serialization — is invisible and gets removed. At runtime you then get `ClassNotFoundException`, `NoSuchMethodException`, missing resources, or proxy creation failures. **The fix — hints.** GraalVM accepts *reachability metadata* (historically JSON files like `reflect-config.json`, `resource-config.json`, `proxy-config.json`, `serialization-config.json`) that tells it to keep those elements. Spring wraps this in a typed Java API so you don't hand-write JSON. **`RuntimeHintsRegistrar`.** A functional interface in `org.springframework.aot.hint` with a single method: ```java void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader); ``` You implement it and, inside, call sub-registrars on the `RuntimeHints` object: - `hints.reflection()` — `ReflectionHints` for reflective access to types/methods/fields/constructors. - `hints.resources()` — `ResourceHints` for classpath resources and resource bundles. - `hints.proxies()` — `ProxyHints` for JDK dynamic proxies (interface-based). - `hints.serialization()` — `SerializationHints` for Java serialization. - (also `hints.jni()` for JNI access.) **When it runs.** Not at runtime. Spring executes registrars during **AOT processing** — the `processAot` step of the build (Gradle `nativeCompile`/`bootBuildImage` runs it, or `./gradlew processAot`). The hints are converted into GraalVM config that the native compiler consumes. **How Spring finds your registrar.** Two main ways: 1. `@ImportRuntimeHints(MyHints.class)` on a `@Configuration` class, bean, or `@SpringBootApplication`. 2. Registering the fully-qualified class name under the `org.springframework.aot.hint.RuntimeHintsRegistrar` key in `META-INF/spring/aot.factories` (loaded via `AotServices`) — used by libraries/starters that must contribute hints even without a config class. **Why not automatic?** Spring's AOT engine already generates hints for a lot: bean definitions, `@ConfigurationProperties`, `@Controller` argument binding, etc. But it cannot infer reflection you do *inside your own code* (e.g. you reflectively load a class name from a config file). That gap is exactly what a `RuntimeHintsRegistrar` fills. **Gotchas.** - Missing hints don't fail the *build* — they fail at *runtime* in the native binary, often far from the cause. Test with the native image or the `RuntimeHintsPredicates` test utility. - On the JVM everything still works (reflection is fully available), so a missing hint is invisible until you actually run natively. - The registrar runs at build time, so keep it cheap and side-effect free.

  • Does a missing hint cause the native build to fail?
    No. The build succeeds; the failure surfaces at runtime in the native binary (e.g. ClassNotFoundException / NoSuchMethodException), and it works fine on the JVM, which is why native-specific testing is needed.
  • At what point does registerHints actually execute?
    During AOT processing at build time (the processAot step), not at application runtime. Its output is translated into GraalVM reachability metadata for the compiler.

saying these in an interview costs you the question

  • Thinking hints are needed on the plain JVM (they're only for native image)
  • Believing a missing hint breaks the build rather than runtime
  • Saying registerHints runs at application startup

context

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

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

What is the ClassLoader parameter of registerHints for, and how do you decide between a RuntimeHintsRegistrar, @RegisterReflectionForBinding, and a BeanRegistrationAotProcessor?

level: principalimportance: nice to knowfreq 20%

basics

~20 s

The ClassLoader lets you register hints conditionally — e.g. only if an optional dependency is on the classpath (ClassUtils.isPresent). Choose @RegisterReflectionForBinding for simple binding types, RuntimeHintsRegistrar for general programmatic hints, and a BeanRegistrationAotProcessor when hints must be derived per bean during AOT.

open as a page