skip to content

Spring AOT Engine

What Spring's AOT engine does at build time: generating the bean factory, emitting bean-definition code, the processor SPI, freezing conditions, and running AOT mode on a plain JVM. This is the Spring half of native support, separate from GraalVM itself.

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

explore

questions

25

What are the generated `*__BeanDefinitions` source classes that Spring's AOT engine produces at build time, and why are they created?

level: juniorimportance: must knowfreq 55%

answer

  1. `MyService__BeanDefinitions` static methods build RootBeanDefinition
  2. processAot task → build/generated/aotSources
  3. closed-world = no runtime reflection/scanning
  4. generated ApplicationContextInitializer replaces scanning
  5. spring.aot.enabled=true flips the runtime path

basics

~20 s

During an AOT build, Spring generates plain Java classes named like MyService__BeanDefinitions that build each bean's definition in explicit code instead of scanning and reflecting at startup. This lets GraalVM native images work without runtime reflection.

solid answer

~40 s

Spring's ahead-of-time (AOT) engine runs at build time (Spring Boot's `processAot` task). It analyzes the `BeanFactory` and, for each bean, emits a generated Java source class such as `MyService__BeanDefinitions` containing static methods that construct a `RootBeanDefinition`. Alongside these it generates an `ApplicationContextInitializer` that registers all of them. The point is the closed-world model of GraalVM native image: everything must be known at build time. Instead of classpath scanning, parsing `@Configuration`, and reflectively invoking constructors at startup, the generated code does it in ordinary Java — faster startup and no reflection. When `spring.aot.enabled=true` (automatic in native builds) the context uses this generated initializer instead of the normal refresh scanning path.

code

java · 21 lines
java
// Roughly what the AOT engine emits into build/generated/aotSources/
public final class MyService__BeanDefinitions {

    public static BeanDefinition getMyServiceBeanDefinition() {
        RootBeanDefinition beanDefinition = new RootBeanDefinition(MyService.class);
        // instance supplier replaces reflective instantiation:
        beanDefinition.setInstanceSupplier(MyService::new);
        return beanDefinition;
    }
}

// And a generated initializer that registers it (simplified):
public class DemoApplication__ApplicationContextInitializer
        implements ApplicationContextInitializer<GenericApplicationContext> {
    @Override
    public void initialize(GenericApplicationContext context) {
        DefaultListableBeanFactory bf = context.getDefaultListableBeanFactory();
        bf.registerBeanDefinition("myService",
            MyService__BeanDefinitions.getMyServiceBeanDefinition());
    }
}

go deeper

for a junior

Know the name pattern *__BeanDefinitions, that it's generated at build time, and that it replaces runtime scanning/reflection for native image.

for a middle

Explain the processAot build step, the generated ApplicationContextInitializer, and the spring.aot.enabled runtime switch.

for a senior

Connect it to the closed-world assumption, build-time condition/profile evaluation, and where RuntimeHints fill remaining reflection gaps.

for a principal

Discuss how freezing the bean graph at build time changes conditional configuration semantics and the testing strategy for AOT vs. non-AOT parity.

