Property precedence and the Config Data API
Which value actually wins, and where it came from.
Open this lesson in the learning hubKey points
- Property sources are an ordered list, not a merge. The first source that has the key wins, and the order is documented and deterministic rather than incidental.
- From highest priority downwards: devtools settings, test annotations, command-line arguments,
SPRING_APPLICATION_JSON, servlet parameters, JNDI, system properties, environment variables, then profile-specific files, then plainapplication.yml, and finally defaults inside the jar. - That ordering is why
SPRING_DATASOURCE_PASSWORDbeats the value committed inapplication.yml- which is what makes secret injection work with no code change. - Relaxed binding maps
spring.datasource.maxPoolSize,max-pool-sizeandSPRING_DATASOURCE_MAXPOOLSIZEonto the same target, so an environment variable can set any property. spring.config.importreplaced the old bootstrap context. It pulls in another file, a Vault path or a Kubernetes ConfigMap as an ordinary property source, and theoptional:prefix stops a missing one failing startup.- When two sources disagree, do not guess:
/actuator/envreports the origin of every value - which file and which line it came from.
Example
spring:
config:
import:
- optional:file:./local.yml # developer override, absent in CI
- optional:configtree:/run/secrets/ # one file per key, as mounted by K8s
---
# Same key, four ways. Relaxed binding means all of these bind identically:
#
# application.yml spring.datasource.max-pool-size: 20
# system property -Dspring.datasource.maxPoolSize=20
# environment SPRING_DATASOURCE_MAXPOOLSIZE=20
# command line --spring.datasource.max-pool-size=20
#
# and the command line wins over the environment, which wins over the file.
---
# Binding a group of properties onto an immutable object. A record works
# directly - Boot 3 uses constructor binding when there is one constructor.
#
# @ConfigurationProperties("audit")
# @Validated
# record AuditProperties(
# @NotBlank String tableName,
# @DurationMin(seconds = 1) Duration flushInterval,
# List<String> ignoredUsers) { }
#
# Validation failures stop startup with the offending key named, rather than
# surfacing as a NullPointerException on the first request that needs it.
Precedence is a documented ordered list, not a merge - and /actuator/env tells you exactly which source supplied a value.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Spring Boot course, and every lesson in it is listed on the Spring Boot contents page.