Externalized Configuration
Externalized configuration is what lets one Spring Boot artifact run unchanged across every environment. Settings live outside the compiled code — in YAML, environment variables, command-line arguments, or an imported config server — and Spring merges them from many sources in a well-defined precedence order. Understanding that order is the key to predictable configuration.
Why externalize
The goal is a single immutable JAR or container image that you promote from dev to staging to production. Defaults ship inside the artifact; each environment overrides only what differs. Nothing environment-specific or secret is baked into the build.
Property-source precedence
Spring Boot loads many sources and lets higher-priority ones override lower ones. The list below runs from highest priority (wins) to lowest (most easily overridden). This is the abbreviated, most-used view of Spring Boot’s full ordering.
| Priority | Source |
|---|---|
| Highest | Devtools global settings (~/.config/spring-boot) |
@TestPropertySource / test properties | |
Command-line arguments (--server.port=9090) | |
SPRING_APPLICATION_JSON (inline JSON) | |
ServletConfig / ServletContext init params | |
JNDI attributes (java:comp/env) | |
Java System properties (-Dserver.port=9090) | |
OS environment variables (SERVER_PORT=9090) | |
Profile-specific application-<profile>.yml (outside jar) | |
Profile-specific application-<profile>.yml (inside jar) | |
Base application.yml (outside jar) | |
| Lowest | Base application.yml (inside jar), @PropertySource, defaults |
This precedence is exactly what makes the same JAR portable: ship defaults in YAML, then override per environment with env vars or command-line args.
Command-line arguments
Any property can be passed as a --key=value argument. These sit near the top of the precedence list, so they override almost everything.
java -jar app.jar --server.port=9090 --spring.profiles.active=prod
SPRING_APPLICATION_JSON lets you supply a whole tree as inline JSON, useful in CI and PaaS environments:
java -jar app.jar \
--spring.application.json='{"app":{"name":"Shop","mail":{"port":587}}}'
OS environment variables
Spring’s relaxed binding maps environment variables to property keys: uppercase the key and replace dots and dashes with underscores. So spring.datasource.url is satisfied by SPRING_DATASOURCE_URL.
export SERVER_PORT=9090
export SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/shop
export APP_MAIL_MAX_ATTEMPTS=5 # binds to app.mail.maxAttempts
java -jar app.jar
This convention is the backbone of containerized and cloud deployments, where connection strings and credentials arrive as variables rather than files.
Tip: Environment-variable binding works for both
@Valueplaceholders and @ConfigurationProperties. For grouped settings, prefer the typed approach.
Importing additional config
spring.config.import pulls in extra configuration sources during startup. It can load extra files, directories, or — through Spring Cloud — a config server or secrets manager.
# application.yml
spring:
config:
import:
- optional:file:./config/local.yml # optional: won't fail if missing
- optional:configtree:/etc/secrets/ # one file per key (Kubernetes Secrets)
The configtree: resource reads a directory where each file’s name is a property key and its contents are the value — the standard layout for Kubernetes Secrets and Docker secrets mounted at /run/secrets/.
.env-style files
There is no automatic .env support, but you can import a properties file explicitly:
spring:
config:
import: optional:file:.env[.properties]
The [.properties] extension hint tells Spring how to parse a file whose name lacks a recognized suffix.
Handling secrets
Never commit passwords or API keys to application.yml. Keep only safe defaults in version control and supply secrets at runtime.
# application.yml — placeholder only, real value injected at runtime
spring:
datasource:
username: ${DB_USER}
password: ${DB_PASSWORD}
| Approach | Where secrets live | Best for |
|---|---|---|
| Environment variables | Process environment | Simple deployments, 12-factor apps |
configtree: import | Mounted secret files | Kubernetes / Docker secrets |
| Config server | Spring Cloud Config / Vault | Many services, central rotation |
| Cloud secrets manager | AWS / GCP / Azure | Managed cloud platforms |
Warning: Anything in
application.ymlinside the JAR is shipped to every environment and visible to anyone with the artifact. Treat it as public, and externalize every credential.
Configuration in containers
In a Docker or Kubernetes deployment, the image is immutable and configuration is injected at run time.
FROM eclipse-temurin:17-jre
COPY target/app.jar /app/app.jar
ENTRYPOINT ["java","-jar","/app/app.jar"]
docker run \
-e SPRING_PROFILES_ACTIVE=prod \
-e SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/shop \
-e DB_USER=shop -e DB_PASSWORD=$DB_PASSWORD \
myorg/shop:1.4.0
For Kubernetes, mount a ConfigMap as files and a Secret as a config tree, then reference them via spring.config.import. This keeps the same image identical across clusters.
Best Practices
- Ship safe defaults in
application.yml; override per environment with env vars or command-line args. - Understand the precedence order so you always know which source wins.
- Externalize every secret — use env vars, config trees, or a secrets manager, never committed YAML.
- Use
spring.config.importwithoptional:so a missing local override does not break startup. - Bind grouped settings with @ConfigurationProperties for type safety.
- Keep the artifact immutable; the same image should run in every environment.