Property precedence and the Config Data API

Spring Boot · lesson 36 of 39 · 6 min read

Which value actually wins, and where it came from.

Open this lesson in the learning hub

Key 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 plain application.yml, and finally defaults inside the jar.
  • That ordering is why SPRING_DATASOURCE_PASSWORD beats the value committed in application.yml - which is what makes secret injection work with no code change.
  • Relaxed binding maps spring.datasource.maxPoolSize, max-pool-size and SPRING_DATASOURCE_MAXPOOLSIZE onto the same target, so an environment variable can set any property.
  • spring.config.import replaced the old bootstrap context. It pulls in another file, a Vault path or a Kubernetes ConfigMap as an ordinary property source, and the optional: prefix stops a missing one failing startup.
  • When two sources disagree, do not guess: /actuator/env reports 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.