skip to content

How can you activate or change profiles programmatically, and why is timing critical?

level: seniorimportance: should knowfreq 38%

answer

  1. ConfigurableEnvironment = mutable Environment
  2. setActiveProfiles replaces / addActiveProfiles appends
  3. must run before refresh()
  4. @Profile evaluated during registration
  5. no live profile hot-swap

basics

~10 s

Use ConfigurableEnvironment (from context.getEnvironment()): setActiveProfiles(...) or addActiveProfiles(...). This must happen before the context is refreshed, because @Profile is evaluated during bean registration. After refresh it's too late.

solid answer

~40 s

`Environment` is read-only; `ConfigurableEnvironment` adds mutators: `setActiveProfiles(String...)` (replaces the set), `addActiveProfiles(String)` (appends one), and `setDefaultProfiles(...)`. You reach it before startup, e.g. `new SpringApplicationBuilder(App.class).profiles("prod").run()`, `SpringApplication.setAdditionalProfiles(...)`, or on a raw context: `ctx.getEnvironment().setActiveProfiles("prod")` then `ctx.refresh()`. **Timing is the whole story**: `@Profile` is a `@Conditional` evaluated while bean definitions are being registered, which happens *during* `refresh()`. If you mutate profiles after the context is refreshed, existing beans are already decided and nothing re-registers — the change is effectively inert for `@Profile`. So programmatic activation must occur pre-refresh, or via `spring.profiles.active`/env before the context builds. There is no supported way to 'reload' a running context's beans by flipping a profile.

code

java · 10 lines
java
public static void main(String[] args) {
    var ctx = new AnnotationConfigApplicationContext();
    ConfigurableEnvironment env = ctx.getEnvironment();
    env.setActiveProfiles("prod", "cloud");   // BEFORE refresh — honored
    ctx.register(AppConfig.class);
    ctx.refresh();                             // @Profile evaluated here

    env.addActiveProfiles("debug");            // AFTER refresh — inert for @Profile
    // getActiveProfiles() now shows debug, but no new beans registered
}

go deeper

for a junior

May only know the property-based activation, not programmatic.

for a middle

Know ConfigurableEnvironment.setActiveProfiles exists and is set before startup.

for a senior

Explain the pre-refresh timing constraint tied to @Conditional evaluation, and replace-vs-append semantics.

for a principal

Discuss initializer/EnvironmentPostProcessor hooks, why profiles are a startup concern not a runtime toggle, and steering teams toward feature flags for runtime behavior changes.

## Environment vs ConfigurableEnvironment `org.springframework.core.env.Environment` exposes reads. Its subinterface **`ConfigurableEnvironment`** adds mutation: - `void setActiveProfiles(String... profiles)` — **replace** the active set. - `void addActiveProfiles(String profile)` — **append** one profile. - `void setDefaultProfiles(String... profiles)` — change the fallback set. - `MutablePropertySources getPropertySources()` — add/remove/reorder property sources. - `getSystemProperties()`, `getSystemEnvironment()`, `merge(...)`. Every `ApplicationContext` returns a `ConfigurableEnvironment` from `getEnvironment()` (the return type on `ConfigurableApplicationContext` is already `ConfigurableEnvironment`). ## Ways to activate profiles programmatically 1. **Raw context (rare, non-Boot):** ```java var ctx = new AnnotationConfigApplicationContext(); ctx.getEnvironment().setActiveProfiles("prod", "cloud"); ctx.register(AppConfig.class); ctx.refresh(); // registration + @Profile evaluation happens here ``` 2. **Spring Boot builder:** `new SpringApplicationBuilder(App.class).profiles("prod").run(args);` 3. **SpringApplication:** `app.setAdditionalProfiles("prod");` before `run` — *adds* without erasing config-file profiles. 4. **ApplicationContextInitializer / EnvironmentPostProcessor** — mutate the environment very early, before refresh. 5. Externally (preferred): `spring.profiles.active`, `SPRING_PROFILES_ACTIVE`, `-Dspring.profiles.active=...`. ## Why timing is critical `@Profile` is meta-annotated `@Conditional(ProfileCondition.class)`. Conditions are evaluated by the `ConfigurationClassPostProcessor` / bean-definition registration phase, which runs **inside `AbstractApplicationContext.refresh()`**. Consequences: - Mutating `setActiveProfiles` **before** `refresh()` → honored. - Mutating **after** `refresh()` → the profile set changes in the `Environment`, and `getActiveProfiles()` will report it, but **no beans are re-registered or removed**. `@Profile` gating already happened. So it's effectively a no-op for wiring. - There is no built-in 'hot swap' of profiles for a live context. To change profiles you rebuild/restart the context. ## setActiveProfiles vs addActiveProfiles vs setAdditionalProfiles - `setActiveProfiles` **overwrites** — dangerous if you wanted to keep file-configured profiles. - `addActiveProfiles`/Boot's `setAdditionalProfiles` **augment** the existing set. ## Interaction with spring.profiles.active If both a property and programmatic call set profiles, the last write before refresh wins for that environment; Boot's `setAdditionalProfiles` merges rather than overrides the property-driven ones. ## Gotchas - Calling `setActiveProfiles` inside a `@Bean` method or a running bean is too late. - Empty varargs `setActiveProfiles()` clears active profiles (reverting to defaults). - Property sources added via `getPropertySources()` after refresh will be seen by later `getProperty` calls but won't retroactively affect already-resolved `@Value` injections.

  • You call setActiveProfiles after refresh() — will a @Profile("debug") bean appear?
    No. @Profile is evaluated during context refresh/bean registration. After refresh the beans are already decided; the environment reports the new profile but no bean definitions are re-processed.
  • Difference between SpringApplication.setAdditionalProfiles and Environment.setActiveProfiles?
    setAdditionalProfiles augments the profiles resolved from configuration (merges), while setActiveProfiles on the environment overwrites the active set entirely. Additional is safer when you want to keep file/env-driven profiles.
  • How would you actually switch environments at runtime?
    You don't flip a profile on a live context. You restart with a different spring.profiles.active, or design runtime-configurable behavior with regular beans/feature flags rather than @Profile.

saying these in an interview costs you the question

  • Thinking setActiveProfiles after refresh re-wires beans
  • Believing you can hot-swap profiles on a running context
  • Confusing addActiveProfiles (append) with setActiveProfiles (replace)
  • Assuming @Profile is evaluated lazily at bean access time

context