Table of Contents#
- Understanding @PropertySource in Spring
- What is
@PropertySource? - Basic Usage
- What is
- The Classpath Wildcard Conundrum: Why It Fails
- Common Scenario: Using Wildcards with
@PropertySource - The Root Cause:
@PropertySourceLimitations
- Common Scenario: Using Wildcards with
- Solutions to Read Multiple Properties Files with Wildcards
- Solution 1: Using
@PropertySourceswith Explicit Files - Solution 2: Programmatically Registering Property Sources
- Solution 3: Leveraging Spring Boot’s
spring.config.import - Solution 4: Custom
PropertySourceLocator
- Solution 1: Using
- Step-by-Step Example: Implementing the Programmatic Approach
- Project Setup
- Creating Multiple Property Files
- Java Config with Programmatic Registration
- Testing the Configuration
- Best Practices for Managing Multiple Property Files
- Organizing Property Files
- Avoiding Property Overrides
- Environment-Specific Configuration
- Conclusion
- References
Understanding @PropertySource in Spring#
What is @PropertySource?#
@PropertySource is a Spring annotation used to add property sources (e.g., .properties or .yml files) to the Spring Environment. It is typically used alongside @Configuration classes to externalize configuration, making properties accessible via @Value injections, the Environment object, or configuration properties (e.g., @ConfigurationProperties in Spring Boot).
Basic Usage#
To use @PropertySource, annotate a @Configuration class with the path to your property file:
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@PropertySource("classpath:app.properties") // Loads app.properties from classpath
public class AppConfig {
// Configuration beans here
}You can then access properties using @Value:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class MyComponent {
@Value("${app.name}") // Reads "app.name" from app.properties
private String appName;
// Getter/setter
}Or via the Environment object:
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
@Component
public class MyComponent {
private final Environment environment;
public MyComponent(Environment environment) {
this.environment = environment;
}
public String getAppVersion() {
return environment.getProperty("app.version"); // Reads "app.version"
}
}The Classpath Wildcard Conundrum: Why It Fails#
Common Scenario: Using Wildcards with @PropertySource#
Suppose you have multiple property files in your classpath (e.g., app1.properties, app2.properties, db.properties) and want to load all of them dynamically. You might try:
@Configuration
@PropertySource("classpath:*.properties") // Attempt to load all .properties files
public class AppConfig { ... }Problem: This does not work. Spring will not resolve the wildcard *.properties and will fail to load any properties files, resulting in IllegalArgumentException or unresolvable @Value dependencies.
The Root Cause: @PropertySource Limitations#
The core issue is that @PropertySource does not natively support wildcards for resolving multiple resource files. According to the Spring Javadoc, the value attribute expects a single resource location (e.g., classpath:app.properties), not a wildcard pattern.
While Spring’s Resource abstraction supports wildcards (e.g., classpath:*.properties), @PropertySource is designed to register a single property source, not multiple. Wildcards are only resolved if explicitly handled via a ResourceArrayPropertySource or programmatic registration.
Solutions to Read Multiple Properties Files with Wildcards#
Let’s explore four solutions to load multiple properties files using wildcards in Spring.
Solution 1: Using @PropertySources with Explicit Files#
If you know the exact names of your properties files upfront, use @PropertySources (plural) to list them explicitly:
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.PropertySources;
@Configuration
@PropertySources({
@PropertySource("classpath:app1.properties"),
@PropertySource("classpath:app2.properties"),
@PropertySource("classpath:db.properties")
})
public class AppConfig { ... }Pros: Simple and works with minimal code.
Cons: Not dynamic—you must update the annotation if new files are added.
Solution 2: Programmatically Registering Property Sources#
For dynamic wildcard resolution, register property sources programmatically in a @Configuration class. This involves:
- Using
ResourceLoaderto resolve all resources matching the wildcard pattern. - Adding these resources as
PropertySourceobjects to the SpringEnvironment.
Solution 3: Leveraging Spring Boot’s spring.config.import#
If you’re using Spring Boot, leverage its auto-configuration features. Spring Boot 2.4+ supports spring.config.import in application.properties/application.yml to import multiple files via wildcards:
# In application.properties
spring.config.import=classpath:*.propertiesThis tells Spring Boot to import all .properties files in the classpath root. For subdirectories (e.g., src/main/resources/config/), use:
spring.config.import=classpath:config/*.propertiesPros: Boot-specific but requires minimal configuration.
Cons: Only works with Spring Boot (not plain Spring Framework).
Solution 4: Custom PropertySourceLocator#
For advanced use cases (e.g., resolving properties from external systems or complex wildcard patterns), implement PropertySourceLocator and register it with Spring. This interface allows you to dynamically locate and add property sources to the environment.
Step-by-Step Example: Implementing the Programmatic Approach#
Let’s walk through the programmatic approach (Solution 2) to load multiple properties files with wildcards in a plain Spring application.
Project Setup#
Create a Maven/Gradle project with the following dependencies (Maven pom.xml example):
<dependencies>
<!-- Spring Core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.1.2</version> <!-- Use latest version -->
</dependency>
<!-- For testing -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>6.1.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>Creating Multiple Property Files#
Add two properties files to src/main/resources:
-
app1.properties:app.name=MyApplication app.version=1.0.0 -
db.properties:db.url=jdbc:mysql://localhost:3306/mydb db.username=root
Java Config with Programmatic Registration#
Create a @Configuration class to programmatically load all .properties files using a wildcard:
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePropertySource;
import javax.annotation.PostConstruct;
import java.io.IOException;
@Configuration
public class PropertyConfig {
private final ResourceLoader resourceLoader;
private final ConfigurableEnvironment environment;
// Inject ResourceLoader and Environment via constructor
public PropertyConfig(ResourceLoader resourceLoader, ConfigurableEnvironment environment) {
this.resourceLoader = resourceLoader;
this.environment = environment;
}
@PostConstruct // Executes after dependency injection
public void loadProperties() throws IOException {
// Resolve all .properties files in the classpath root
Resource[] resources = resourceLoader.getResources("classpath:*.properties");
MutablePropertySources propertySources = environment.getPropertySources();
// Add each properties file as a PropertySource
for (Resource resource : resources) {
if (resource.exists()) {
PropertySource<?> propertySource = new ResourcePropertySource(
resource.getFilename(), // Name the property source (e.g., "app1.properties")
resource
);
// Add to the environment (order matters: later sources override earlier ones)
propertySources.addLast(propertySource);
}
}
}
}Key Details:#
ResourceLoader: Resolves resources using Spring’s resource abstraction.resourceLoader.getResources("classpath:*.properties")fetches all.propertiesfiles in the classpath root.- Use
classpath*:*.properties(with a star afterclasspath) to search all classpath locations (including JARs).
- Use
ConfigurableEnvironment: Allows modification of property sources at runtime.ResourcePropertySource: Wraps aResource(e.g., a.propertiesfile) into aPropertySourcefor the environment.- Order of Property Sources:
addLast()adds the new property source with lower precedence (existing sources override it). UseaddFirst()for higher precedence.
Testing the Configuration#
Create a test to verify properties are loaded:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = PropertyConfig.class) // Load the config
public class PropertyConfigTest {
@Autowired
private Environment environment;
@Test
public void testPropertiesLoaded() {
// Verify properties from app1.properties
assertEquals("MyApplication", environment.getProperty("app.name"));
assertEquals("1.0.0", environment.getProperty("app.version"));
// Verify properties from db.properties
assertEquals("jdbc:mysql://localhost:3306/mydb", environment.getProperty("db.url"));
assertEquals("root", environment.getProperty("db.username"));
}
}Result: The test passes, confirming all properties from app1.properties and db.properties are loaded.
Best Practices for Managing Multiple Property Files#
1. Organize Property Files#
Store properties in subdirectories to avoid clutter. For example:
src/main/resources/
├── config/
│ ├── app/
│ │ ├── app1.properties
│ │ └── app2.properties
│ └── db/
│ └── db.properties
└── application.properties
Use classpath:config/**/*.properties (with ** for recursive subdirectories) to load all files:
Resource[] resources = resourceLoader.getResources("classpath:config/**/*.properties");2. Avoid Property Overrides#
If multiple files define the same property key, the last-added property source (via addLast()) will override earlier ones. To prevent unintended overrides:
- Use unique property keys (e.g.,
app1.name,app2.nameinstead ofapp.name). - Document property ownership (e.g., which file defines which keys).
3. Environment-Specific Configuration#
Use Spring profiles to load environment-specific properties (e.g., dev, test, prod). For example:
src/main/resources/
├── config/
│ ├── dev/
│ │ └── app-dev.properties
│ └── prod/
│ └── app-prod.properties
In the programmatic setup, load profile-specific files:
String profile = environment.getActiveProfiles()[0]; // Get active profile
Resource[] resources = resourceLoader.getResources("classpath:config/" + profile + "/*.properties");Conclusion#
While @PropertySource does not natively support classpath wildcards, you can dynamically load multiple properties files using:
@PropertySourcesfor explicit file lists,- Programmatic registration with
ResourceLoaderandEnvironment, - Spring Boot’s
spring.config.import(Boot-specific), or - Custom
PropertySourceLocatorfor advanced use cases.
The programmatic approach offers the most flexibility for plain Spring applications, while Spring Boot users can leverage spring.config.import for simplicity. By following best practices like organizing files and avoiding overrides, you can manage complex configurations efficiently across multiple applications.