How do you implement and register a RuntimeHintsRegistrar with @ImportRuntimeHints, and what kinds of hints can it declare?
answer
- implements RuntimeHintsRegistrar, no-arg ctor
- reflection() / resources() / proxies() / serialization()
- MemberCategory picks what's kept
- @RegisterReflectionForBinding shortcut
- test via RuntimeHintsPredicates
basics
~10 sWrite 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.
solid answer
~30 sYou create a class implementing org.springframework.aot.hint.RuntimeHintsRegistrar. It needs a public no-arg constructor because Spring instantiates it reflectively during AOT — it is not a managed bean. In registerHints(RuntimeHints hints, ClassLoader cl) you declare what dynamic behavior to preserve: hints.reflection().registerType(Foo.class, MemberCategory...) for reflective construction/method/field access; hints.resources().registerPattern("messages/*.properties") for classpath resources; hints.proxies().registerJdkProxy(SomeInterface.class) for JDK dynamic proxies; hints.serialization().registerType(...) for Java serialization. You then attach it with @ImportRuntimeHints(MyRegistrar.class) on a @Configuration class or bean. During AOT processing Spring calls the registrar, merges its hints with others, and writes GraalVM reachability metadata so native-image keeps the right classes, members, and resources.
code
java · 24 linesimport org.springframework.aot.hint.*;
import org.springframework.context.annotation.*;
@Configuration
@ImportRuntimeHints(AppConfig.Hints.class)
class AppConfig {
static class Hints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader cl) {
// reflection on a type bound by a library
hints.reflection().registerType(OrderDto.class,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_PUBLIC_METHODS,
MemberCategory.DECLARED_FIELDS);
// classpath resources
hints.resources().registerPattern("config/rules/*.json");
// an i18n bundle
hints.resources().registerResourceBundle("messages");
// a JDK proxy
hints.proxies().registerJdkProxy(PaymentGateway.class);
}
}
}go deeper
Know the shape: implement RuntimeHintsRegistrar, annotate a config with @ImportRuntimeHints.
Fluently use reflection/resources/proxies registries and MemberCategory; know the no-arg-ctor rule and the @RegisterReflectionForBinding shortcut.
Decide between a full registrar vs targeted annotations and test hints with predicates.
Standardize hint conventions across teams and libraries, and choose registration channels deliberately.
## Step 1 — implement RuntimeHintsRegistrar ```java public class MyRuntimeHints implements RuntimeHintsRegistrar { @Override public void registerHints(RuntimeHints hints, ClassLoader classLoader) { ... } } ``` **Constraints:** - Must have an accessible **no-arg constructor** — Spring's AOT engine instantiates it via reflection, *not* through the bean container. You cannot `@Autowired` anything into it. - It is stateless build-time code. Keep it deterministic; don't read live runtime state. ## Step 2 — declare hints via the RuntimeHints sub-registries `RuntimeHints` groups capabilities: - **Reflection** — `hints.reflection().registerType(Class, MemberCategory...)` or `.registerType(TypeReference.of("com.x.Y"), ...)`. `MemberCategory` values control what is kept: `INVOKE_DECLARED_CONSTRUCTORS`, `INVOKE_PUBLIC_METHODS`, `DECLARED_FIELDS`, `PUBLIC_CLASSES`, etc. You can also register specific `Method`/`Field` members and set field/constructor invocation. - **Resources** — `hints.resources().registerPattern("templates/*.html")` bundles matching classpath resources; `.registerResourceBundle("messages")` for i18n bundles. - **Proxies** — `hints.proxies().registerJdkProxy(MyInterface.class, Marker.class)` for JDK dynamic proxies (order of interfaces matters, mirrors `Proxy.getProxyClass`). - **Serialization** — `hints.serialization().registerType(TypeReference.of(MySerializable.class))` for Java (de)serialization. There are helper predicates like `TypeReference.of(String)` so you can reference types that may not be on the compile classpath. ## Step 3 — wire it with @ImportRuntimeHints ```java @Configuration @ImportRuntimeHints({MyRuntimeHints.class, ExtraHints.class}) class AppConfig { } ``` Multiple registrars are supported by passing an array. The annotation can sit on any configuration/component class. ## Convenience alternatives to hand-writing reflection hints For the very common case of "this DTO is reflectively bound by Jackson / serialization," Spring offers: - **`@RegisterReflectionForBinding(Foo.class)`** — registers the type and its members for serialization/deserialization binding. - **`@RegisterReflection(...)`** — general-purpose reflection registration on a class/method. - **`@Reflective`** — marks an element so a `ReflectiveProcessor` contributes hints. Use these when they fit; drop down to a full `RuntimeHintsRegistrar` when you need resources, proxies, patterns, or conditional/computed logic. ## Common mistakes - Forgetting the no-arg constructor → AOT fails to instantiate the registrar. - Registering the wrong `MemberCategory` (e.g. only fields when you actually call methods reflectively) → native runtime still throws. - Trying to inject beans into the registrar — impossible; pass what you need as constants or compute from the ClassLoader. - Registering hints for types that GraalVM already covers via a starter — harmless but redundant. - Expecting the annotation to matter on a plain JVM — it only feeds native/AOT. ## Testing Use `RuntimeHintsPredicates` + a `RuntimeHints` instance in a unit test: ```java RuntimeHints hints = new RuntimeHints(); new MyRuntimeHints().registerHints(hints, getClass().getClassLoader()); assertThat(RuntimeHintsPredicates.reflection() .onType(Foo.class)).accepts(hints); ```
- Why can't the RuntimeHintsRegistrar be a Spring bean with injected dependencies?Because Spring's AOT engine instantiates it directly via a no-arg constructor during build-time processing, outside the bean container. It runs before/independently of context refresh, so there is nothing to inject — it must be self-contained.
- You added reflection hints but native runtime still throws NoSuchMethodException. What's likely wrong?The MemberCategory scope is too narrow — e.g. you registered fields or constructors but the code invokes methods reflectively. Add INVOKE_PUBLIC_METHODS or INVOKE_DECLARED_METHODS (or register the specific Method), and verify the exact type/member is what's accessed.
saying these in an interview costs you the question
- Thinking the registrar is a Spring bean that supports dependency injection
- Believing registerType keeps all members automatically without specifying MemberCategory
- Assuming @ImportRuntimeHints itself declares hints (it only references registrars)