When should you register a RuntimeHintsRegistrar via @ImportRuntimeHints versus via spring/aot.factories?
answer
- annotation = bean-scoped, conditional
- aot.factories = global, unconditional
- library authors → aot.factories
- app feature hints → @ImportRuntimeHints
- conditions evaluated at build time
basics
~20 sUse @ImportRuntimeHints when the hints belong to a specific @Configuration/bean and should only apply when that component is part of the context. Use spring/aot.factories when you're a library/starter author and the registrar should run globally and unconditionally during any app's AOT processing.
solid answer
~40 sBoth channels register the same RuntimeHintsRegistrar; the difference is scope and coupling. @ImportRuntimeHints ties the registrar to a concrete @Configuration class or bean — it only contributes hints if that component is present in the AOT-processed context, and its intent is co-located with the feature that needs the hints. That's ideal for application code and feature-scoped configs. Registering under the RuntimeHintsRegistrar key in a spring/aot.factories file makes the registrar apply globally and unconditionally to any application that has your JAR on the classpath during AOT — no bean needs to reference it. That's the right tool for library and Spring Boot starter authors who must contribute hints for consumers who never see the internal types. Rule of thumb: application-owned, feature-specific hints → @ImportRuntimeHints; library-wide hints that must always fire → spring/aot.factories.
code
text · 9 lines// Library author: META-INF/spring/aot.factories
// (fires for every consumer app during AOT, no bean needed)
org.springframework.aot.hint.RuntimeHintsRegistrar=\
com.acme.lib.AcmeRuntimeHints
// Application author: co-locate with the feature config
// @Configuration
// @ImportRuntimeHints(MyFeatureHints.class)
// class MyFeatureConfig { ... }go deeper
Just know both exist and register the same registrar type.
Explain that the annotation is bean-scoped while aot.factories is global.
Choose the right channel by ownership/conditionality and know build-time condition evaluation implications.
Set org-wide conventions for where library vs app hints live and audit for conditional-hint pitfalls.
## Two registration channels, one interface A `RuntimeHintsRegistrar` can reach Spring's AOT engine through either: 1. **`@ImportRuntimeHints(MyRegistrar.class)`** on a `@Configuration`/component. 2. A line in **`META-INF/spring/aot.factories`** under the key `org.springframework.aot.hint.RuntimeHintsRegistrar=com.example.MyRegistrar`. (Note: it's `aot.factories`, distinct from the classic `spring.factories`.) ## The decisive differences ### Scope / conditionality - **@ImportRuntimeHints** is **bean-scoped**. The registrar runs only if the annotated component is part of the context that AOT processes. If that `@Configuration` is excluded (e.g. `@ConditionalOnProperty`, `@Profile` not active at build time), its hints are not contributed. This is a feature, not a bug: you get hints exactly when the feature is active. - **aot.factories** is **global and unconditional**. Any app with the JAR on its classpath at AOT time runs the registrar regardless of active beans or conditions. Great for "always needed" library metadata; wasteful/incorrect if the hints should only apply when a feature is enabled. ### Ownership / discoverability - `@ImportRuntimeHints` co-locates the hint declaration next to the code needing it — easy for application teams to find and maintain. - `aot.factories` centralizes hints for a whole library where consumers can't (and shouldn't) annotate anything. ### Coupling - The annotation couples the registrar to a specific config class — fine within one codebase. - The factories file decouples entirely — the registrar needn't be referenced by any bean, which is exactly what a library wants. ## Practical guidance | Situation | Use | |---|---| | App-level DTOs reflectively bound in one feature | `@ImportRuntimeHints` on that feature's config (or `@RegisterReflectionForBinding`) | | A `@ConditionalOnProperty` integration that needs proxies only when enabled | `@ImportRuntimeHints` on the conditional config | | A reusable starter that must register hints for internal types in every consumer | `spring/aot.factories` | | Framework-level hints independent of any bean graph | `spring/aot.factories` | ## Related mechanisms (don't confuse them) - **`@Reflective` / `ReflectiveProcessor`** — element-level reflection hints via a bean-factory-initialization AOT processor. - **`@RegisterReflectionForBinding` / `@RegisterReflection`** — convenience reflection registration. - **`BeanRegistrationAotProcessor` / `BeanFactoryInitializationAotProcessor`** — lower-level SPIs for generating code and hints during AOT; more powerful and more complex than a plain registrar. ## Gotchas - Putting hints in `aot.factories` when they're conditional can register metadata for features that are off — usually harmless (bigger image) but occasionally wrong if types aren't present. - Relying on `@ImportRuntimeHints` for library hints fails silently for consumers who never import your config. - Both channels still require the registrar to have a no-arg constructor and to actually call the `RuntimeHints` registries. - AOT evaluates `@Conditional`/`@Profile` at **build time** with the build-time environment — so a config gated on a property absent during the AOT build won't contribute its hints. Make sure build-time conditions match intended native runtime.
- A @Configuration carrying @ImportRuntimeHints is gated by @ConditionalOnProperty that is false during the AOT build. Are its hints contributed?No. AOT evaluates conditions at build time; if the config is excluded then, its registrar never runs and no hints are emitted. If the feature can be enabled at native runtime you must ensure the property is set at build time or move the hints to an unconditional channel.
- Why do Spring Boot starters prefer aot.factories over @ImportRuntimeHints?Because consumers never reference the starter's internal config classes, so a bean-scoped annotation wouldn't reliably fire. aot.factories makes the registrar run globally whenever the starter JAR is on the AOT classpath.
saying these in an interview costs you the question
- Claiming both channels are identical with no scope difference
- Saying @ImportRuntimeHints hints always fire regardless of conditions
- Confusing spring.factories with spring/aot.factories