Why does changing spring.profiles.active at runtime fail to swap beans in an AOT / native Spring Boot app?
answer
- @Profile == ProfileCondition == frozen at build
- runtime profile activation = property values yes, beans no
- set spring.profiles.active during processAot
- getActiveProfiles shows it but beans missing
- one image per environment/profile
basics
~20 sBecause @Profile is a condition. AOT evaluates profiles at build time and bakes in only the beans for the profiles active then. Activating a different profile at runtime cannot add beans that were never generated.
solid answer
~40 s`@Profile` is just another condition, so it is resolved during AOT processing using whatever profiles were active at build time. The generated ApplicationContextInitializer contains bean definitions only for those profiles; beans gated by a profile that was inactive at build time are simply not in the generated code. At runtime you can still *activate* a profile — the Environment will report it and profile-specific property files (application-{profile}.properties) still load for their **values** — but you cannot bring back profile-gated **beans**, because there is nothing to register. That is why Spring Boot warns you to specify the profiles that should be active during AOT processing. The practical rule: profile-driven bean selection becomes a build-time choice, often one native image per profile/environment.
code
kotlin · 13 lines// build.gradle.kts — choose the profile(s) baked into the AOT build
tasks.named<JavaExec>("processAot") {
args("--spring.profiles.active=cloud")
}
// At runtime, this profile-gated bean only exists if 'cloud'
// was active during the AOT build above.
@Configuration
class CloudConfig {
@Bean
@Profile("cloud")
fun cloudMetricsExporter() = CloudMetricsExporter()
}go deeper
Know that @Profile is a condition and AOT freezes it at build time.
Explain the split between frozen profile beans and still-loaded profile property files, and how to set the profile during processAot.
Discuss the getActiveProfiles-vs-missing-bean symptom and the build-warning, plus multi-profile combinations.
Move profile selection into the build/deploy pipeline (image per environment) or eliminate profile-gated beans in favor of runtime-valued configuration.
## @Profile is a condition `@Profile("cloud")` is implemented as a `@Conditional(ProfileCondition.class)`. Like every other condition, AOT evaluates it **once at build time** against the profiles that are active during the `processAot` run. Whatever wins is compiled into the generated bean definitions; the loser beans are **not generated at all**. ## Two different things called 'profiles' It helps to separate: 1. **Profile-gated beans** — `@Profile` / `@ConditionalOnExpression` on `@Bean`/`@Configuration`. These are **frozen** at build time. 2. **Profile-specific property files** — `application-{profile}.yml/properties`. These are still loaded at **runtime** based on the active profiles, and their **values** feed into `@Value`/`@ConfigurationProperties` of the *baked-in* beans. So activating `SPRING_PROFILES_ACTIVE=cloud` at runtime **can** change property *values* (if the `cloud` beans were also baked in, or the values apply to existing beans), but it **cannot** introduce beans that only exist under `@Profile("cloud")` unless `cloud` was active during the build. ## How to set profiles for the build - Command line: `-Dspring.profiles.active=cloud` when running `processAot`. - Spring Boot **Gradle** plugin: `tasks.named('processAot') { args('--spring.profiles.active=cloud') }` (or configure via the plugin's AOT support). - Spring Boot **Maven** plugin: the `spring-boot:process-aot` goal with a `profiles` configuration. Spring Boot emits a warning at build time reminding you that AOT-processed applications require profiles to be chosen at build time, precisely because runtime activation won't re-select beans. ## Gotchas - **Multiple profiles per environment**: if prod needs `cloud,metrics`, both must be active during the AOT build, or one image per combination. - **Test vs prod**: don't AOT-build with the `test` profile accidentally active — you'll bake in test beans. - **Detecting the freeze**: at runtime, `Environment.getActiveProfiles()` may show a profile you activated, yet its beans are missing — a confusing symptom that traces straight back to the build-time freeze. - **Not a native-only issue**: this reproduces on the JVM with `spring.aot.enabled=true`; native image just makes it unavoidable via the closed-world model. ## When to use / design guidance Treat profile selection as part of the **build/deploy** pipeline: build a distinct native image per environment, or use property-based (value) configuration rather than profile-gated beans for anything that must vary at runtime.
- A teammate says 'but Environment.getActiveProfiles() returns cloud at runtime, so profiles work' — how do you respond?Active profiles and profile-gated beans are different. The Environment tracks the runtime-activated profile (and loads its property files for values), but @Profile beans were selected at build time. Seeing the profile active while its beans are absent is exactly the frozen-condition symptom.
- How do you support three environments that need different profile-gated beans with native images?Build one native image per environment with the appropriate spring.profiles.active set during processAot, or refactor the differences into runtime property values consumed by always-present beans instead of @Profile-gated beans.
saying these in an interview costs you the question
- Claiming SPRING_PROFILES_ACTIVE at runtime swaps @Profile beans in native images
- Not distinguishing profile-gated beans (frozen) from profile property files (runtime values)
- Forgetting to set the profile during processAot and shipping the wrong bean set
- Assuming getActiveProfiles() proving the profile is active means its beans are present