Configuration
Property sources
Spring Boot reads properties from many places. Later entries override earlier ones:
| Priority | Source |
|---|---|
| Lowest | SpringApplication.setDefaultProperties |
@PropertySource on a @Configuration class | |
Config data — application.properties / application.yaml | |
RandomValuePropertySource (random.*) | |
| OS environment variables | |
Java system properties (-D...) | |
SPRING_APPLICATION_JSON | |
| Highest | Command-line arguments (--key=value) |
Two consequences worth internalising:
- A command-line
--server.port=9000always beats anything in a file. This is the reliable way to override one value without editing configuration. - Environment variables use relaxed binding:
SPRING_DATASOURCE_URLmaps tospring.datasource.url. This is how configuration is normally injected in containers.
Config data locations
Within config data itself, Spring Boot searches these locations. Again, later wins:
1. classpath:/
2. classpath:/config/
3. file:./
4. file:./config/
5. file:./config/*/
So an application.properties sitting next to the jar overrides the one
packaged inside it — which is the whole point of the ordering.
spring.config.location
Replaces the default list entirely. Nothing in the defaults is read any more, so the file you point at must be complete.
--spring.config.location=file:/root/service/backend/application.properties
A location ending in / is treated as a directory and Spring Boot looks for
application.properties inside it; anything else is treated as a file. Use
optional: to tolerate a missing path:
--spring.config.location=optional:file:/etc/app/
spring.config.additional-location
Adds to the default list instead of replacing it. The additional locations
are searched before the defaults, and since later-searched locations take
precedence, the packaged application.properties still wins for any key both
files define.
That surprises people who expect an external file to override the bundled one.
If the external file must win, use spring.config.location, or move the value
to a command-line argument or environment variable.
Profiles
spring.profiles.active selects which profile-specific files load.
application-{profile}.properties overrides plain application.properties for
the keys it defines, regardless of which location each came from.
--spring.profiles.active=prod
--spring.profiles.active=dev,local # multiple; later wins
Profiles are not a location — they are a filename suffix and a bean-activation condition. A profile-specific file in a low-priority location still loses to a non-profile file in a higher-priority location.
Running a packaged jar
# Production: external config directory plus the prod profile
java -jar /root/service/backend/backend-0.0.1-SNAPSHOT.jar \
--spring.profiles.active=prod \
--spring.config.additional-location=/root/service/backend/
# Development on Windows (IDE run configuration arguments)
--spring.config.additional-location="C:/my/build/project/config/common/" --spring.profiles.active=dev
The trailing / matters: without it the path is read as a file name, and a
directory silently contributes nothing.
Forward slashes work on Windows and avoid the escaping problems that
C:\\my\\build\\ invites. If backslashes are unavoidable, keep the whole value
quoted.
Note the difference between the two argument kinds:
java -Dspring.profiles.active=prod -jar app.jar # JVM system property, before -jar
java -jar app.jar --spring.profiles.active=prod # application argument, after the jar
Both work, but anything after the jar name goes to the application, and anything
before it goes to the JVM. Putting --spring.profiles.active before -jar makes
the JVM treat it as an unrecognised option and refuse to start.
JVM time zone
java -Duser.timezone=UTC -jar app.jar
Set this explicitly on every server. Without it the JVM inherits the host time
zone, so the same build produces different timestamps in different environments,
and LocalDateTime values written to the database silently shift.
Setting the database session time zone as well is usually necessary:
spring.jpa.properties.hibernate.jdbc.time_zone=UTC
Store instants as Instant or OffsetDateTime rather than LocalDateTime
where the moment matters — LocalDateTime carries no zone and cannot survive a
zone change unambiguously.
Checking a port
Windows PowerShell:
Test-NetConnection 192.168.1.2 -Port 3389
tnc 192.168.1.2 -Port 3389 # tnc is the built-in alias
Linux:
ss -tlnp | grep 8080 # what is listening locally
nc -zv 192.168.1.2 3389 # can this host reach that port