skip to content

During AOT processing, how far does Spring take the ApplicationContext, and what code runs at build time versus at runtime under spring.aot.enabled?

level: seniorimportance: should knowfreq 24%

answer

  1. partial refresh: BFPPs run, singletons don't
  2. ConfigurationClassPostProcessor at build time
  3. no @PostConstruct / runners at processing
  4. runtime: generated initializer then normal refresh
  5. proxyBeanMethods=false is AOT-friendly

basics

~20 s

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

AOT 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
java
// 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

for a junior

Know build time generates definitions and runtime instantiates beans.

for a middle

State that BeanFactoryPostProcessors run at build while singleton instantiation and lifecycle happen at runtime.

for a senior

Explain the partial-refresh boundary precisely and the side-effect risk of custom post-processors executing during processAot.

for a principal

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

context