Skip to main content

Configuration

Property sources

Spring Boot reads properties from many places. Later entries override earlier ones:

PrioritySource
LowestSpringApplication.setDefaultProperties
@PropertySource on a @Configuration class
Config data — application.properties / application.yaml
RandomValuePropertySource (random.*)
OS environment variables
Java system properties (-D...)
SPRING_APPLICATION_JSON
HighestCommand-line arguments (--key=value)

Two consequences worth internalising:

  • A command-line --server.port=9000 always beats anything in a file. This is the reliable way to override one value without editing configuration.
  • Environment variables use relaxed binding: SPRING_DATASOURCE_URL maps to spring.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