Spring Boot environment variables and profiles
Spring Boot ranks OS environment variables above application.properties and maps them with relaxed binding. Profiles, config trees and Crunu explained.
Part of the guide
Spring BootAt a glance
- Env file that wins
application-{profile}.propertiesFirst of 2 .env files, highest priority first. See the load order
- Public prefix
- None
No names are copied into browser code by a prefix.
- Values applied
- At runtime
Values are read from the environment when the app runs.
- Built-in secret store
- None: config trees can read secrets mounted as files
docs.spring.io/spring-boot/reference/features/external-config.html
- Crunu command
crunu run --env orders/development --inherit-env -- ./mvnw spring-boot:runRecipeCrunu has no dedicated integration here: this is the general setup.
Common pitfalls
- OS environment variables beat application.properties, but Java system properties, SPRING_APPLICATION_JSON and command-line arguments beat them.
- Relaxed binding removes dashes: spring.main.log-startup-info becomes SPRING_MAIN_LOGSTARTUPINFO, with no underscore where the dash was.
- Map keys bound from environment variables are lowercased, so MY_PROPS_VALUES_KEY becomes the key key, not KEY.
- Spring Boot reads no .env file. A value that only exists in .env never reaches the Environment unless you import the file yourself.
- JAVA_HOME is not in crunu run's default allowlist, so Maven and Gradle wrappers may need --inherit-env or JAVA_HOME in the environment.
On this page
Spring Boot collects configuration from many sources into one Environment and lets later sources override earlier ones. OS environment variables rank above every application.properties and application-{profile}.properties file, and are matched to property names by relaxed binding: spring.datasource.password is read from SPRING_DATASOURCE_PASSWORD. Java system properties, SPRING_APPLICATION_JSON and command-line arguments rank above environment variables. Spring Boot doesn't read .env files. Values are bound when the application context starts. This page follows the Spring Boot externalized configuration reference for Spring Boot 4.1.2, checked October 30, 2026.
Where environment variables rank
The reference lists fifteen property sources from lowest to highest priority. The ones that matter for secrets:
| Rank (low to high) | Source |
|---|---|
| 1 | default properties set in code |
| 2 | @PropertySource annotations |
| 3 | config data: application.properties, YAML and profile-specific files |
| 4 | random.* values |
| 5 | OS environment variables |
| 6 | Java system properties, -Dname=value |
| 7 | JNDI attributes |
| 8, 9 | servlet init parameters |
| 10 | SPRING_APPLICATION_JSON |
| 11 | command-line arguments, --name=value |
| 12 to 15 | test annotations and devtools global settings |
Within config data, profile-specific files override plain ones, and files outside the jar override files packaged inside it. If .properties and YAML sit in the same place, .properties wins. None of this changes the main point: a real environment variable beats every file, and a --spring.datasource.password=… argument beats the environment variable. That last rule surprises people when an old start script still passes a password on the command line, where it's also visible in the process list.
Relaxed binding: from property name to variable name
Environment variables can't contain dots, so Spring Boot converts names. The documented rules:
- Replace dots with underscores.
- Remove dashes.
- Use upper case.
| Property | Environment variable |
|---|---|
spring.datasource.password | SPRING_DATASOURCE_PASSWORD |
spring.main.log-startup-info | SPRING_MAIN_LOGSTARTUPINFO |
my.service[0].other | MY_SERVICE_0_OTHER |
The dash rule is where most mistakes happen. SPRING_MAIN_LOG_STARTUP_INFO looks right and doesn't bind. For lists, surround the index with underscores. For maps, remember that Spring Boot lowercases the variable name before binding, so MY_PROPS_VALUES_KEY=VALUE produces the map entry key with the value VALUE. Only the name is lowercased.
Always refer to properties in their canonical kebab-case form in placeholders. ${demo.item-price} picks up DEMO_ITEMPRICE from the environment, and so does ${demo.itemPrice}, but the reference notes the two forms aren't equivalent for file-based sources.
Profiles
Profiles select extra config files. spring.profiles.active=prod loads application-prod.properties on top of application.properties. As an environment variable it's SPRING_PROFILES_ACTIVE. With several profiles, such as prod,live, the last one wins where they overlap. Without an active profile, the default profile applies.
Profiles are good for non-secret differences, like connection pool sizes or feature flags per stage. Secrets are better kept out of profile files entirely, because those files usually live in the repository or inside the jar. Read secrets from environment variables, which outrank the files anyway.
Other ways to bring secrets in
- Config trees.
spring.config.import=optional:configtree:/run/secrets/turns each file in that directory into a property named after the file. That's how Kubernetes and Docker mount secrets, and values can bind toStringorbyte[]. SPRING_APPLICATION_JSON. A whole JSON document in one variable. It ranks above ordinary environment variables, and itsnullvalues can't override lower sources.- An environment prefix.
SpringApplication.setEnvironmentPrefix("orders")makes the app readORDERS_REMOTE_TIMEOUTinstead ofREMOTE_TIMEOUT, which helps when several Spring Boot apps share one environment. The prefix applies only to environment variables.
Moving secrets out of properties files
Most Spring Boot projects start with a password or two typed straight into application.properties or application-prod.yml. Moving them out is mostly deletion, because environment variables already outrank the files:
- Find every secret in config data. Search all
application*.propertiesand YAML files, including ones undersrc/test, for passwords, tokens, keys and connection strings with credentials in them. - Pick a name for each. Either the relaxed binding name, such as
SPRING_DATASOURCE_PASSWORD, or a shorter name of your own referenced by a placeholder:spring.datasource.password=${DB_PASSWORD}. Placeholders resolve against the wholeEnvironment, so the variable fills them. - Delete the value from the file. Leave the property out, or leave the placeholder. A missing variable then fails at startup instead of silently using an old password.
- Reissue what was committed. A value that sat in a file in Git is in the history of every clone. Moving it doesn't make it secret again; replacing it at the provider does.
Non-secret settings can stay in the files, where they're reviewed with the code.
Run Spring Boot with values from Crunu
crunu run decrypts a Crunu environment in memory and starts your command with those variables in its process environment. Spring Boot sees them as OS environment variables, rank 5, above every properties file. No .env support is needed, and the names simply follow the relaxed binding rules.
- Store values under their environment names, such as
SPRING_DATASOURCE_URL,SPRING_DATASOURCE_PASSWORDandSPRING_PROFILES_ACTIVE, in Crunu environments likeorders/developmentandorders/production. - Develop with
crunu run --env orders/development --inherit-env -- ./mvnw spring-boot:run, or the Gradle equivalent./gradlew bootRun. - Build the jar in CI without secrets, since Spring Boot binds properties at startup rather than at build time.
- On a Linux server with the agent, start
crunu run --env orders/production --restart on-change -- java -jar /opt/orders/orders.jar.
Why --inherit-env in step 2? By default crunu run gives the program a short allowlist from your shell, such as PATH, HOME and LANG, plus the Crunu variables. JAVA_HOME, MAVEN_OPTS and GRADLE_USER_HOME are not on it, and the Maven and Gradle wrappers may depend on them on your laptop. --inherit-env passes your whole shell. On a server, prefer the clean environment and put JAVA_HOME or JVM options into the production environment explicitly, so the service doesn't depend on whoever's shell started it.
With --restart on-change, crunu sends SIGTERM after a value changes, waits up to 30 seconds for the JVM to shut down, and starts it again. If the application uses graceful shutdown, in-flight requests can finish within that window. Make sure shutdown completes in time, or raise --stop-timeout.
Two habits to drop when you move: passing secrets as --spring... arguments, which outrank the environment and show up in the process list, and keeping a local application-local.properties with real passwords, which tends to get committed sooner or later.
Related reading
- Go for a runtime with no configuration layers at all.
- Docker Compose for config trees from Compose secrets.
- Render and Railway for platform-held variables.
- How to share .env files with your team and crunu run.
Frequently asked questions
Something else? Write to support@crunu.com
Does an environment variable override application.properties in Spring Boot?
Yes. OS environment variables rank above all config data files, including profile-specific ones inside and outside the jar. Java system properties, SPRING_APPLICATION_JSON and command-line arguments rank higher still.
How do I set spring.datasource.password with an environment variable?
Replace dots with underscores, remove dashes and use upper case: SPRING_DATASOURCE_PASSWORD.
How do I choose a profile with an environment variable?
Set SPRING_PROFILES_ACTIVE, the environment form of spring.profiles.active. With several profiles, the last one wins where they define the same property.
What is a config tree?
A directory where each file name is a property name and each file's content is its value, as Kubernetes and Docker mount secrets. Import it with spring.config.import and the configtree prefix, for example for /run/secrets.
Sources
Every source this page relies on, with the date we last checked it.
- Spring Boot: Externalized configuration · checked October 30, 2026