What is Spring's AOT mode on the JVM, and what does the spring.aot.enabled property do?
answer
- build-time generated ApplicationContextInitializer
- spring.aot.enabled=true on a normal JVM
- no scanning / no reflection at startup
- native sets it automatically; JVM you set it
- must run processAot first
basics
~20 sAOT 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.
solid answer
~40 sSpring Boot's AOT (ahead-of-time) engine analyzes your application at build time and generates Java source: bean definitions, an ApplicationContextInitializer, and runtime hints. On a normal JVM, setting spring.aot.enabled=true tells SpringApplication to use that generated initializer instead of the usual reflection-and-scanning refresh. You get faster startup and slightly lower memory without any native image or GraalVM — it is the exact same optimization native images rely on, just running on the regular JVM. The trade-off is that bean definitions are 'frozen' at build time: conditions and profiles are evaluated during AOT processing, not at runtime. Native images set spring.aot.enabled automatically; on a plain JVM you must set it yourself, and only after you have actually run AOT processing during the build so the generated code exists.
code
java · 16 lines// 1) Build with AOT processing (Gradle): ./gradlew processAot bootJar
// or Maven: mvn spring-boot:process-aot package
//
// 2) Run on a NORMAL JVM (no GraalVM), enabling the generated context:
// java -Dspring.aot.enabled=true -jar build/libs/app.jar
//
// The generated initializer that gets invoked looks (conceptually) like:
public class MyApplication__ApplicationContextInitializer
implements ApplicationContextInitializer<GenericApplicationContext> {
@Override
public void initialize(GenericApplicationContext context) {
// pre-computed bean registrations, no classpath scanning
context.registerBean("myService", MyService.class, MyService__BeanDefinitions::get);
// ...
}
}go deeper
Know it is a build-time optimization enabled by spring.aot.enabled that speeds startup on a regular JVM.
Explain the two phases (build-time generation, runtime flag) and that native sets the flag automatically.
Articulate the generated ApplicationContextInitializer and the frozen-bean consequence of condition evaluation moving to build time.
Frame it as a low-risk startup/memory optimization decoupled from native compilation, with a clear cost model (build complexity, frozen composition).
## What 'AOT' means here **AOT** stands for **ahead-of-time**. Normally a Spring Boot app is optimized *just-in-time*: at startup it scans the classpath (`@ComponentScan`), evaluates `@Conditional`/`@Profile`/`@ConditionalOnProperty` conditions, parses `@Configuration` classes, and builds the bean registry using reflection. That work happens every single time the app boots. Spring's **AOT engine** moves that work to **build time**. It starts a partial application context far enough to know every bean definition, then *emits Java source code* that reproduces the same context directly — no scanning, no condition evaluation, minimal reflection. The generated artifacts include: - a class named like `MyApplication__ApplicationContextInitializer` that programmatically registers all bean definitions, - generated `BeanDefinition` supplier methods (e.g. `MyService__BeanDefinitions`), - CGLIB/`@Configuration` proxy classes generated ahead of time, - `reflect-config.json` / `resource-config.json` runtime **hints** (mainly used by native image, but produced here too). ## What `spring.aot.enabled` does `spring.aot.enabled` is a **runtime** flag. When `true`, `SpringApplication` detects and runs the generated `ApplicationContextInitializer` instead of doing the reflective refresh. The context is populated from the pre-generated definitions. Key facts: - Default is **false**. On a plain JVM you must pass `-Dspring.aot.enabled=true` (or set the env var `SPRING_AOT_ENABLED=true`). - **Native images set it to true automatically** — inside a native binary there is no other way to build the context, since scanning/reflection are unavailable. - The property only *works* if the AOT-generated code is actually on the classpath. That requires running AOT processing during the build (Gradle `processAot` task / Maven `process-aot` goal). Setting the flag on a jar built without AOT does nothing useful. ## Why do this on the JVM (no native image)? Because you get most of the startup/memory win of native without the cost and constraints of GraalVM native compilation (long builds, closed-world assumptions, harder debugging). It is a low-risk stepping stone: measurably faster boot, same JVM tooling, same JIT peak throughput. ## The catch: frozen beans Because conditions and profiles are evaluated at **build time**, the *set* of beans is fixed at build time. Switching Spring profiles at runtime will **not** change which beans exist. This is the single most important gotcha and is covered in depth by the sibling questions in this leaf. ## Scope note Compiling to a native binary with GraalVM is a *separate* concern (build tooling). This leaf is only about running the AOT-generated context on a standard JVM via `spring.aot.enabled`.
- Do you need GraalVM to use spring.aot.enabled?No. AOT mode on the JVM runs the generated context on a standard JVM. GraalVM native image is a separate, optional step that happens to reuse the same AOT output.
- What happens if you set spring.aot.enabled=true but never ran AOT processing in the build?There is no generated ApplicationContextInitializer to run, so you get no AOT benefit — Spring falls back to the normal context creation (and you may see it simply ignore the flag).
saying these in an interview costs you the question
- Thinking spring.aot.enabled compiles the app to a native binary
- Believing AOT mode requires GraalVM to run
- Assuming the flag alone speeds things up without running processAot at build time