I’ve seen quite a lot of Spring projects over the years that use multiple PropertyPlaceholderConfigurer
instances in the same bean factory.
Consider a project where maven module A depends on maven module B in the same maven project. The idea is that module A is self-sustaining and loads its own property files, and module B does as well. This philosophy is good, but can lead to a horrible mess when used in combination with default values.
The problem setup
You have a Spring project with two PropertyPlaceholderConfigurer
instances, each loading their own property file, and one Spring Bean that has a @Value
injection with a default value.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@Configuration @ComponentScan class AppConfiguration { @Bean PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() { PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer(); propertyPlaceholderConfigurer.setLocation(new ClassPathResource("application.properties")); return propertyPlaceholderConfigurer; } @Bean PropertySourcesPlaceholderConfigurer anotherPropertyPlaceholderConfigurer() { PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer(); propertyPlaceholderConfigurer.setLocation(new ClassPathResource("application1.properties")); return propertyPlaceholderConfigurer; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@Component public class MyBean { private String myKey; @Autowired public MyBean(@Value("${myKey:defaultValue}") String myKey) { this.myKey = myKey; } public String getMyKey() { return myKey; } } |
Furthermore, you have the two property files where the first property file is missing the property and the second property file actually has it.
application.properties
1 |
#myKey=value1 |
application1.properties
1 |
myKey=value2 |
The symptoms
The bean receives the default value "defaultValue"
, although property placeholder 2 actually has a value “value2” for this property.
The cause
PropertyPlaceholderConfigurer
instances don’t know about each other’s existence. What happened here is:
- Property placeholder 1 tries to resolve the value of
myKey
. It doesn’t have a value for that, but “fortunately” the bean specifies a default value, so it uses that. - Property placeholder 2 sees that the bean already has received a value (set by Property placeholder 1), so it ignores it.
Using ignoreUnresovablePlaceholders
doesn’t help because the placeholder configurers will still use the default value if encountered on a bean.
To make matters even worse, Spring shows undeterministic behaviour here, because it uses unordered hashmaps internally that result in property placeholder 2 sometimes being fired before property placeholder 1. The Spring Bean then actually receives value “value2” which is what we would expect. I’ve seen two log files where starting the Spring container a second time reverses the order in which the property placeholder configurers are loaded.
I guess that propertyplaceholder configurers not knowing about each other is by design, but the above can be a big gotcha.
I’ve posted a sample project on GitHub that reproduces the problem. If you run the unit test multiple times it will inevitably fail at some point.
The solution alternatives
- Only use one
PropertyPlaceholderConfigurer
for your deployable unit (a WAR, for example), at the top level. I would recommend this solution first. - Do not use defaults in your
@Value
statements. Instead, explicitly define all property values once and use this in combination withignoreUnresovablePlaceholders
on all property placeholder configurers. I consider this a brittle solution at best.
The Spring issue tracker describes the problem here. As of Spring 4.3.5 it is still unresolved.