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:

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

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:

See also