What are the two AOT processor SPIs Spring gives modules to plug into ahead-of-time processing, and what does each contribute?
answer
- Per-bean vs whole-factory
- processAheadOfTime returns nullable Contribution
- Contribution.applyTo(GenerationContext, …Code)
- Build-time code generation for native
- GenerationContext = hints + generated classes
basics
~20 sBeanRegistrationAotProcessor runs once per bean; BeanFactoryInitializationAotProcessor runs once over the whole bean factory. Both let a module contribute generated Java code and RuntimeHints during the build-time AOT step so the app works as a native image.
solid answer
~40 sSpring's AOT engine runs at build time and turns the runtime bean factory into generated Java code. Two SPIs let libraries participate. BeanRegistrationAotProcessor.processAheadOfTime(RegisteredBean) is invoked per bean and returns a nullable BeanRegistrationAotContribution — used to customise how a single bean is instantiated (e.g. Spring Data repository proxies) and to register reflection/resource hints for that bean. BeanFactoryInitializationAotProcessor.processAheadOfTime(ConfigurableListableBeanFactory) is invoked once for the entire factory and returns a nullable BeanFactoryInitializationAotContribution — used for cross-cutting concerns not tied to one bean (event listeners, @ConfigurationProperties binding, etc.). Both contributions receive a GenerationContext, from which they can emit generated classes and register RuntimeHints. Returning null means 'nothing to contribute'.
go deeper
Know there are two hooks: one per bean, one for the whole factory, both feeding build-time code generation.
Know the method signatures, RegisteredBean vs ConfigurableListableBeanFactory inputs, and that contributions carry generated code + hints.
Can articulate when to pick per-bean vs factory-wide and give real examples (Spring Data proxies, event listeners).
Can reason about the AOT engine lifecycle, determinism constraints, and how these SPIs keep native-image support extensible across the ecosystem.
## Background: what is AOT processing? Spring's *ahead-of-time* (AOT) engine runs at **build time** (via the `processAot` Gradle/Maven task, or `SpringApplicationAotProcessor`). It refreshes the `ApplicationContext` far enough to know every bean definition, then **generates Java source** that reconstructs those beans without the reflection- and classpath-scanning-heavy runtime path. This generated code is what makes GraalVM **native images** (and faster JVM startup) possible, because native images cannot do arbitrary runtime reflection or classpath scanning. ## The two SPIs Spring exposes two hooks so that **any module/library** can contribute to this generated code and to the metadata (RuntimeHints) the native compiler needs. ### 1. `BeanRegistrationAotProcessor` (per-bean) ```java public interface BeanRegistrationAotProcessor extends AotProcessor { @Nullable BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean); default boolean isBeanExcludedFromAotProcessing() { return true; } } ``` Called **once for every bean** in the factory. `RegisteredBean` exposes the bean name, resolved bean class, and merged `BeanDefinition`. You return a `BeanRegistrationAotContribution` (or `null`). This is the right hook when the *way a specific bean is created* needs to change in the generated code — the classic example is Spring Data, which replaces a repository interface with a generated proxy and registers reflection hints for its query methods. ### 2. `BeanFactoryInitializationAotProcessor` (factory-wide) ```java public interface BeanFactoryInitializationAotProcessor { @Nullable BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory); } ``` Called **once for the whole factory**. Use it for concerns that are not attached to a single bean — e.g. discovering all `@EventListener` methods, contributing `@ConfigurationProperties` binding metadata, or registering hints derived from scanning many beans at once. ## Contributions and the GenerationContext Both `applyTo(...)` methods receive a `GenerationContext`, which offers: - `getRuntimeHints()` — register reflection/resource/serialization/proxy hints (the categories themselves are a sibling topic). - `getGeneratedClasses()` / `getGeneratedFiles()` — emit new generated Java classes/files. ```java BeanRegistrationAotContribution.applyTo(GenerationContext ctx, BeanRegistrationCode code); BeanFactoryInitializationAotContribution.applyTo(GenerationContext ctx, BeanFactoryInitializationCode code); ``` ## Key gotchas - **Both return `@Nullable`** — return `null` to mean 'I have nothing to add'; don't return an empty contribution needlessly. - **Build time, not runtime.** The code inside `processAheadOfTime` executes during the build; it must be deterministic and must not depend on runtime state. - **Registration** is via `META-INF/spring/aot.factories` (not the classic `spring.factories`), or a bean that itself implements the interface is auto-detected. ## When to use which - Customising one bean's instantiation / hints → `BeanRegistrationAotProcessor`. - Cross-cutting, multi-bean, or factory-level metadata → `BeanFactoryInitializationAotProcessor`. - Only need to add hints, no code gen → prefer the lighter `RuntimeHintsRegistrar`.
- What does processAheadOfTime return when a processor has nothing to add?null — the return type is @Nullable, so returning null signals 'no contribution' and Spring skips it.
- Which hook would Spring Data use to swap a repository interface for a generated proxy?BeanRegistrationAotProcessor, because the change is specific to each repository bean's instantiation.