Configuration, Profiles & Actuator

Externalized Configuration in Boot

18 min Lesson 1 of 13

Externalized Configuration in Boot

One of Spring Boot's most important production features is externalized configuration: the ability to change how an application behaves without recompiling and redeploying it. Database URLs, API keys, feature flags, thread-pool sizes — none of these belong in your JAR. Spring Boot provides a rich, layered system of property sources so the same binary can run correctly in local development, CI, staging, and production simply by supplying different values at startup.

The Core Abstraction: PropertySource

At the heart of the system is org.springframework.core.env.PropertySource<T>. Every place a property can come from — a file, the OS environment, a command-line argument, a database — is modelled as a PropertySource. Spring's Environment holds an ordered list of these sources and answers property lookups by scanning from the highest-priority source downward, returning the first value it finds.

You rarely interact with PropertySource directly. You interact with its two main consumers:

  • @Value("${some.key}") — injects a single property into a field or constructor parameter.
  • Environment.getProperty("some.key") — programmatic lookup anywhere you have the Environment bean.

The Precedence Order (Most → Least)

Spring Boot defines 17 standard property sources. In practice, the ones you deal with daily are (from highest to lowest priority):

  1. Command-line arguments (--server.port=9090)
  2. SPRING_APPLICATION_JSON inline JSON in an env var or system property
  3. Servlet container init parameters (if deployed to an external container)
  4. OS environment variables (SPRING_DATASOURCE_URL=...)
  5. JVM system properties (-Dspring.datasource.url=...)
  6. Application properties outside the JARapplication-{profile}.properties and application.properties next to the JAR
  7. Application properties inside the JAR — packaged in src/main/resources
  8. @PropertySource annotations on @Configuration classes
  9. Default properties set via SpringApplication.setDefaultProperties
Key rule: A source higher in the list always wins. An OS environment variable with the same name as a property in application.properties will override it. This is the intended design — it lets operators override developer defaults at deploy time without touching source code.

application.properties — Your Day-to-Day File

The most common source is src/main/resources/application.properties. Spring Boot auto-discovers it. Any Spring or third-party key can go here:

# Server server.port=8080 server.servlet.context-path=/api # Datasource (values overridden in prod by env vars) spring.datasource.url=jdbc:postgresql://localhost:5432/shopdb spring.datasource.username=appuser spring.datasource.password=secret # JPA spring.jpa.show-sql=false spring.jpa.hibernate.ddl-auto=validate # Custom application key app.payments.gateway-url=https://sandbox.payments.example.com app.payments.timeout-seconds=10

Note the relaxed binding: Spring Boot normalises key names, so app.payments.timeout-seconds, APP_PAYMENTS_TIMEOUT_SECONDS (env var form), and app.payments.timeoutSeconds all resolve to the same property. This is what makes environment variables work seamlessly as overrides.

Injecting Values with @Value

import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component public class PaymentClient { private final String gatewayUrl; private final int timeoutSeconds; public PaymentClient( @Value("${app.payments.gateway-url}") String gatewayUrl, @Value("${app.payments.timeout-seconds:30}") int timeoutSeconds) { this.gatewayUrl = gatewayUrl; this.timeoutSeconds = timeoutSeconds; } }

The :30 suffix is a default value — if the property is absent, Spring uses 30 instead of throwing a startup error. Always provide a sensible default for optional properties and omit it for required ones so a missing config is caught immediately at startup.

Prefer constructor injection over field injection for @Value. Constructor-injected values are set before the bean is used, make dependencies explicit, and allow the class to be unit-tested without a Spring context by simply passing values directly.

Programmatic Lookup with Environment

When you need to read a property outside a managed bean, or when the key is only known at runtime, inject org.springframework.core.env.Environment:

import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; @Service public class FeatureFlagService { private final Environment env; public FeatureFlagService(Environment env) { this.env = env; } public boolean isEnabled(String flagName) { return env.getProperty("feature." + flagName, Boolean.class, false); } }

Overriding at Runtime — Practical Examples

Because command-line arguments and environment variables sit above the file, you can override any property without changing source code.

Command-line override (local testing):

java -jar shop-1.0.jar --server.port=9090 --spring.datasource.password=testpw

Environment variable override (Docker / Kubernetes):

# In a Docker run command or K8s env: block SPRING_DATASOURCE_URL=jdbc:postgresql://prod-db:5432/shopdb SPRING_DATASOURCE_PASSWORD=prodSecret SERVER_PORT=8080

Spring Boot maps environment variable names to property names by lowercasing and replacing underscores with dots: SPRING_DATASOURCE_URLspring.datasource.url. This is called relaxed binding and it works in both directions.

Properties Files Outside the JAR

When you place an application.properties (or application.yml) file in the same directory as the JAR — or in a config/ subdirectory — Spring Boot picks it up automatically and gives it higher priority than the packaged version. This is the traditional Unix-admin approach to per-server configuration:

/opt/apps/shop/ shop-1.0.jar config/ application.properties <-- overrides the packaged one
Never put passwords in version-controlled property files. The packaged application.properties should contain only safe defaults. Secrets must arrive via environment variables, an external config server, or a secrets manager (AWS Secrets Manager, HashiCorp Vault, Kubernetes Secrets) — all covered later in this tutorial.

How the Layers Work Together

In a typical production setup the layers complement each other:

  • Packaged application.properties — safe defaults that work in development (server.port=8080, spring.jpa.show-sql=true).
  • Profile-specific files (application-prod.properties) — production-safe non-secret overrides (spring.jpa.show-sql=false). Activated by the SPRING_PROFILES_ACTIVE=prod env var.
  • OS environment variables — secrets and infrastructure endpoints that ops injects at deploy time, never committed to git.
  • Command-line arguments — one-off overrides during local debugging or smoke tests.

Summary

Spring Boot's externalized configuration is built on an ordered list of PropertySource objects. Higher-priority sources (command-line, env vars) always win over lower-priority ones (packaged files). Your day-to-day workflow is: keep safe defaults in application.properties, override secrets and environment-specific values through OS environment variables or a config server, and use @Value or Environment to consume properties in beans. The next lesson moves from individual properties to binding entire groups into strongly-typed configuration classes with @ConfigurationProperties.