skip to content

@ImportRuntimeHints

@ImportRuntimeHints attaches a registrar to a configuration class or bean so its hints are contributed during AOT processing. This is how a library ships its own native support instead of pushing the work onto users.

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

explore

questions

5

What does the @ImportRuntimeHints annotation do in a Spring application?

level: juniorimportance: must knowfreq 45%

answer

  1. Registrar wired to a @Configuration/bean
  2. runs during AOT, not runtime
  3. reflection / resources / proxies / serialization hints
  4. no-arg ctor, not a bean
  5. alternative to spring/aot.factories

basics

~20 s

@ImportRuntimeHints is placed on a @Configuration class or bean to register one or more RuntimeHintsRegistrar classes. During AOT/native-image build, Spring calls those registrars so they can declare reflection, resource, and proxy hints the GraalVM compiler needs.

solid answer

~40 s

@ImportRuntimeHints links a RuntimeHintsRegistrar (a class implementing registerHints(RuntimeHints, ClassLoader)) to a Spring component — usually a @Configuration class or any bean. When Spring runs ahead-of-time (AOT) processing, typically for a GraalVM native image, it invokes each referenced registrar so it can programmatically declare hints: which classes need reflection, which resources must be bundled, which JDK proxies are used, which types are serialized. These hints tell the native-image compiler to keep metadata that closed-world static analysis would otherwise strip. Without them, code that works on the JVM fails at native runtime with ClassNotFoundException, missing-method, or missing-resource errors. It is the annotation-driven, ergonomic way to contribute hints, as an alternative to registering a RuntimeHintsRegistrar in a spring/aot.factories file.

code

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

@Configuration
@ImportRuntimeHints(MyConfig.MyHints.class)
class MyConfig {

    static class MyHints implements RuntimeHintsRegistrar {
        @Override
        public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
            hints.reflection().registerType(
                com.example.SomeReflectivelyUsedType.class,
                MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
                MemberCategory.INVOKE_DECLARED_METHODS);
            hints.resources().registerPattern("templates/*.html");
        }
    }
}

go deeper

for a junior

Know it registers a RuntimeHintsRegistrar for native-image hints and runs at build time, not runtime.

for a middle

Should be able to write a registrar, call reflection()/resources(), and place @ImportRuntimeHints on a config.

for a senior

Contrast @ImportRuntimeHints (bean-scoped) with spring/aot.factories (global) and the @Reflective family; know the no-arg-ctor constraint.

for a principal

Reason about hint strategy across a modular codebase, conditional configs at AOT time, and testing hints with RuntimeHintsPredicates.

