How does ${...} placeholder resolution work against the PropertySources, and what role does PropertySourcesPlaceholderConfigurer play?
answer
- ${key:default} default after colon
- resolves via ordered PropertySources, first match
- PropertySourcesPlaceholderConfigurer = BeanFactoryPostProcessor, must be static @Bean
- Boot auto-registers it; plain Spring you declare it
- unresolvable → throws unless ignoreUnresolvablePlaceholders
basics
~10 s${key} placeholders are resolved by looking key up in the Environment's ordered PropertySources. ${key:default} supplies a fallback. In plain Spring, a PropertySourcesPlaceholderConfigurer bean resolves these placeholders; Spring Boot registers one automatically.
solid answer
~40 sA ${key} placeholder is resolved by consulting the Environment's PropertySources in order (first match wins). ${key:default} provides a default if the key is absent, and placeholders can nest (${outer.${inner}}). Resolution is driven by a PropertySourcesPlaceholderConfigurer — a BeanFactoryPostProcessor that resolves ${...} in bean definitions and @Value against the Environment. Spring Boot auto-registers it (PropertyPlaceholderAutoConfiguration); in plain @Configuration you declare it as a static @Bean so it runs early enough. Placeholders also work inside @PropertySource's own location, e.g. @PropertySource("classpath:${region}.properties"), resolved against sources already registered. By default an unresolvable placeholder with no default throws; you can set ignoreUnresolvablePlaceholders. The Environment also resolves placeholders directly via env.resolvePlaceholders("...").
code
java · 15 lines@Configuration
@PropertySource("classpath:config-${env:dev}.properties") // placeholder in the location itself
public class AppConfig {
// In PLAIN Spring you must register this; must be static.
@Bean
static PropertySourcesPlaceholderConfigurer placeholders() {
var c = new PropertySourcesPlaceholderConfigurer();
c.setIgnoreUnresolvablePlaceholders(false); // fail fast on typos
return c;
}
}
// Programmatic resolution needs no configurer:
// String url = env.resolveRequiredPlaceholders("jdbc://${db.host}:${db.port:5432}/app");go deeper
Know ${key} reads a property and ${key:default} gives a fallback.
Explain that resolution goes through the ordered PropertySources and that a PropertySourcesPlaceholderConfigurer (auto in Boot) drives ${...} in @Value/bean defs.
Cover static @Bean requirement, ignoreUnresolvablePlaceholders, nesting, placeholders in @PropertySource locations, and env.resolvePlaceholders.
Weigh fail-fast vs ignore semantics for config safety, the legacy-configurer trap, and interaction with Boot's ConfigData/relaxed binding.
**What a placeholder is.** The `${...}` syntax is text substitution: `${db.url}` means "replace me with the value of property `db.url`". Resolution always goes through the Environment's ordered `PropertySources` (same first-match-wins lookup as `getProperty`). **Syntax features.** - **Default value:** `${db.port:5432}` → uses `5432` if `db.port` is undefined. The separator is `:`. - **Nesting:** `${db.${env}.url}` — the inner placeholder resolves first, then the outer. - **Escaping:** a literal `${` can be escaped as `\${` (via the placeholder prefix/escape handling in `PropertyPlaceholderHelper`). **Who does the resolving.** - `PropertySourcesPlaceholderConfigurer` is a `BeanFactoryPostProcessor` (`static`-registered) that, at container startup, resolves `${...}` in **bean definition metadata** and registers a `StringValueResolver` so `@Value("${...}")` resolves against the Environment. In **Spring Boot** it is auto-configured by `PropertyPlaceholderAutoConfiguration`, so you rarely declare it. In **plain @Configuration**, you must add it yourself, and it must be a **static** `@Bean` method (because BeanFactoryPostProcessors are instantiated very early, before regular bean configuration). - The **Environment itself** can resolve placeholders on demand via `environment.resolvePlaceholders("jdbc:${db.host}")` and `resolveRequiredPlaceholders(...)` — no configurer needed for programmatic use; the configurer is what wires it into `@Value` and XML/annotation bean definitions. (Note: how the *resolved* value is then bound/injected into a field via `@Value` or `@ConfigurationProperties` is the Value-Injection leaf's concern; here we care only about the ${...}→PropertySources resolution mechanism.) **Placeholders in @PropertySource locations.** `@PropertySource("classpath:config-${spring.profiles.active:dev}.properties")` is legal. The location is resolved **against sources already present** when that annotation is processed (system props, env, and earlier-processed @PropertySource files). You **cannot** reference a property defined by the *same* or a *later* @PropertySource — chicken-and-egg. **Unresolvable placeholders.** If `${x}` has no value and no default, the default configurer **throws** an `IllegalArgumentException` ("Could not resolve placeholder 'x'"). Set `setIgnoreUnresolvablePlaceholders(true)` to leave the raw `${x}` text in place instead — useful when a downstream tool substitutes it, dangerous if it hides real misconfiguration. **Legacy note.** `PropertyPlaceholderConfigurer` (no "Sources") is the older, pre-3.1 variant that does **not** consult the Environment's PropertySources. Always prefer `PropertySourcesPlaceholderConfigurer`. **Gotchas.** (1) In plain Spring, forgetting the configurer means `@Value("${x}")` injects the literal string `${x}` (or fails) — Boot users forget it's auto-provided. (2) The configurer must be **static**, else you get a warning that it won't post-process early beans. (3) `:` is both the default separator and legal in values — nest/escape carefully. (4) Placeholder location in @PropertySource can't self-reference.
- In a plain (non-Boot) @Configuration app, @Value("${app.name}") injects the literal string "${app.name}". Why?No PropertySourcesPlaceholderConfigurer is registered, so nothing resolves ${...} in @Value. Add a static @Bean PropertySourcesPlaceholderConfigurer. Boot provides one automatically, which is why the problem only shows up in plain Spring.
- What happens to ${db.port} if it isn't defined anywhere and has no default?The configurer throws IllegalArgumentException ('Could not resolve placeholder db.port') at startup — fail-fast — unless ignoreUnresolvablePlaceholders is true, in which case the raw ${db.port} text is left in.
- Why must the PropertySourcesPlaceholderConfigurer @Bean method be static?It's a BeanFactoryPostProcessor, instantiated very early before the enclosing @Configuration is fully processed. A non-static method would force early instantiation of the config class and log a warning that the BFPP won't be applied to all beans.
saying these in an interview costs you the question
- Thinking @Value/${...} always works even in plain Spring without a configurer
- Using the old PropertyPlaceholderConfigurer (ignores PropertySources) instead of PropertySourcesPlaceholderConfigurer
- Declaring the configurer as a non-static @Bean
- Assuming an unresolved placeholder silently becomes null/empty (it throws by default)
- Believing a @PropertySource location can reference a key defined by that same @PropertySource