Externalized configuration, precedence and profiles
Where a property's value really comes from — the precedence order, environment-variable names, typed @ConfigurationProperties, and how profiles layer on top.
After this lesson you can
- Predict which source wins when a property is set in several places
- Write the environment variable that overrides a given property
- Use profiles for both property files and conditional beans, and know which profile file wins
Spring Boot reads configuration from many property sources — config
files, environment variables, JVM system properties, command-line
arguments and more — and merges them into one Environment. When the
same key appears in several, the source with the higher precedence
wins. A trimmed version of the documented order, highest first:
- command-line arguments (
--server.port=9000) - Java system properties (
-Dserver.port=9000) - OS environment variables (
SERVER_PORT=9000) - profile-specific config files (
application-prod.properties) application.properties/application.yml
Within the config files, a file outside the packaged jar beats the one
inside it — even a profile file: an application.properties next to
the jar overrides an application-prod.properties packaged inside it.
The practical consequence is the deployment model Boot is
built for: ship one artifact with sensible defaults in
application.properties, and let each environment override what it
needs with environment variables or arguments.
Environment variable names
Most platforms do not allow dots or dashes in environment variable
names, so Boot's relaxed binding maps them: replace . with _,
drop -, and uppercase. spring.datasource.url is
SPRING_DATASOURCE_URL; app.max-retries is APP_MAXRETRIES.
import java.util.*; public class Solution { static String envName(String key) { return key.replace(".", "_").replace("-", "").toUpperCase(); } static String resolve(String key, Map<String, String> args, Map<String, String> env, Map<String, String> profileFile, Map<String, String> baseFile) { if (args.containsKey(key)) return args.get(key) + " (command line)"; if (env.containsKey(envName(key))) return env.get(envName(key)) + " (env " + envName(key) + ")"; if (profileFile.containsKey(key)) return profileFile.get(key) + " (application-prod)"; if (baseFile.containsKey(key)) return baseFile.get(key) + " (application)"; return "<unset>"; } public static List<String> run() { Map<String, String> base = Map.of( "server.port", "8080", "app.max-retries", "3", "app.greeting", "hello", "logging.level.root", "INFO"); Map<String, String> prod = Map.of("server.port", "8443", "logging.level.root", "WARN"); Map<String, String> env = Map.of("APP_MAXRETRIES", "5", "SERVER_PORT", "9090"); Map<String, String> args = Map.of("logging.level.root", "DEBUG"); List<String> out = new ArrayList<>(); for (String key : List.of("server.port", "app.max-retries", "app.greeting", "logging.level.root", "app.timeout")) { out.add(key + " = " + resolve(key, args, env, prod, base)); } return out; }}Reading configuration: prefer typed properties
@ConfigurationProperties(prefix = "app.payments")
@Validated
public record PaymentProperties(
@NotBlank String provider,
Duration timeout,
int maxRetries) {}
Registered with @EnableConfigurationProperties(PaymentProperties.class)
or @ConfigurationPropertiesScan, this binds app.payments.provider,
app.payments.timeout=5s (converted to a Duration) and
app.payments.max-retries in one place, validated at startup. A
Duration accepts a number with a unit (500ms, 30s, 2m, 1h,
1d); a bare number is read as milliseconds unless the field carries
@DurationUnit, so timeout=5 means five milliseconds, not seconds.
@Value("${app.payments.timeout:5s}") reads a single value with an
optional default after the colon; it is fine for one-off values but
scatters the configuration of a feature across many classes.
Profiles
A profile is a named slice of configuration. Activate profiles with
spring.profiles.active=prod,eu (or ). Two
things then change:
Exercise · Pro
Bind a timeout
Exercises are free in the first 3 lessons of every course. From here on they are part of Pro and Sprint; the lesson and its runnable examples stay open.
Upgrade