skip to content

What runtime dynamism does AOT give up, and what must be decided at build time?

level: seniorimportance: should knowfreq 30%

answer

  1. bean graph frozen at build time
  2. conditions & profiles evaluated once
  3. @ConditionalOnProperty baked in
  4. values still runtime, beans not
  5. missing RuntimeHints fail only in native

basics

~20 s

AOT freezes the bean graph at build time. Component scanning, @Conditional evaluation, and active profiles are all resolved during process-aot, so you can't add or switch beans by profile or condition at runtime. Missing reflection needs explicit RuntimeHints.

solid answer

~40 s

AOT trades runtime flexibility for speed. Because `ApplicationContextAotGenerator` bakes the bean factory at build time, several things become **fixed**: the set of bean definitions, the results of all `@Conditional`/auto-configuration evaluations, and the **active profiles** (evaluated when process-aot runs, not from runtime env). You therefore can't rely on switching profile-scoped beans at runtime, adding definitions dynamically, or having conditions re-evaluate against the runtime environment. Property *values* can still be injected at runtime, but which beans exist cannot change. Reflection, resource loading, and proxies that Spring can't infer must be declared via `RuntimeHints` (through `RuntimeHintsRegistrar`/`@ImportRuntimeHints` or `@RegisterReflectionForBinding`), or they fail — often only visible in the native image. This is why side-effect-free, statically analyzable configuration is essential for AOT-friendly apps.

go deeper

for a junior

Know the bean set is fixed at build time and reflection may need hints.

for a middle

List what's frozen (definitions, conditions, profiles) vs. what stays runtime (values).

for a senior

Explain the @ConditionalOnProperty/profile pitfalls and the JVM-passes/native-fails hint trap.

for a principal

Judge AOT suitability per workload and set org policy: profile-per-image, hint coverage as done-criteria, side-effect-free config.

## The core tradeoff Spring's normal runtime is **dynamic**: it discovers beans by scanning, decides which to create by evaluating `@Conditional`/`@Profile` against the *runtime* environment, and uses reflection liberally. AOT replaces that with **pre-computed, generated code**, so the flexibility that depends on runtime discovery is lost. ## What is frozen at build time 1. **Bean definitions.** The exact set of beans is captured during `refreshForAotProcessing`. You cannot add/remove definitions at runtime beyond what was generated. 2. **Conditions & auto-configuration.** Every `@ConditionalOnClass`, `@ConditionalOnProperty`, `@ConditionalOnMissingBean`, etc. is evaluated **once, at build time**. If a condition depended on a property, the property value **at build time** decides the outcome. (Note: `@ConditionalOnProperty` outcomes are therefore fixed — a frequent surprise.) 3. **Active profiles.** Profiles are resolved when `process-aot` runs. Beans guarded by `@Profile` are included/excluded based on the *build-time* active profiles, set via `spring.profiles.active` during the goal. Switching profiles at runtime **won't** re-introduce excluded beans. 4. **Proxy classes / reflection surface.** What can be reflected on is limited to what's declared in `RuntimeHints`. ## What still works at runtime - **Property/value injection**: `@Value`, `Environment`, `@ConfigurationProperties` still bind real values from the runtime environment — the *beans* are fixed, but their *configured values* are not (as long as binding was hinted). - **Ordinary business logic, HTTP, DB access** — unchanged. ## RuntimeHints and its failure mode Anything requiring reflection Spring can't statically infer — JSON (de)serialization of DTOs, JPA entities, dynamic proxies, loading a resource by name — needs a **hint**: - `RuntimeHintsRegistrar` + `@ImportRuntimeHints` - `@RegisterReflectionForBinding` for serialization types - `@ReflectiveScan` / `@Reflective` for reflective methods Missing hints usually **compile fine and even run on the JVM**, then fail with `ClassNotFoundException`/`NoSuchMethodException`/serialization errors **only inside the native image** — the classic AOT trap. Testing on the JVM with `spring.aot.enabled=true` plus the GraalVM reachability metadata repository mitigates this. ## Design implications - Keep configuration **statically analyzable**: avoid deciding bean existence via runtime-only signals. - Keep context init **side-effect-free** (process-aot actually refreshes the context). - Pin profiles explicitly in the build. - Treat hint coverage as part of the definition of done for AOT/native. ## When to accept the constraints AOT/native suits **fixed-shape** deployments (serverless, CLI, scale-to-zero) where startup/footprint matter more than runtime reconfiguration. Highly plugin-driven apps that reshape their bean graph at runtime are a poor fit.

  • Can @ConfigurationProperties still read runtime values under AOT?
    Yes — value binding happens at runtime, so property values are read from the live environment. What's fixed is the set of beans and any @Conditional decisions, not the injected values (assuming binding types are hinted).
  • Why do missing reflection hints often pass on the JVM but fail natively?
    The JVM can still reflect at runtime, so it papers over missing hints. GraalVM's closed-world native image only allows reflection that was declared as a RuntimeHint, so the gap surfaces as a runtime failure only there.
  • How would you handle a bean that must differ per environment under AOT?
    Decide it at build time via profiles pinned during process-aot (separate images per profile), or make it a single bean whose *behavior* is driven by runtime-injected properties rather than by bean existence.

saying these in an interview costs you the question

  • Claiming you can switch Spring profiles at runtime under AOT to change which beans exist.
  • Believing @ConditionalOnProperty is re-evaluated at runtime in an AOT/native app.
  • Assuming a JVM run with AOT disabled proves native correctness (missing hints won't show).
  • Thinking AOT freezes property values too (it freezes bean definitions, not injected values).

context