What are the generated `*__BeanDefinitions` source classes that Spring's AOT engine produces at build time, and why are they created?
answer
- `MyService__BeanDefinitions` static methods build RootBeanDefinition
- processAot task → build/generated/aotSources
- closed-world = no runtime reflection/scanning
- generated ApplicationContextInitializer replaces scanning
- spring.aot.enabled=true flips the runtime path
basics
~20 sDuring 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 sSpring'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// 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
Know the name pattern *__BeanDefinitions, that it's generated at build time, and that it replaces runtime scanning/reflection for native image.
Explain the processAot build step, the generated ApplicationContextInitializer, and the spring.aot.enabled runtime switch.
Connect it to the closed-world assumption, build-time condition/profile evaluation, and where RuntimeHints fill remaining reflection gaps.
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)