Placeholders
A placeholder ${key} is replaced by the value of the property key; ${key:default} uses default when key is not set.
Why
A fact should be written once. The shop's URL is set in one place, and the API address is built from it. A value that differs per machine comes from an environment variable, with a default for the laptop. Placeholders do both, without code.
Syntax
| Form | Result |
|---|---|
${shop.name} |
The value of shop.name. Fails where the value is needed when no source has it. |
${shop.port:8080} |
8080 when shop.port is not set. |
${shop.motto:} |
An empty string when shop.motto is not set. |
${a:${b:c}} |
A default can contain placeholders. |
${shop.${region}.url} |
A key can contain placeholders. |
\${not.a.placeholder} |
A literal ${not.a.placeholder}. In .properties, escape the backslash: \\${. |
${random.int(10)}, ${random.uuid} |
A new random value on every read. |
Rules:
- A value that is found is resolved again, so values can refer to other values. A loop is detected and reported.
${db:url}is first looked up as the keydb:url, then asdbwith the defaulturl.- A
${without a closing}is kept as text. - Relaxed names apply:
${SHOP_URL}finds the environment variable, and${shop.url}findsSHOP_URLtoo.
Example
# lib/resources/application.yml
shop:
name: NEON Books
title: ${shop.name} (${spring.profiles.active:local})
url: ${SHOP_URL:http://localhost:8080}
api: ${shop.url}/api/v${shop.api-version:1}
instance-id: ${random.uuid}
@Service()
class ApiClient {
ApiClient(
// A raw string: in '${...}' Dart would interpolate.
@Value(r'${shop.api}') this.baseUrl,
@Value(r'${shop.timeout:5s}') this.timeout,
);
final String baseUrl;
final Duration timeout;
}
Where they are resolved
| Place | When a placeholder cannot be resolved |
|---|---|
| Values in config files and other sources, when read through the environment | Fails where the value is read |
@Value |
Fails the start, naming the key. A nullable target gets null. |
environment.getProperty, getPropertyAs |
Throws PlaceholderResolutionException |
environment.resolvePlaceholders(text) |
Stays as written |
environment.resolveRequiredPlaceholders(text) |
Throws PlaceholderResolutionException |
@ConfigurationProperties fields |
Kept as written. @RawValue() skips resolution for one field. |
@PropertySource locations |
Fails the start. With ignoreResourceNotFound: true the location is skipped. |
spring.config.import locations |
Fails the start. An optional: import is skipped. |
Starter annotations that take configurable text, such as @Scheduled(fixedDelayString: ...), the URL of a Feign client and request mapping paths |
Depends on the starter |
EmbeddedValueResolverAware beans |
The bean calls the resolver itself |
Placeholders are not SpEL. #{...} is evaluated only in @Value and @ConditionalOnExpression, never in config files. In @Value, placeholders are replaced first, so #{${shop.max-basket-size} * 2} works.
In Dart code, always write placeholders as raw strings: @Value(r'${shop.name}'). The dart_spring editor extension marks a plain string as an error.
Resolving your own text
PropertyPlaceholderHelper is the engine behind the environment. Use it to fill placeholders from something that is not the configuration, such as template variables:
final helper = PropertyPlaceholderHelper();
final values = {'host': 'localhost', 'port': '8080'};
helper.replacePlaceholders(
r'http://${host}:${port:80}/${path:}',
(key) => values[key],
); // 'http://localhost:8080/'
In Spring (Java)
@Value("${shop.api}") String baseUrl;
@Value("${shop.timeout:5s}") Duration timeout;
The syntax and rules are Spring's, including the Spring 6.2 lookup of ${db:url}. Differences that matter:
- In Dart, a placeholder in code must be in a raw string (
r'...'). ${random.*}returns at most 53-bit numbers on the web.