Externalizing Configuration with Properties
Externalizing Configuration with Properties
Hard-coding configuration values — database URLs, API keys, timeouts, feature flags — directly into your Java classes is one of the most common mistakes in early Spring projects. The moment you need to run the same application against a staging database or rotate a secret, you are forced to recompile or redeploy. Spring's externalized configuration system eliminates this problem by keeping those values in .properties files that live outside compiled code, and injecting them into beans at startup.
This lesson covers the two core mechanisms: @PropertySource for explicit file loading, and the Environment API for programmatic access. Lesson 5 builds on top of this with @Value and SpEL for injecting values directly into fields and constructor parameters.
Why Externalize Configuration?
The twelve-factor app methodology mandates a strict separation between code and config. Practically this means:
- The same compiled JAR runs in every environment — development, CI, staging, production.
- Secrets never appear in version control.
- Changing a value (e.g., a connection pool size) requires no recompile and no new build artefact.
- Operations teams can tune behaviour without touching source code.
The @PropertySource Annotation
@PropertySource tells Spring to load a .properties file and register its key-value pairs in the application's Environment. You place it on a @Configuration class.
Suppose you have this file at src/main/resources/app.properties:
Load it into a configuration class:
@PropertySource is a Spring resource path. classpath: resolves from the JVM classpath (your JAR or resources/ folder). file:/etc/myapp/secrets.properties reads from the filesystem at runtime — useful for secrets injected by a container orchestrator. https:// is also supported but rarely used for properties.
Using the Environment API
org.springframework.core.env.Environment is Spring's unified abstraction over all property sources. Once a file is loaded via @PropertySource, its keys are available through this API from anywhere in the application context.
Key methods you need to know:
env.getProperty("key")— returns the value ornullif the key does not exist.env.getProperty("key", "defaultValue")— returns the value or a default string.env.getProperty("key", Integer.class)— returns the value converted to the requested type, ornull.env.getProperty("key", Integer.class, 10)— typed lookup with a default.env.getRequiredProperty("key")— throwsIllegalStateExceptionif the key is missing; the preferred choice for mandatory values.env.containsProperty("key")— boolean check, useful for optional feature flags.
Multiple Files and Stacking
You can load several files on a single class using @PropertySources (the container annotation) or the Java 8 repeating-annotation syntax:
When the same key exists in multiple sources, the last one registered wins. This makes it straightforward to ship sensible defaults and let a team-specific or environment-specific file override only what differs.
application.properties) in version control with non-sensitive defaults and placeholder comments. A companion file like application-local.properties (git-ignored) holds developer-specific overrides. In Spring Boot this is built in; in plain Spring you wire it manually with @PropertySource.
Ignoring Missing Files with ignoreResourceNotFound
By default, if the file named in @PropertySource does not exist, Spring throws an exception at context startup. You can relax this for optional files:
Use this for files that are optional — developer overrides, local secrets, test fixtures — so the application starts cleanly even when the file is absent.
Property Placeholders in the Values Themselves
Spring resolves ${placeholder} syntax inside property values, allowing you to compose values from other properties:
This works automatically once you register a PropertySourcesPlaceholderConfigurer bean (in plain Spring) or rely on Spring Boot's auto-configuration, which registers it for you.
${...} placeholders in @Value annotations and XML configuration are only resolved if a PropertySourcesPlaceholderConfigurer bean is present. Declare it as a static bean so it is processed before other beans:
@PropertySource with a Factory: Encoding and Custom Formats
By default Spring reads .properties files as ISO-8859-1. If your values contain UTF-8 characters (Arabic text, accented letters, CJK characters) you must declare the encoding explicitly:
For non-standard formats (YAML, TOML, encrypted property files), Spring allows a custom PropertySourceFactory:
Then reference it on the annotation:
application.yml is loaded automatically by YamlPropertySourceLoader — no custom factory needed. The custom factory pattern is primarily useful in plain Spring or when loading additional non-standard files alongside the main configuration.
Property Source Priority
Spring maintains a prioritised list of property sources. In Spring Boot the order (highest priority first) is roughly:
- Command-line arguments (
--server.port=9090) - OS environment variables (
SPRING_DATASOURCE_URL) - JVM system properties (
-Dspring.profiles.active=prod) - Profile-specific properties (
application-prod.properties) - Default properties (
application.properties) - Files added via
@PropertySource
Files you load with @PropertySource sit at the bottom of this stack. This is intentional: it means system environment variables and command-line arguments always override what is in your committed properties files, giving operators full control at runtime without touching source code.
Summary
@PropertySource is the explicit, annotation-driven way to load a .properties file into Spring's Environment. Once loaded, keys are accessible through Environment.getProperty() or getRequiredProperty(). You can stack multiple files, ignore missing optional files, compose values with ${} placeholders, and even plug in custom factories for non-standard formats. In the next lesson you will see how to skip the Environment API calls entirely and inject values straight into bean fields and constructor parameters using @Value.