skip to content

What is the ClassLoader parameter of registerHints for, and how do you decide between a RuntimeHintsRegistrar, @RegisterReflectionForBinding, and a BeanRegistrationAotProcessor?

level: principalimportance: nice to knowfreq 20%

answer

  1. ClassLoader => conditional ClassUtils.isPresent
  2. @Nullable loader, use the passed one
  3. @RegisterReflectionForBinding = declarative shortcut
  4. RuntimeHintsRegistrar = general escape hatch
  5. BeanRegistrationAotProcessor = per-bean hints from AOT model

basics

~20 s

The ClassLoader lets you register hints conditionally — e.g. only if an optional dependency is on the classpath (ClassUtils.isPresent). Choose @RegisterReflectionForBinding for simple binding types, RuntimeHintsRegistrar for general programmatic hints, and a BeanRegistrationAotProcessor when hints must be derived per bean during AOT.

solid answer

~40 s

The ClassLoader argument is the build-time class loader; use it to make hints conditional on classpath presence — ClassUtils.isPresent("com.optional.Lib", classLoader) so you don't force a hard dependency and only contribute hints when the optional library is actually there. It's also passed to TypeReference resolution for types you reference by name. On tool choice: @RegisterReflectionForBinding (or @RegisterReflection) is the declarative shortcut for reflection on serialization/binding types — least code, colocated with the DTO. RuntimeHintsRegistrar is the general programmatic escape hatch for arbitrary reflection/resource/proxy/serialization hints, wired via @ImportRuntimeHints or aot.factories. A BeanRegistrationAotProcessor (or BeanFactoryInitializationAotProcessor) is for when hints must be computed by inspecting bean definitions during AOT — e.g. a framework contributing hints per matching bean. Prefer the most declarative option that fits; drop to the processor only when you need the AOT bean model.

code

java · 18 lines
java
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.util.ClassUtils;

public class ConditionalHints implements RuntimeHintsRegistrar {
    @Override
    public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
        // Only contribute if the optional integration is on the classpath.
        if (ClassUtils.isPresent("com.optional.FancyClient", classLoader)) {
            hints.reflection().registerType(
                    TypeReference.of("com.optional.FancyClient"),
                    MemberCategory.INVOKE_PUBLIC_METHODS);
            hints.resources().registerPattern("fancy/*.properties");
        }
    }
}

go deeper

for a junior

Not expected to know the ClassLoader nuance.

for a middle

Should know the ClassLoader enables conditional hints via ClassUtils.isPresent.

for a senior

Should choose between @RegisterReflectionForBinding and RuntimeHintsRegistrar appropriately.

for a principal

Should place all options on the declarative-to-powerful ladder and justify dropping to a Bean*AotProcessor only when the AOT bean model is required.

**The ClassLoader parameter.** `registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader)` receives the class loader active during AOT processing. Two uses: 1. **Conditional contribution.** Libraries often support optional integrations. You only want to emit hints for a dependency if it's actually present, avoiding a hard classpath requirement: ```java public void registerHints(RuntimeHints hints, ClassLoader cl) { if (ClassUtils.isPresent("com.optional.FancyClient", cl)) { hints.reflection().registerType( TypeReference.of("com.optional.FancyClient"), MemberCategory.INVOKE_PUBLIC_METHODS); } } ``` Using the *passed* loader (not `getClass().getClassLoader()`) ensures you check against the same view of the classpath the AOT build uses. It may be `@Nullable`, so guard accordingly. 2. **Name-based type resolution.** When you build `TypeReference`s or need to load a type by name for inspection, the provided loader is the correct one. **Choosing the right AOT extension point.** Spring offers a ladder from most declarative to most powerful: - **`@RegisterReflectionForBinding(Foo.class)` / `@RegisterReflection`.** Declarative annotations placed on a class or method. `@RegisterReflectionForBinding` registers the reflection needed to *bind* (serialize/deserialize) the given types — the common case for DTOs used with Jackson or message converters. Least boilerplate, colocated with the code. Use this first when the need is 'make these binding types reflectively available'. - **`RuntimeHintsRegistrar`.** The general programmatic API. Reach for it when you need hints beyond simple binding: resource patterns, JDK proxies, serialization, conditional/classpath-dependent logic, or hints for types you don't own. Wire via `@ImportRuntimeHints` (app) or `aot.factories` (library). - **`BeanRegistrationAotProcessor`.** A callback invoked *per registered bean* during AOT processing; it returns a `BeanRegistrationAotContribution` that can both customize the generated bean instantiation code *and* register runtime hints. Use it when the hints depend on inspecting each bean definition — e.g. a framework that, for every bean implementing `SomeMarker`, must contribute tailored reflection. It has access to the `RegisteredBean` model that a plain registrar lacks. - **`BeanFactoryInitializationAotProcessor`.** Operates once over the whole bean factory during AOT, returning a `BeanFactoryInitializationAotContribution`. Use for factory-wide analysis (scan all beans, then contribute code + hints) rather than per-bean. **Decision heuristic.** - Just need binding types reflectively available? → `@RegisterReflectionForBinding`. - Need arbitrary/mixed hints or conditional-on-classpath logic, not derived from beans? → `RuntimeHintsRegistrar`. - Hints must be *computed from the bean model* (per bean or per factory)? → `BeanRegistrationAotProcessor` / `BeanFactoryInitializationAotProcessor`. **Gotchas.** - Don't reach for a processor when a registrar suffices — the processors are heavier and tie you to the AOT contribution lifecycle. - The ClassLoader can be null; unconditional `TypeReference.of` avoids needing it for pure name-based registration. - Processors are registered themselves via `aot.factories` (their respective keys), not `@ImportRuntimeHints`. - All of these run only at build time; none affect JVM runtime behavior.

  • Why use the ClassLoader passed into registerHints instead of getClass().getClassLoader()?
    The passed loader reflects the classpath view the AOT build is analyzing, so ClassUtils.isPresent checks match what will actually be compiled. Using your own loader risks a different view and inconsistent conditional hints; it may also be null, which you must guard.
  • When would a plain RuntimeHintsRegistrar be insufficient and you'd need a BeanRegistrationAotProcessor?
    When the hints depend on inspecting individual bean definitions during AOT — e.g. for every bean of a certain type you must contribute tailored reflection or customize its generated instantiation code. A registrar has no access to the RegisteredBean/AOT bean model; the processor does.

saying these in an interview costs you the question

  • Ignoring that the ClassLoader can be null
  • Using a RuntimeHintsRegistrar when @RegisterReflectionForBinding would do
  • Thinking BeanRegistrationAotProcessor is just a fancier registrar (it works on the bean model)
  • Hard-depending on an optional library instead of ClassUtils.isPresent

context