skip to content

Generated Bean Definitions

AOT emits generated bean-definition sources with instance suppliers, replacing reflective instantiation with direct calls. This is what makes a Spring context work under the closed-world native assumption.

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

explore

questions

5

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

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

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