skip to content

How does Spring Boot bind sensitive values supplied via environment variables, and where do env vars and imported config trees sit in the property-source precedence order?

level: seniorimportance: should knowfreq 45%

answer

  1. SPRING_DATASOURCE_PASSWORD -> spring.datasource.password (uppercase, _ for . and -)
  2. cmdline > SPRING_APPLICATION_JSON > OS env > app.yml
  3. env vars override committed defaults
  4. importer-over-imported for config trees
  5. /actuator/env shows the winning source (redacted)

basics

~20 s

An env var like SPRING_DATASOURCE_PASSWORD maps via relaxed binding to spring.datasource.password. OS environment variables sit high in precedence — above application.properties/application.yml — so they override committed defaults, which is exactly what you want for secrets.

solid answer

~40 s

Boot's **relaxed binding** lets one canonical property come from many formats: `spring.datasource.password` can be supplied as `SPRING_DATASOURCE_PASSWORD` (uppercase, dots/dashes → underscores) — the standard, portable form for env vars. In the property-source precedence order, **OS environment variables rank above** `application.properties`/`application.yml` and profile-specific files, but below command-line args and `SPRING_APPLICATION_JSON`. So an env var overrides any committed default. Config-tree values imported via `spring.config.import` are config-data property sources whose precedence is governed by where the import sits and the importer-over-imported rule; they are typically used alongside, not instead of, env vars. The practical takeaway: commit non-secret defaults, inject secrets at runtime via env vars or config trees, and rely on precedence so the runtime value wins over the file.

code

java · 15 lines
java
// Committed application.yml holds only a non-secret default:
//   spring:
//     datasource:
//       password: CHANGE_ME   # overridden at runtime
//
// Runtime injects the real secret via env var (relaxed binding):
//   export SPRING_DATASOURCE_PASSWORD='s3cr3t'
// OS env outranks application.yml, so 's3cr3t' wins.

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("spring.datasource")
public record DataSourceProps(String url, String username, String password) {
    // password resolves to 's3cr3t' from SPRING_DATASOURCE_PASSWORD
}

go deeper

for a junior

Know SPRING_DATASOURCE_PASSWORD maps to spring.datasource.password and env vars beat committed files.

for a middle

State the relaxed-binding rule and that OS env ranks above application config files.

for a senior

Order the main property sources, place config-tree imports, and justify env/tree injection over committed secrets.

for a principal

Reason about the precedence contract across environments, the leakage tradeoffs of env vars vs trees, and observability via /actuator/env with sanitization.

## Relaxed binding for env vars Spring Boot binds **canonical, kebab/dotted** property names (`spring.datasource.password`) from many source formats. Environment variables can't contain dots, so Boot defines a deterministic mapping: **uppercase the name, and replace `.` and `-` with `_`**. Thus: - `spring.datasource.password` ← `SPRING_DATASOURCE_PASSWORD` - `app.api-key` ← `APP_API_KEY` - an index like `my.list[0]` ← `MY_LIST_0_` This is why 12-factor-style secret injection works cleanly: the platform sets `SPRING_DATASOURCE_PASSWORD`, and Boot's `@ConfigurationProperties`/`@Value` see `spring.datasource.password` with no code change. ## Precedence order (high → low, common subset) Boot merges many `PropertySource`s; **earlier = higher priority (wins)**: 1. Devtools global settings (dev only) 2. `@TestPropertySource` / test props 3. **Command-line arguments** (`--spring.datasource.password=…`) 4. `SPRING_APPLICATION_JSON` (inline JSON) 5. Servlet init params 6. JNDI (`java:comp/env`) 7. **OS environment variables** ← where `SPRING_*` secrets land 8. Java System properties (`-D…`) *(note: system props are actually just above OS env in Boot's list)* 9. Profile-specific `application-{profile}.properties/yml` 10. `application.properties/yml` 11. `@PropertySource` 12. Default properties (The exact adjacency of system properties vs OS env is a known ordering detail; both sit **above** the application config files, which is the point candidates must get right.) ## Where imported config trees fit `spring.config.import` locations are **config-data** sources resolved during Environment prep. Their relative order follows: import order, and the **importer-over-imported** rule (a value in the importing document beats the same value in an imported one). Config trees imported by `application.yml` therefore behave like additional low-to-mid-priority sources — good for *supplying* secrets, while an env var of the same key will still override them because OS env sits above the application config files. ## Why bind secrets from the environment/trees rather than committed files - **No secrets in git or image layers** — the repo holds only non-sensitive defaults. - **Per-environment values without rebuilds** — same artifact, different `SPRING_*` / mounted tree per stage. - **Precedence guarantees the runtime value wins** — a committed placeholder in `application.yml` is safely overridden by the injected secret. ## Gotchas - **Casing/underscore mistakes**: `SPRING.DATASOURCE.PASSWORD` won't bind; must be `SPRING_DATASOURCE_PASSWORD`. - **Env vars are process-global and leak** (`/proc/<pid>/environ`, child processes, crash dumps) — config-tree files are often safer for truly sensitive values. - **A committed default that is *higher* than you think**: nothing in `application.yml` outranks OS env, so you can't accidentally shadow an injected secret from the file — but you *can* shadow it with a command-line arg. - **Origin tracking**: `/actuator/env` shows which source each property came from (redacting sensitive keys per the sanitizer), useful for debugging precedence.

  • What environment-variable name binds to the property app.oauth.client-secret?
    APP_OAUTH_CLIENT_SECRET — uppercase everything and replace both the dots and the dash with underscores per relaxed binding.
  • Can a value in application.yml override a secret injected as an OS environment variable?
    No. OS environment variables rank above application.yml/properties, so the env var wins. Only higher sources like command-line args or SPRING_APPLICATION_JSON could override it.

saying these in an interview costs you the question

  • Saying application.properties overrides environment variables
  • Writing env var names with dots (SPRING.DATASOURCE.PASSWORD)
  • Believing @Value or @ConfigurationProperties needs special code to read env vars
  • Assuming env vars are as safe as mounted secret files (they leak more readily)

context