During AOT processing, how far does Spring take the ApplicationContext, and what code runs at build time versus at runtime under spring.aot.enabled?
answer
- partial refresh: BFPPs run, singletons don't
- ConfigurationClassPostProcessor at build time
- no @PostConstruct / runners at processing
- runtime: generated initializer then normal refresh
- proxyBeanMethods=false is AOT-friendly
basics
~20 sAt 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.
solid answer
~40 sAOT processing (`ContextAotProcessor`/`SpringApplicationAotProcessor`) does a **partial refresh**: it runs `BeanFactoryPostProcessor`s — especially `ConfigurationClassPostProcessor`, which performs component scanning, parses `@Configuration`/`@Bean`, and evaluates conditions — so the `BeanDefinitionRegistry` is fully populated. It stops **before** instantiating your singleton beans, so your bean constructors and `BeanPostProcessor`s do **not** run at build time. It then emits the generated `ApplicationContextInitializer`, per-bean definition suppliers, ahead-of-time CGLIB proxies, and reflection/resource hints. At runtime with `spring.aot.enabled=true`, `SpringApplication` invokes the generated initializer to register the pre-computed definitions, then the context refresh proceeds normally from there: singletons are instantiated, `@PostConstruct`/`InitializingBean` run, `ApplicationListener`s fire, the web server starts. So build time captures *structure*; runtime does the actual *instantiation and lifecycle*.
code
java · 23 lines// A BeanFactoryPostProcessor runs at BUILD time during processAot.
// Anything environment-specific here executes on the build machine!
@Component
class MyRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// Runs during AOT processing -> keep it deterministic, no live DB/HTTP.
}
@Override public void postProcessBeanFactory(ConfigurableListableBeanFactory bf) { }
}
// A bean constructor does NOT run at build time — only at runtime refresh.
@Service
class OrderService {
OrderService(Repo repo) {
// Not invoked during processAot; runs when the AOT jar actually starts.
}
@PostConstruct void init() { /* fires at runtime only */ }
}
// AOT-friendly configuration: no CGLIB proxy generated.
@Configuration(proxyBeanMethods = false)
class AppConfig { @Bean Repo repo() { return new Repo(); } }go deeper
Know build time generates definitions and runtime instantiates beans.
State that BeanFactoryPostProcessors run at build while singleton instantiation and lifecycle happen at runtime.
Explain the partial-refresh boundary precisely and the side-effect risk of custom post-processors executing during processAot.
Guide teams to keep definition-time logic deterministic and env-independent, prefer proxyBeanMethods=false, and validate hint completeness under AOT tests.
## The build-time partial refresh Spring's AOT engine (`ContextAotProcessor`, driven by `SpringApplicationAotProcessor` for a Boot app) needs to know every bean definition without actually starting your application. It does this by performing a refresh that includes the **`BeanDefinitionRegistryPostProcessor`/`BeanFactoryPostProcessor` phase** but not singleton instantiation. What therefore **runs at build time**: - `ConfigurationClassPostProcessor` — component scanning, `@Import` resolution, `@Bean` method registration, condition evaluation (`@Conditional`, `@Profile`, `@ConditionalOnXxx`). - Any custom `BeanFactoryPostProcessor` / `BeanDefinitionRegistryPostProcessor` you define — these execute during processing. If one of these does expensive or environment-specific work, be aware it runs at build. - `BeanRegistrationAotProcessor` / `BeanFactoryInitializationAotProcessor` — the AOT-specific SPIs that contribute generated code and hints. What does **NOT** run at build time: - Your singleton **bean constructors** and factory `@Bean` method bodies are not invoked to create instances (the engine reasons about definitions, not instances). - `BeanPostProcessor` callbacks, `@PostConstruct`, `InitializingBean.afterPropertiesSet`, `SmartInitializingSingleton`, `ApplicationRunner`/`CommandLineRunner` — none of these fire; the app is never truly started. - The web server is not started. ## The generated artifacts From the captured registry the engine emits: - `MyApplication__ApplicationContextInitializer` — registers all bean definitions programmatically. - `SomeBean__BeanDefinitions` — supplier methods creating each `RootBeanDefinition` with pre-resolved constructor/factory metadata (so reflection to find constructors is avoided). - Ahead-of-time **CGLIB proxies** for `@Configuration(proxyBeanMethods=true)` classes and other proxy needs. - Runtime **hints** files (`reflect-config.json`, `resource-config.json`, `proxy-config.json`) — chiefly consumed by native image but produced regardless. ## Runtime under `spring.aot.enabled=true` `SpringApplication` detects AOT mode and, instead of adding the reflective config-processing machinery, applies the generated `ApplicationContextInitializer`. Concretely: 1. The generated initializer registers the frozen bean definitions into the `GenericApplicationContext`. 2. The context proceeds with a **normal refresh from that point**: singletons are instantiated (now their constructors DO run), dependency injection happens, `BeanPostProcessor`s and lifecycle callbacks fire, listeners publish `ContextRefreshedEvent`, and the web server starts. So the runtime behavior of your beans is unchanged — same lifecycle, same JIT, same throughput. Only the *discovery/definition* work was pre-computed. ## Practical implications and gotchas - **Side effects in `BeanFactoryPostProcessor`s run at build time.** If a custom post-processor reads a database or a live service to decide bean definitions, that happens during `processAot`, which is usually undesirable. Keep post-processors deterministic and environment-independent. - **Constructors must be well-behaved for metadata extraction.** Ambiguous constructors or reflection-heavy definition logic can produce incomplete hints; test in AOT mode. - **`@Configuration(proxyBeanMethods=false)`** is preferred for AOT-friendliness because it avoids generating CGLIB proxies; Spring Boot's own auto-config uses this. - **Startup ordering is identical at runtime** — you have not changed *when* singletons initialize, only removed the scan/condition cost that preceded it.
- Does @PostConstruct run during AOT processing?No. AOT processing stops before singleton instantiation, so constructors, @PostConstruct, InitializingBean, and runners only fire at runtime when the AOT jar actually starts.
- Why is @Configuration(proxyBeanMethods=false) considered AOT-friendly?It avoids the need for a CGLIB proxy of the config class, reducing generated proxy code and reflection hints; inter-@Bean calls just become plain method calls.
saying these in an interview costs you the question
- Thinking the whole app starts (singletons, runners) during AOT processing
- Assuming BeanFactoryPostProcessors do NOT run at build time
- Believing AOT changes when your beans initialize at runtime