Property sources
A property source is one place property values come from, under a name: a config file, the command line, the environment variables, a map of defaults.
Why
The environment holds no properties itself. It holds an ordered list of property sources and asks them in turn; for each key, the first source that has it wins. That order is how the command line overrides a config file, and a config file overrides the defaults.
An application rarely touches the list. It matters in two cases:
- to know which value wins when several places set the same key;
- to read properties from a place the framework does not know, such as a feature flag service or a secrets vault.
The order of sources
From the highest:
| # | Source | Name in the list |
|---|---|---|
| 1 | run(properties: ...), springBootTest(properties: ...) |
|
| 2 | Command-line arguments | commandLineArgs |
| 3 | --dart-define values |
dartDefines |
| 4 | SPRING_APPLICATION_JSON / spring.application.json |
spring.application.json |
| 5 | OS environment variables (in Flutter apps only with spring.flutter.scan-os-environment=true) |
systemEnvironment |
| 6 | ${random.*} values |
random |
| 7 | Config files: imported files above their importer, profile files above plain ones, file:./ above resources:/, .properties above .yml above .yaml, a later document above an earlier one |
one source per file and document |
| 8 | @PropertySource files |
the annotation's name, or the location |
| 9 | SpringApplication(defaultProperties: ...) |
defaultProperties |
A list property comes whole from the highest source that has it. Map entries are merged from every source, the higher source winning per key.
Command line
dart run bin/main.dart --shop.max-basket-size=30 --spring.profiles.active=prod --debug
--key=valuesetskey. Only the first=separates.--flagalone setsflagto an empty value.- A repeated option keeps all its values, joined with commas.
- A lone
--ends the options. Arguments that are not options are innonOptionArgs.
OS environment variables and --dart-define
SHOP_MAXBASKETSIZE=20 sets shop.max-basket-size. The name is split at _ and lower-cased, and dashes do not matter. A number part becomes an index: SHOP_HOSTS_0=eu sets shop.hosts[0]. --dart-define values use the same names.
A client app cannot keep secrets: --dart-define values and config files end up inside the APK, the IPA or main.dart.js.
Example: a source of your own
Extend AbstractPropertySource and add the source to the environment early, from an EnvironmentPostProcessor, so every bean sees its values.
import 'package:spring_boot_dart/spring_boot_dart.dart';
// Flags from a flag service; they override the config files.
class FeatureFlagSource extends AbstractPropertySource<FlagClient> {
FeatureFlagSource(FlagClient client) : super('featureFlags', client);
@override
Object? getProperty(String name) => source.valueOf(name);
}
class FeatureFlags implements EnvironmentPostProcessor {
@override
void postProcessEnvironment(
ConfigurableEnvironment environment,
SpringApplication application,
) {
final sources = environment.propertySources;
// Above the environment variables, below the command line.
sources.addBefore(
SystemEnvironmentPropertySource.defaultName,
FeatureFlagSource(FlagClient()),
);
// A plain map, below everything.
sources.addLast(MapPropertySource('fallbacks', {'shop.beta': 'false'}));
}
}
final app = SpringApplication(
[appBeans],
environmentPostProcessors: [FeatureFlags()],
);
When the source can list its keys, extend EnumerablePropertySource instead. @ConfigurationProperties binding then finds keys by prefix, which lists and maps need.
For a file that belongs to one part of the application, @PropertySource is simpler:
@Configuration()
@PropertySource([
'file:config/app.properties',
r'file:config/app-${env:local}.properties',
], ignoreResourceNotFound: true)
class AppConfig {}
Members
| Type | Member | Meaning |
|---|---|---|
AbstractPropertySource<T> |
name |
Unique in the list. Two sources with the same name are equal. |
source |
The object the values come from: a map, the arguments, a client. | |
getProperty(name) |
The raw value, or null when this source does not have it. |
|
containsProperty(name) |
Whether the source has the key. | |
EnumerablePropertySource<T> |
propertyNames |
Every key of the source. |
MapPropertySource |
MapPropertySource(name, map) |
A source backed by a Map<String, Object?>. |
CompositePropertySource |
addPropertySource, addFirstPropertySource |
Several sources under one name. |
MutablePropertySources |
addFirst(source) |
Wins over every other source. |
addLast(source) |
Every other source wins over it. | |
addBefore(name, source), addAfter(name, source) |
Next to a named source. Throw ArgumentError when the name is absent. |
|
get(name), containsSource(name) |
Find a source. | |
remove(name), replace(name, source) |
Take out, or swap in place. | |
precedenceOf(source) |
0 for the winning source, -1 when absent. |
Adding a source whose name is already present removes the old one first.
@PropertySource parameters:
| Name | Type | Default | Meaning |
|---|---|---|---|
value (positional) |
List<String> |
required | Resource locations (file:, resources:, patterns). May contain ${...}. The last location wins. |
name |
String? |
null |
The source's name. Derived from the location when null. |
ignoreResourceNotFound |
bool |
false |
Skip a missing file instead of failing the start. |
factory |
Type? |
null |
A PropertySourceFactory for another format, such as YAML. .properties when null. |
encoding |
String? |
null |
The charset of the files. UTF-8 when null. |
In Spring (Java)
class FeatureFlagSource extends PropertySource<FlagClient> {
FeatureFlagSource(FlagClient client) { super("featureFlags", client); }
@Override
public Object getProperty(String name) { return getSource().valueOf(name); }
}
environment.getPropertySources().addBefore(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
new FeatureFlagSource(client));
Differences that matter:
- The base class is
AbstractPropertySource, becausePropertySourceis the name of the annotation. - There are no JVM system properties.
--dart-definevalues take a place just below the command line. EnvironmentPostProcessors are passed toSpringApplication, not listed inMETA-INF/spring.factories.