Careeroza
Environment Variables and Externalized Config
Never Commit Secrets
Passwords and API keys must not live in Git. Put them in environment variables (or a secret manager) and reference them, or let Spring bind them automatically. Spring's relaxed binding maps an environment variable to a property: uppercase, dots become underscores, dashes are removed.
| Property | Environment variable |
|---|---|
spring.datasource.password | SPRING_DATASOURCE_PASSWORD |
shopease.payment.key-secret | SHOPEASE_PAYMENT_KEYSECRET |
server.port | SERVER_PORT |
Who Wins? The Precedence Order
Spring Boot reads many sources. When a key appears in several, the higher source wins (simplified list, highest first):
- Command-line arguments:
--server.port=9090 SPRING_APPLICATION_JSON- OS environment variables:
SERVER_PORT=9090 - Profile files outside the jar, then inside:
application-prod.yml application.yml/application.propertiesoutside the jar, then inside- Defaults in code (
@Value("${x:default}"))
Example
A plain-Java model of that lookup, including the environment-variable name conversion:
import java.util.*;
public class Main {
static String toEnvName(String key) {
return key.replace(".", "_").replace("-", "").toUpperCase();
}
public static void main(String[] args) {
Map<String, Map<String, String>> sources = new LinkedHashMap<>(); // highest priority first
sources.put("command line", Map.of("server.port", "9090"));
sources.put("environment", Map.of("SERVER_PORT", "8081", "SHOPEASE_PAYMENT_KEYSECRET", "rzp_live_secret"));
sources.put("application-prod.yml", Map.of("spring.datasource.url", "jdbc:mysql://db:3306/shopease",
"shopease.payment.timeout", "3s"));
sources.put("application.yml", Map.of("server.port", "8080", "shopease.payment.timeout", "5s",
"spring.application.name", "shopease"));
for (String key : List.of("server.port", "shopease.payment.key-secret", "shopease.payment.timeout",
"spring.datasource.url", "spring.application.name", "shopease.payment.max-retries")) {
String found = "(not set, use default)";
for (var source : sources.entrySet()) {
String name = source.getKey().equals("environment") ? toEnvName(key) : key;
String value = source.getValue().get(name);
if (value != null) { found = value + " <- " + source.getKey() + (name.equals(key) ? "" : " (" + name + ")"); break; }
}
System.out.printf("%-30s = %s%n", key, found);
}
}
}Output
server.port = 9090 <- command line shopease.payment.key-secret = rzp_live_secret <- environment (SHOPEASE_PAYMENT_KEYSECRET) shopease.payment.timeout = 3s <- application-prod.yml spring.datasource.url = jdbc:mysql://db:3306/shopease <- application-prod.yml spring.application.name = shopease <- application.yml shopease.payment.max-retries = (not set, use default)
.env Files, Config Trees and Secret Managers
- Locally, keep secrets in a
.envfile that is in.gitignore; IntelliJ run configurations and Docker Compose (env_file) can load it. spring.config.import=optional:file:.env[.properties]imports a properties-style file directly.- In Kubernetes, secrets mounted as files are read with
spring.config.import=configtree:/run/secrets/. - Cloud secret managers (AWS Secrets Manager, GCP Secret Manager, Vault) have Spring Cloud integrations.
Exercise: Production must listen on port 9000 without changing any file inside the jar. Give two ways.
Show answer
# 1. command-line argument
java -jar shopease.jar --server.port=9000
# 2. environment variable
SERVER_PORT=9000 java -jar shopease.jar