skip to content

How does Spring discover and register a custom BeanRegistrationAotProcessor or BeanFactoryInitializationAotProcessor?

level: middleimportance: must knowfreq 40%

answer

  1. META-INF/spring/aot.factories (not spring.factories)
  2. Key = FQ interface name
  3. Or: bean that implements the SPI
  4. isBeanExcludedFromAotProcessing() default true
  5. Check build/generated/aotSources

basics

~10 s

Two ways: list the implementing class in META-INF/spring/aot.factories under the SPI interface name, or register a bean that implements the interface — Spring auto-detects such beans during AOT processing.

solid answer

~40 s

There are two discovery mechanisms. (1) Library-style registration: put the fully-qualified interface name (e.g. org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor) as a key in META-INF/spring/aot.factories, with your implementation class as the value — this is loaded even before the context is fully built, ideal for framework/library extensions. Note it is aot.factories, a distinct file from the classic spring.factories. (2) Bean-style registration: define a Spring bean that implements the SPI interface; the AOT engine detects those beans in the factory and invokes them. For BeanRegistrationAotProcessor, such infrastructure beans are excluded from normal AOT bean contribution by default (isBeanExcludedFromAotProcessing() returns true), so the processor itself is not registered as a runtime bean in the optimized context.

code

java · 27 lines
java
// META-INF/spring/aot.factories
// org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
// com.example.MyRegistrationAotProcessor

package com.example;

import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.lang.Nullable;

public class MyRegistrationAotProcessor implements BeanRegistrationAotProcessor {

    @Nullable
    @Override
    public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
        if (!MyMarker.class.isAssignableFrom(registeredBean.getBeanClass())) {
            return null; // nothing to contribute for this bean
        }
        return (generationContext, beanRegistrationCode) ->
            generationContext.getRuntimeHints()
                .reflection()
                .registerType(registeredBean.getBeanClass());
    }

    // Kept out of the runtime optimized context by default (returns true).
}

go deeper

for a junior

Know that a factories file or an implementing bean makes Spring pick the processor up.

for a middle

Know the exact file (META-INF/spring/aot.factories), the interface-name key, and the bean-detection alternative.

for a senior

Understand isBeanExcludedFromAotProcessing and when library vs application registration is appropriate.

for a principal

Can weigh load-ordering and runtime-footprint implications of the two paths and design AOT support for a shared library.

## The problem: how does the AOT engine find your processor? A custom AOT processor is useless unless the build-time engine knows to call it. Spring supports two registration paths. ### Path 1 — `META-INF/spring/aot.factories` (recommended for libraries) A `spring.factories`-style properties file, but a **separate file dedicated to AOT**: ``` # META-INF/spring/aot.factories org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\ com.example.MyInitializationAotProcessor org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\ com.example.MyRegistrationAotProcessor ``` The key is the **fully-qualified SPI interface name**; the value is your implementation (comma-separated for several). This file is loaded via `AotFactoriesLoader`/`SpringFactoriesLoader` early in the AOT run, so the processor participates even for infrastructure that exists before the user context is built. This is how framework modules (Spring Data, Spring Security, etc.) ship their AOT support. **Gotcha:** it is `META-INF/spring/aot.factories`, *not* `META-INF/spring.factories`. Putting an AOT processor in the classic file will not register it for AOT. ### Path 2 — a bean that implements the interface If you register an ordinary bean whose class implements `BeanRegistrationAotProcessor` or `BeanFactoryInitializationAotProcessor`, the AOT engine detects it while walking the factory and invokes it. This is convenient inside an application (no factories file needed): ```java @Bean MyRegistrationAotProcessor myRegistrationAotProcessor() { return new MyRegistrationAotProcessor(); } ``` #### The `isBeanExcludedFromAotProcessing()` subtlety `BeanRegistrationAotProcessor` declares: ```java default boolean isBeanExcludedFromAotProcessing() { return true; } ``` Because such a bean is **infrastructure that only matters at build time**, by default it is excluded from being contributed as a regular bean in the generated (optimized) context — you do not want your build-time processor present as a live runtime bean. If for some reason you *do* need it at runtime too, override the method to return `false`. ### When each path is used - **`aot.factories`** — you are a library/starter, or your processor must run before the application beans exist. Purely build-time; no runtime bean. - **bean-implements-interface** — you are inside an application and it is simplest to just declare a bean. ## Verification tip After running `./gradlew processAot`, inspect `build/generated/aotSources` to confirm your contribution's generated code/hints appear — that is the concrete proof your processor was discovered and invoked.

  • Why is aot.factories a separate file from spring.factories?
    AOT processors run only at build time and must be loadable before the full context exists; keeping them in a dedicated aot.factories avoids polluting runtime SpringFactoriesLoader lookups and makes the build-time-only nature explicit.
  • Your processor bean is showing up in the running native app — why, and how do you stop it?
    It's a BeanRegistrationAotProcessor whose isBeanExcludedFromAotProcessing() was overridden to return false; restore the default true so the infrastructure bean is excluded from the optimized runtime context.

saying these in an interview costs you the question

  • Claiming registration goes in META-INF/spring.factories
  • Thinking the processor runs at runtime rather than build time
  • Believing you must always use a factories file — a bean implementing the interface also works

context