How do you register a RuntimeHintsRegistrar so Spring picks it up during AOT processing, and what are the trade-offs between the approaches?
answer
- @ImportRuntimeHints on config = app path
- aot.factories key = library path
- AotServices loads aot.factories
- colocated+conditional vs unconditional+decoupled
- no-arg constructor required
basics
~10 sAnnotate 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.
solid answer
~40 sTwo mechanisms. For application code, put @ImportRuntimeHints(MyRegistrar.class) on a @Configuration class, a bean method's class, or the @SpringBootApplication class — Spring instantiates the registrar during AOT processing and calls registerHints. For library/starter code that ships hints independent of any user configuration, register the fully-qualified class name under the key org.springframework.aot.hint.RuntimeHintsRegistrar in META-INF/spring/aot.factories; Spring loads it via AotServices regardless of which beans the user defines. @ImportRuntimeHints is discoverable and colocated with the config that needs it, so it's preferred in apps. The aot.factories route is unconditional and framework-friendly but easy to forget and not tied to a bean, so it's the right tool for reusable libraries. Both run only at build time.
code
java · 16 lines// Library path: META-INF/spring/aot.factories
// org.springframework.aot.hint.RuntimeHintsRegistrar=com.acme.AcmeHints
package com.acme;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
public class AcmeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.resources().registerPattern("acme/*.properties");
}
}
// Runs for every downstream app that has the jar on the classpath,
// with no @Import required.go deeper
Should at least know @ImportRuntimeHints exists.
Should describe both registration paths and pick @ImportRuntimeHints for app code.
Should articulate the conditional-vs-unconditional trade-off and when a library must use aot.factories.
Should reason about API surface: exposing hints via aot.factories vs annotations, and avoiding over-contribution.
There are two supported ways to make Spring invoke your `RuntimeHintsRegistrar`, and choosing correctly matters. **1. `@ImportRuntimeHints` (application-level).** ```java @Configuration @ImportRuntimeHints(MyRuntimeHints.class) class MyConfig { } ``` You can place it on any `@Configuration` class, on a class that declares `@Bean` methods, or directly on the `@SpringBootApplication` class. During AOT processing Spring detects the annotation, instantiates the referenced registrar(s) (they need a no-arg constructor), and calls `registerHints`. The value is an array, so you can list several registrars. This is the idiomatic choice for your own app because the hint declaration lives right next to the code that needs it — discoverable and refactor-friendly. **2. `META-INF/spring/aot.factories` (library-level).** Spring loads AOT contributions through `AotServices`, which reads `META-INF/spring/aot.factories` (note: *aot*.factories, the AOT analogue of the classic `spring.factories`). Add: ``` org.springframework.aot.hint.RuntimeHintsRegistrar=com.acme.lib.AcmeRuntimeHints ``` Spring instantiates and runs it during AOT processing **unconditionally** — it doesn't depend on any bean or `@Import` being present. This is exactly what a reusable starter/library needs: it must contribute native metadata for anyone who puts it on the classpath, even if they never reference an Acme configuration class. **Trade-offs.** - `@ImportRuntimeHints` is *conditional and colocated*: hints only get contributed if that config/bean is part of the context. Good for app code; risky for a library because the user might not import your config. - `aot.factories` is *unconditional and decoupled*: always contributes, ideal for libraries, but it's a separate file that's easy to forget and not tied to any specific bean, so it can over-contribute if you're not careful. **Related annotations (not the same thing).** - `@RegisterReflectionForBinding(SomeDto.class)` — a convenience that registers reflection hints for serialization/deserialization binding without writing a registrar. - `@Reflective` on a method/element plus `ReflectiveRuntimeHintsRegistrar` — Spring's mechanism for annotation-driven reflection hints. These are shortcuts; `RuntimeHintsRegistrar` is the general, programmatic escape hatch. **Gotchas.** - The registrar class must be instantiable (no-arg constructor). A `record` or a simple static nested class works. - Both paths execute at build time only, once per AOT processing run. - Forgetting the `aot.factories` entry is the classic reason a library's native support 'silently' doesn't work in downstream apps.
- Your Spring Boot starter contributes hints, but downstream native apps still get ClassNotFoundException. What's the likely cause?You used @ImportRuntimeHints on a config the user never imports, so it's conditional. A starter should register the RuntimeHintsRegistrar in META-INF/spring/aot.factories so it contributes unconditionally.
- How is aot.factories different from the classic spring.factories?aot.factories is the AOT-processing analogue loaded via AotServices and only consulted during build-time AOT; spring.factories is the runtime factories mechanism. Keeping them separate avoids running build-time-only contributors at runtime.
saying these in an interview costs you the question
- Claiming spring.factories (not aot.factories) is where registrars go
- Saying @ImportRuntimeHints works fine for a library used by third parties
- Thinking the registrar needs to be a Spring bean