## The problem it solves GraalVM **native image** compiles a Java application ahead-of-time under a **closed-world assumption**: everything reachable must be known at build time. Dynamic features — **reflection**, **JDK dynamic proxies**, loading **resources** from the classpath, **serialization** — are invisible to static analysis, so the compiler strips the metadata for them. Code that relied on `Class.forName(...)`, `clazz.getDeclaredMethod(...)`, or `getResourceAsStream(...)` then fails at native **runtime**. Spring solves this with **runtime hints**: a build-time description of the dynamic behavior your app needs, emitted into GraalVM's `reachability-metadata`/config files during Spring's **AOT processing** phase. ## RuntimeHintsRegistrar `org.springframework.aot.hint.RuntimeHintsRegistrar` is the interface you implement: ```java public interface RuntimeHintsRegistrar { void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader); } ``` `RuntimeHints` exposes sub-registries: `hints.reflection()`, `hints.resources()`, `hints.proxies()`, `hints.serialization()`, and `hints.reflection().registerType(...)` etc. You call these to declare what the native compiler must preserve. ## @ImportRuntimeHints — the wiring `org.springframework.context.annotation.ImportRuntimeHints` takes an array of `RuntimeHintsRegistrar` classes and attaches them to the annotated component: ```java @Configuration @ImportRuntimeHints(MyRuntimeHints.class) class MyConfig { } ``` During AOT processing (e.g. `./gradlew nativeCompile`, `mvn -Pnative`, or the `process-aot` phase), Spring's **AOT engine** discovers every bean/config carrying `@ImportRuntimeHints`, **instantiates each registrar via its default (no-arg) constructor**, and calls `registerHints`. The resulting hints are merged with hints from starters, `@Reflective`-annotated code, and `spring/aot.factories`, then written to GraalVM's config so `native-image` keeps the metadata. ## Where it can go - On a `@Configuration` class (most common). - On any `@Component`/`@Bean` class — it is a meta-annotation-friendly, `@Import`-adjacent mechanism processed as configuration metadata. - It is **repeatable-by-array**: `@ImportRuntimeHints({A.class, B.class})`. ## Alternative registration paths (know the difference) 1. **`@ImportRuntimeHints`** — declarative, tied to a bean; the registrar only runs if that bean/config is part of the context. Best when hints are specific to a feature/config. 2. **`spring/aot.factories`** — list `RuntimeHintsRegistrar` under the `org.springframework.aot.hint.RuntimeHintsRegistrar` key. This runs **globally/unconditionally** during AOT of any app on the classpath, regardless of which beans are active. Best for library/starter authors. 3. **`@Reflective` / `@RegisterReflectionForBinding` / `@RegisterReflection`** — targeted convenience annotations for the common reflection cases without writing a registrar. ## Key gotchas - **Runs at build time, not runtime.** The registrar executes during AOT processing, never when the app serves traffic. Don't put runtime logic there. - **No-arg constructor required.** Spring instantiates the registrar reflectively with no dependency injection — it is not a bean and cannot @Autowired anything. - **Hints are additive, not automatic.** Declaring the registrar does nothing unless you actually call the `RuntimeHints` methods for the right types/resources. - **Conditional beans.** If the @Configuration carrying the annotation is excluded (e.g. by `@ConditionalOnProperty`) it still participates in AOT the same way it would at runtime — AOT evaluates conditions at build time, so an excluded config contributes no hints. - **Only matters for AOT/native.** On a plain JVM the annotation is effectively inert; nothing breaks, no hints are needed. - **Test with `RuntimeHintsPredicates`.** Assert your registrar produced the expected hints in a unit test rather than discovering gaps only at native-image build.

  • When would the registrar's registerHints method actually execute?
    Only during Spring's ahead-of-time (AOT) processing phase — the build-time step that runs before/for a GraalVM native image (or when AOT mode is explicitly enabled). Never during normal request-serving runtime.
  • Does anything happen if you run the app on a normal JVM without native image?
    Effectively nothing — the hints aren't needed because the JVM keeps all reflective/resource metadata by default. The annotation is inert unless AOT/native processing runs, so it's safe to leave in place.

context

open as a page

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

level: middleimportance: must knowfreq 40%

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.

open as a page

Why does GraalVM native image need runtime hints at all, and what category of failures does @ImportRuntimeHints prevent?

level: middleimportance: should knowfreq 28%

basics

~20 s

GraalVM native image uses closed-world static analysis and strips metadata for dynamic features like reflection, proxies, resources, and serialization. Hints tell the compiler to keep them, preventing native-runtime errors such as ClassNotFoundException, NoSuchMethodException, or missing-resource failures that never occur on the JVM.

open as a page

When should you register a RuntimeHintsRegistrar via @ImportRuntimeHints versus via spring/aot.factories?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Use @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.

open as a page

How do you verify a RuntimeHintsRegistrar produces the right hints, and how do @Conditional configs interact with @ImportRuntimeHints at AOT time?

level: principalimportance: should knowfreq 18%

basics

~20 s

Unit-test the registrar by calling registerHints on a fresh RuntimeHints and asserting with RuntimeHintsPredicates (e.g. reflection().onType(...)). Remember AOT evaluates @Conditional/@Profile at build time, so a config excluded during the AOT build contributes no hints — align build-time conditions with intended native behavior.

open as a page