## The problem AOT solves A normal Spring `ApplicationContext` builds itself at **runtime**: it scans the classpath for `@Component`/`@Configuration`, parses annotations, and uses **reflection** to instantiate beans and inject dependencies. That dynamic behavior is fine on the JVM but incompatible with **GraalVM native image**, which uses a **closed-world assumption** — the native compiler (`native-image`) must know at build time every class, constructor, method, and field that will ever be reflected on, proxied, or serialized. Anything discovered dynamically at runtime is unavailable. ## What the AOT engine does Spring Framework 6 / Spring Boot 3 introduced an **AOT processing phase** that runs **at build time** (Boot's Gradle `processAot` task or Maven `spring-boot:process-aot`). It actually creates and partially refreshes the `BeanFactory` in a headless way, then hands it to `ApplicationContextAotGenerator`, which walks every registered `BeanDefinition` and **generates Java source code** into `build/generated/aotSources` (and reflection metadata into `build/generated/aotResources`). For each bean it emits a class named after the bean's defining type with the suffix `__BeanDefinitions`, e.g. `MyService__BeanDefinitions`. Inside are `static` methods that build a `RootBeanDefinition` for that bean — setting its target class, scope, an **instance supplier** (which replaces reflective instantiation), property values, etc. It also generates a top-level `<AppName>__ApplicationContextInitializer` (an `ApplicationContextInitializer`) that, at runtime, registers all the generated bean definitions into the context. This replaces component scanning and `@Configuration` parsing. ## The runtime switch At runtime the property `spring.aot.enabled=true` (set automatically inside a native image, and settable on the JVM to test AOT mode) tells Spring to **use the generated initializer** instead of the normal scan-and-reflect refresh. So the same code path is exercised on the JVM and in native — the generated sources are just regular compiled Java. ## Why this matters - **No reflection at startup** for instantiation → works under the closed world. - **Faster startup / lower memory**, even on the JVM, because scanning and annotation parsing are skipped. - The generated code is **human-readable Java** — you can open `MyService__BeanDefinitions.java` in `build/generated/` and see exactly what Spring decided. ## Gotchas - Generated sources are a build **output**, not something you edit — they are regenerated every AOT build. - AOT freezes the bean set: beans that only appear based on runtime-only conditions won't exist. Profiles/conditions are evaluated at **build time** (you can pass `spring.profiles.active` to the AOT task). - Reflection that AOT can't remove (e.g. some field injection, JPA) still needs **`RuntimeHints`**; AOT registers those hints so `native-image` keeps the metadata.

  • Where do the generated sources end up and are they meant to be edited?
    In a Gradle build they land under `build/generated/aotSources` (resources under `aotResources`). They are regenerated on every AOT build and should never be hand-edited; treat them as compiler output you can read for debugging.
  • Does AOT-generated code only help native image, or also plain JVM runs?
    It helps both. Native image needs it for the closed world, but running on the JVM with `spring.aot.enabled=true` also skips scanning and annotation parsing, giving faster startup and lower memory.

saying these in an interview costs you the question

  • Thinking AOT generates bytecode/proxies at runtime — it generates Java source at build time
  • Believing the generated classes are hand-maintained or checked into source control
  • Assuming AOT is native-image-only and has no effect on the JVM
  • Claiming AOT eliminates all reflection (it registers RuntimeHints for what remains)

context

open as a page

What does it mean that Spring AOT 'freezes' conditions and the bean set at build time?

level: juniorimportance: must knowfreq 55%

basics

~20 s

During an AOT build Spring evaluates all @Conditional/@Profile checks and auto-configuration once, decides which beans exist, and generates code registering exactly those beans. Those decisions are fixed; they are not re-checked when the app runs.

open as a page

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

level: middleimportance: must knowfreq 40%

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.

open as a page

What is an instance supplier in AOT-generated bean definitions, and how does it replace reflective bean instantiation?

level: middleimportance: must knowfreq 50%

basics

~20 s

An instance supplier is a callback set on the BeanDefinition (via setInstanceSupplier) that creates the bean by directly calling its constructor or factory method in generated code, instead of Spring reflectively finding and invoking the constructor at runtime.

open as a page

Why does changing spring.profiles.active at runtime fail to swap beans in an AOT / native Spring Boot app?

level: middleimportance: must knowfreq 60%

basics

~20 s

Because @Profile is a condition. AOT evaluates profiles at build time and bakes in only the beans for the profiles active then. Activating a different profile at runtime cannot add beans that were never generated.

open as a page

What is ApplicationContextAotGenerator and what does it produce?

level: middleimportance: must knowfreq 40%

basics

~20 s

ApplicationContextAotGenerator is the Spring class that drives AOT context generation. Given a prepared application context, it emits a generated ApplicationContextInitializer that programmatically registers all the bean definitions, so at runtime the context is built without classpath scanning.

open as a page

