How do you programmatically add or reorder PropertySources via MutablePropertySources, and at what point in the lifecycle should you do it?
answer
- getPropertySources() → MutablePropertySources
- addFirst = highest, addLast = lowest
- addBefore/addAfter/replace/remove by name
- register early: EnvironmentPostProcessor / ApplicationContextInitializer
- @PostConstruct/@Bean too late for @Value
basics
~10 sGet ConfigurableEnvironment.getPropertySources() (a MutablePropertySources) and use addFirst/addLast/addBefore/addAfter/replace/remove to control ordering. Do it early — in an ApplicationContextInitializer or EnvironmentPostProcessor — before beans read properties.
solid answer
~40 sMutablePropertySources is the mutable, ordered list behind the Environment. Via env.getPropertySources() you call addFirst(ps) (highest precedence), addLast(ps) (lowest), addBefore(name, ps) / addAfter(name, ps) to position relative to a named source, replace(name, ps), and remove(name). Each PropertySource has a unique name. Timing matters: to influence resolution you must register before anything reads the value. In Spring Boot the right hook is an EnvironmentPostProcessor (registered in spring.factories / AutoConfiguration.imports) which runs before the context is created; a generic ApplicationContextInitializer works too. Doing it from a regular @Bean or @PostConstruct is usually too late for @Value resolution, since placeholders in other beans may already be resolved. Common uses: injecting secrets from a vault, decrypting values, or forcing an override file above env vars with addFirst.
code
java · 12 lines// Spring Boot: inject a source BEFORE the context is built.
public class VaultEnvironmentPostProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment env, SpringApplication app) {
Map<String, Object> secrets = Map.of("db.password", fetchFromVault());
// addFirst => outranks system properties and OS env vars
env.getPropertySources().addFirst(new MapPropertySource("vault", secrets));
}
private String fetchFromVault() { /* ... */ return "s3cr3t"; }
}
// Register in META-INF/spring.factories:
// org.springframework.boot.env.EnvironmentPostProcessor=com.acme.VaultEnvironmentPostProcessorgo deeper
Aware that property sources can be added programmatically via the Environment, but likely fuzzy on ordering/timing.
Knows addFirst/addLast and that ordering controls precedence; may miss the lifecycle-timing requirement.
Explains the full MutablePropertySources API, the correct early hooks (EnvironmentPostProcessor / ApplicationContextInitializer), and why @PostConstruct is too late.
Designs secret-injection/decryption/override strategies with the right registration hook, considers name-collision and refresh-scope implications, and knows how test support injects inlined properties.
**MutablePropertySources — the API.** `ConfigurableEnvironment.getPropertySources()` returns a `MutablePropertySources`, the ordered, mutable list of `PropertySource` objects. Precedence == position; front == highest. Methods: - `addFirst(PropertySource)` — front, **highest** precedence (overrides everything, including env/system). - `addLast(PropertySource)` — back, **lowest** precedence (fallback defaults). - `addBefore(relativeName, ps)` / `addAfter(relativeName, ps)` — position relative to an existing named source (e.g. just above `systemEnvironment`). - `replace(name, ps)` — swap a source in place, keeping its position. - `remove(name)` — drop a source. - `contains(name)`, `get(name)`, `precedenceOf(ps)`, and it's `Iterable`. Every `PropertySource` has a **name**; the relative methods and de-dup logic key off it. **Building a PropertySource.** Common concretes: `new MapPropertySource("myOverrides", map)`, `new PropertiesPropertySource(name, properties)`, `new ResourcePropertySource("classpath:extra.properties")` (loads a file), `CommandLinePropertySource` subclasses, or a `CompositePropertySource` grouping several. **Timing — the crux.** Property resolution happens during bean creation (`@Value`, `${...}` in bean defs). To affect it, your source must be in the list **before** those reads: - **Spring Boot:** implement `org.springframework.boot.env.EnvironmentPostProcessor` and register it (in `META-INF/spring.factories` under that key, or `META-INF/spring/...AutoConfiguration.imports` for auto-config). It runs during `ApplicationEnvironmentPreparedEvent`, before the context/beans exist — the canonical place to inject vault secrets, decrypt values, or add an override file. - **Any Spring app:** an `ApplicationContextInitializer` gets the `ConfigurableApplicationContext` (hence its Environment) before `refresh()`. - **Too late:** mutating sources from a normal `@Bean`, `@PostConstruct`, or `ApplicationRunner`. The context is already refreshed and most `@Value`s resolved; changes won't retroactively re-inject. It *can* still help code that calls `env.getProperty(...)` lazily at runtime, but not eager injection. **Force-override example.** To make a file beat OS env vars: `env.getPropertySources().addFirst(new ResourcePropertySource("classpath:hard-override.properties"))` — because it's now at the front, its keys win over `systemProperties`/`systemEnvironment`. **Ordering pitfalls.** (1) `addLast` then expecting it to override env — no, that's the lowest slot. (2) Name collisions: adding a source whose name already exists via `addFirst/addLast` will `remove` the old one first (names are unique). (3) Relative add against a non-existent name throws `IllegalArgumentException`. **Read-side.** Once positioned, everything (`env.getProperty`, `${...}`, `@Value`) sees the new source through the same first-match-wins walk. Caching: `SystemEnvironmentPropertySource`/`PropertiesPropertySource` read the live map, so late programmatic mutation of the underlying map is visible on next lookup, but eager @Value injection already happened. **When to use.** Vault/secret injection, at-rest value decryption, environment-specific overrides that must outrank env vars, test fixtures (`@TestPropertySource`/`TestPropertySourceUtils.addInlinedPropertiesToEnvironment` do this under the hood), and multi-tenant/dynamic config.
- Why is adding a PropertySource from a @PostConstruct method usually ineffective for @Value injection?By the time @PostConstruct runs the context is refreshed and @Value placeholders on other beans are already resolved. You must register the source earlier — via EnvironmentPostProcessor (Boot) or ApplicationContextInitializer — before bean instantiation reads the values.
- You must make an override file win over OS environment variables. Which method and why?addFirst — it places the source at the front of MutablePropertySources, ahead of systemProperties/systemEnvironment, so first-match-wins picks it. addLast would put it at the bottom with lowest precedence.
saying these in an interview costs you the question
- Using addLast and expecting the source to override env/system properties
- Mutating property sources from a regular bean and expecting already-injected @Values to change
- Not giving sources a unique name / not understanding name-based dedup and relative adds
- Confusing ApplicationContextInitializer (has context) with EnvironmentPostProcessor (Boot, pre-context) timing