How do you produce and run an AOT-optimized Spring Boot application on a plain JVM, and what are the two distinct phases involved?
answer
- Phase 1: processAot / process-aot generates code
- Phase 2: -Dspring.aot.enabled=true runs it
- build-time profiles decide baked bean set
- @Value still resolves at runtime
- processTestAot for tests
basics
~10 sTwo 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.
solid answer
~40 sThere are two separate phases. At **build time** you run Spring's AOT processing — the Gradle `processAot` task or Maven `spring-boot:process-aot` goal. This actually boots a partial ApplicationContext, evaluates all conditions/profiles, and generates Java sources plus hints that are compiled into the jar. At **runtime** you start that jar on an ordinary JVM with `spring.aot.enabled=true`; SpringApplication then invokes the generated `ApplicationContextInitializer` instead of scanning and reflecting. Crucially, the profiles/properties active *during AOT processing* determine the baked-in bean set, so if prod-only beans matter you must run processing with that configuration. Native images do both automatically and set the flag for you; for JVM mode you own both steps explicitly. A common mistake is enabling the flag on a jar that was never AOT-processed, which yields no benefit.
code
java · 23 lines// --- BUILD PHASE ---
// Gradle: generate AOT code, bake the 'prod' shape, package the jar
// ./gradlew processAot -Dspring.profiles.active=prod bootJar
// Maven equivalent:
// mvn -Dspring.profiles.active=prod spring-boot:process-aot package
// --- RUN PHASE (ordinary JVM) ---
// java -Dspring.aot.enabled=true -jar build/libs/app.jar
// A bean whose EXISTENCE is frozen at build time:
@Configuration
class FeatureConfig {
@Bean
@ConditionalOnProperty(name = "billing.enabled", havingValue = "true")
BillingService billingService() { return new BillingService(); }
// ^ decided when processAot runs, NOT at java -jar time
}
// A value that is STILL read at runtime even in AOT mode:
@Component
class Endpoint {
Endpoint(@Value("${app.greeting}") String greeting) { /* resolved on boot */ }
}go deeper
Know the run command uses -Dspring.aot.enabled=true and that a build step produced the code.
Clearly separate the build-time processAot phase from the runtime flag, and know profiles are fixed at processing time.
Distinguish frozen conditions from still-runtime property values, and know processTestAot exists.
Design the build pipeline so AOT processing runs with production-representative configuration and gate releases on AOT-mode tests.
## Phase 1 — Build-time AOT processing This is where the generation happens. It is triggered by: - **Gradle**: the Spring Boot plugin registers a `processAot` task (and `processTestAot` for tests). It is wired automatically when the GraalVM native plugin is applied, but you can invoke it directly: `./gradlew processAot`. - **Maven**: `mvn spring-boot:process-aot` (goal `process-aot` on `spring-boot-maven-plugin`). What it does under the hood: it launches your application in a special mode (`SpringApplicationAotProcessor` / `ContextAotProcessor`) that **refreshes the context far enough to obtain all bean definitions but does not instantiate your singletons or run the app**. During this it: - runs `BeanFactoryPostProcessor`s (e.g. `ConfigurationClassPostProcessor`) so `@Configuration` classes and `@Bean` methods are parsed, - evaluates every `@Conditional`, `@Profile`, `@ConditionalOnProperty`, `@ConditionalOnClass`, etc., - emits generated Java sources (`*__ApplicationContextInitializer`, `*__BeanDefinitions`), CGLIB proxies, and `reflect-config.json`/`resource-config.json` hints. Those generated sources are compiled and packaged into the normal `bootJar`. ## Phase 2 — Runtime Run the resulting jar on any standard JVM: ``` java -Dspring.aot.enabled=true -jar app.jar ``` or via environment variable `SPRING_AOT_ENABLED=true`. `SpringApplication` sees the flag, finds the generated initializer, and builds the context from pre-computed definitions — skipping component scanning and most condition evaluation. Startup is measurably faster (often 20–40% depending on app size) with somewhat lower memory. ## The environment-parity gotcha Because Phase 1 evaluates conditions, the **build-time environment** decides the bean set. If a bean is guarded by `@Profile("prod")` or `@ConditionalOnProperty("feature.x.enabled")`, whether it exists is decided when `processAot` runs. To bake the production shape you must run AOT processing with the matching profile/properties active (e.g. `-Dspring.profiles.active=prod` passed to the processing run). This is different from a normal app where you decide profiles at launch. Note the important nuance: **property values injected via `@Value` or bound via `@ConfigurationProperties` are still read at runtime** — only the *conditions that decide which beans exist* are frozen. So `@Value("${server.port}")` still resolves from the runtime environment. ## Verifying it worked At startup, AOT mode is active when the generated initializer runs; you can confirm by enabling debug logging or checking that `spring.aot.enabled` is set and that the `*__ApplicationContextInitializer` class is present in the jar. If the flag is set but no generated class exists, Spring simply does not get the speedup. ## Tests `processTestAot` / `spring-boot:process-test-aot` generate AOT code for the test context so you can run the test suite in AOT mode and catch AOT-only failures before shipping.
- If you want prod-only beans in the AOT-optimized jar, what must you do?Run AOT processing (processAot / process-aot) with the prod profile/properties active, because condition evaluation happens at build time and freezes the bean set.
- Does AOT mode freeze @Value and @ConfigurationProperties values too?No. Only the conditions that decide which beans exist are frozen. Injected property values still resolve from the runtime Environment on startup.
saying these in an interview costs you the question
- Saying you choose the Spring profile at java -jar time and it changes the bean set
- Confusing processAot (build) with the spring.aot.enabled runtime flag
- Claiming property values are frozen into the binary