AOT mode 'freezes' bean definitions. Explain precisely what is frozen, what still varies at runtime, and which application designs break under this constraint.

level: seniorimportance: must knowfreq 34%

basics

~20 s

AOT freezes the set of bean definitions — every @Conditional, @Profile and @ConditionalOnProperty is evaluated at build time, so the beans that exist are fixed. Injected property values still resolve at runtime. Designs that add/remove beans based on runtime environment break.

open as a page

What are the two AOT processor SPIs Spring gives modules to plug into ahead-of-time processing, and what does each contribute?

level: juniorimportance: should knowfreq 35%

basics

~20 s

BeanRegistrationAotProcessor 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.

open as a page

What is Spring's AOT mode on the JVM, and what does the spring.aot.enabled property do?

level: juniorimportance: should knowfreq 22%

basics

~20 s

AOT mode runs code Spring generated at build time — an ApplicationContextInitializer with pre-computed bean definitions — instead of classpath scanning and reflection at startup. Setting spring.aot.enabled=true enables it, giving faster startup on an ordinary JVM.

open as a page

What is Spring's ahead-of-time (AOT) processing, and what does the process-aot build step do?

level: juniorimportance: should knowfreq 45%

basics

~20 s

AOT processing runs at build time. It inspects your Spring app and pre-computes how the beans wire together, generating Java source that sets up the context quickly at startup. In Maven it's the spring-boot:process-aot goal; in Gradle the processAot task.

open as a page

Does the frozen-condition behavior only apply to GraalVM native images, or also to JVM apps? How would you reproduce and diagnose it?

level: middleimportance: should knowfreq 35%

basics

~10 s

It also applies on the JVM. Setting spring.aot.enabled=true runs the same generated, frozen-bean initializer. So you can reproduce and debug frozen-condition issues on a normal JVM before ever building a native image.

open as a page

How do you produce and run an AOT-optimized Spring Boot application on a plain JVM, and what are the two distinct phases involved?

level: middleimportance: should knowfreq 28%

basics

~10 s

Two phases. Build phase: run AOT processing (Gradle processAot / Maven process-aot) to generate the context code. Run phase: start the jar on a normal JVM with -Dspring.aot.enabled=true so Spring uses the generated ApplicationContextInitializer.

open as a page

How do you trigger AOT processing in a Maven and a Gradle Spring Boot build, and how is it enabled at runtime?

level: middleimportance: should knowfreq 35%

basics

~20 s

In Maven you run the spring-boot:process-aot goal; in Gradle you run the processAot task. Both generate the AOT sources. At runtime the generated code is used only when spring.aot.enabled=true, which is automatic inside a native image.

open as a page

Walk through what a BeanRegistrationAotContribution's applyTo does with the GenerationContext — how does a processor emit generated code and hints?

level: seniorimportance: should knowfreq 38%

basics

~20 s

processAheadOfTime returns a contribution; its applyTo(GenerationContext, BeanRegistrationCode) is called to do the work. Through the GenerationContext it registers RuntimeHints (getRuntimeHints()) and emits generated Java classes (getGeneratedClasses()); the BeanRegistrationCode lets it customise the bean's instantiation code.

open as a page

When would you reach for a BeanFactoryInitializationAotProcessor versus a RuntimeHintsRegistrar, and how do they differ?

level: seniorimportance: should knowfreq 33%

basics

~20 s

RuntimeHintsRegistrar only registers RuntimeHints — it can't generate code and needs no view of the bean factory. Use an AOT processor when you must also emit generated code or make decisions based on the actual bean definitions in the factory.

open as a page

How do AOT-generated bean definitions change the handling of `@Configuration` classes and `@Bean` methods compared to normal CGLIB-based proxying?

level: seniorimportance: should knowfreq 24%

basics

~20 s

Normally full @Configuration classes are CGLIB-enhanced so inter-@Bean calls return the shared singleton. In AOT mode there's no CGLIB subclass; the generated code calls @Bean methods directly and wires bean references through the container, preserving singleton semantics without a proxy.

open as a page

