skip to content

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

level: middleimportance: must knowfreq 40%

answer

  1. implements RuntimeHintsRegistrar, no-arg ctor
  2. reflection() / resources() / proxies() / serialization()
  3. MemberCategory picks what's kept
  4. @RegisterReflectionForBinding shortcut
  5. test via RuntimeHintsPredicates

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.

solid answer

~30 s

You 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 lines
java
import 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

for a junior

Know the shape: implement RuntimeHintsRegistrar, annotate a config with @ImportRuntimeHints.

for a middle

Fluently use reflection/resources/proxies registries and MemberCategory; know the no-arg-ctor rule and the @RegisterReflectionForBinding shortcut.

for a senior

Decide between a full registrar vs targeted annotations and test hints with predicates.

for a principal

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)

context