Walk through how `BeanDefinitionMethodGenerator` and `BeanRegistrationCodeFragments` produce the generated registration code, and how you'd customize a fragment.

level: seniorimportance: should knowfreq 28%

basics

~10 s

For each bean, BeanDefinitionMethodGenerator emits a method that builds the BeanDefinition. It delegates the pieces (new definition, properties, instance supplier) to BeanRegistrationCodeFragments. You customize output by contributing an AOT processor that wraps/overrides those fragments.

open as a page

How do dynamic / programmatic bean registration mechanisms behave under Spring AOT, and what breaks?

level: seniorimportance: should knowfreq 45%

basics

~10 s

Programmatic registration (BeanDefinitionRegistryPostProcessor, ImportBeanDefinitionRegistrar, registerBean) runs at build time during AOT and its output is frozen into generated code. Registration that depends on runtime state, or done after startup, is lost in native images.

open as a page

During AOT processing, how far does Spring take the ApplicationContext, and what code runs at build time versus at runtime under spring.aot.enabled?

level: seniorimportance: should knowfreq 24%

basics

~20 s

At build time AOT refreshes the context to obtain all bean definitions — running BeanFactoryPostProcessors but not instantiating your singletons — then generates code. At runtime the generated ApplicationContextInitializer registers those definitions and normal bean instantiation and lifecycle proceed.

open as a page

What runtime dynamism does AOT give up, and what must be decided at build time?

level: seniorimportance: should knowfreq 30%

basics

~20 s

AOT freezes the bean graph at build time. Component scanning, @Conditional evaluation, and active profiles are all resolved during process-aot, so you can't add or switch beans by profile or condition at runtime. Missing reflection needs explicit RuntimeHints.

open as a page

What are the key correctness gotchas when authoring AOT processors — build-time execution, determinism, the excluded-bean rule, and native-only failures?

level: principalimportance: should knowfreq 25%

basics

~20 s

AOT processors run at build time, so their logic must be deterministic and independent of runtime state. Processor beans are excluded from the runtime context by default. Missing RuntimeHints pass on the JVM but fail only in the native image, so hints must match every reflective call in the generated code.

open as a page

As an architect adopting native image, what are the failure modes of AOT-generated bean definitions and how do you keep JVM and native behavior in parity?

level: principalimportance: should knowfreq 18%

basics

~20 s

The bean graph is frozen at build time, so runtime-only conditions, dynamic bean registration, and un-hinted reflection break. Mitigate by evaluating profiles at AOT time, providing RuntimeHints/AOT processors, and testing with spring.aot.enabled=true plus native tests.

open as a page

Given build-time frozen conditions, how do you architect a Spring Boot service that still needs environment-specific and runtime-toggleable behavior across native deployments?

level: principalimportance: should knowfreq 30%

basics

~20 s

Move variability from bean presence to bean behavior: always register the beans, decide at runtime via configuration values. Reserve @Profile/@Conditional for build-time-stable choices, and build one native image per environment when bean sets truly differ.

open as a page

As a tech lead, when would you adopt JVM AOT mode (spring.aot.enabled) instead of leaving startup unoptimized or going fully native, and what trade-offs govern the decision?

level: principalimportance: should knowfreq 18%

basics

~20 s

Use JVM AOT mode when you want faster startup and lower memory without GraalVM's cost and closed-world debugging pain. It suits scale-to-zero or many-instance JVM deployments, provided a fixed bean shape per build is acceptable. Skip it if you rely on runtime-variable wiring.

open as a page

How would you extend Spring's AOT engine to contribute custom generated code or runtime hints for a bean the engine can't model?

level: principalimportance: nice to knowfreq 18%

basics

~10 s

Implement Spring's AOT SPI: a BeanRegistrationAotProcessor (per-bean) or BeanFactoryInitializationAotProcessor (factory-level) returns an AotContribution that emits generated code and registers RuntimeHints. For simple reflection needs, a RuntimeHintsRegistrar via @ImportRuntimeHints is enough.

open as a page