We are currently using Spring Boot 3.3 and attempting to upgrade to Spring Boot 3.4. We have come across an issue that is the Spring core project that is introduced in version 6.2 where our existing code no longer works. The changes are in:
https://github.com/spring-projects/spring-framework/blob/6.2.x/spring-core/src/main/java/org/springframework/util/PropertyPlaceholderHelper.java and https://github.com/spring-projects/spring-framework/blob/6.2.x/spring-core/src/main/java/org/springframework/util/PlaceholderParser.java
We have a custom class that extends PropertySource to handle our own implementation of properties:
import org.springframework.core.env.PropertySource;
public class CustomPropertySource extends PropertySource<CustomPropertyProvider> {
@Override
public Object getProperty(String propertyName) {
//our code here
}
}
And a property in application.yaml that looks like this:
example-properties:
property1: ${customproperty:this-is-a-custom-property}
With Spring Boot 3.3 (and Spring Framework 6.1.x), we can debug into our CustomPropertySource.getProperty() method and see that “customproperty:this-is-a-custom-property" is passed in the propertyName parameter.
With Spring Boot 3.4 (and Spring Framework 6.2.x), we can debug into our CustomPropertySource.getProperty() method and see that “customproperty" is passed in the propertyName parameter, which causes our code to fail.
One solution to this issue is to modify PlaceHolderParser. SimplePlaceholderPart.resolve() method to change from:
@Override
public String resolve(PartResolutionContext resolutionContext) {
String value = resolveRecursively(resolutionContext);
if (value != null) {
return value;
}
else if (this.fallback != null) {
return this.fallback;
}
return resolutionContext.handleUnresolvablePlaceholder(this.key, text());
}
to:
@Override
public String resolve(PartResolutionContext resolutionContext) {
String value = resolveRecursively(resolutionContext, this.key);
if (value != null) {
return value;
} else {
value = resolveRecursively(resolutionContext, this.text());
}
if (value != null) {
return value;
} else if (this.fallback != null) {
return this.fallback;
}
return resolutionContext.handleUnresolvablePlaceholder(this.key, text());
}
So that if the key does not find the right value, the entire text value of the property will be sent in a second call to the class that extends PropertySource.
Comment From: snicoll
Thanks for the report.
Why do you expect « customproperty:this-is-a-custom-property » to be queried. This has a separator character there so the key is « custom property ».
Comment From: johnf703506
We are using all of the contents of "customproperty:this-is-a-custom-property" to determine what the value should be. So our code in public Object getProperty(String propertyName) checks to see if the property name starts with customproperty and if it does, it uses the data after the colon. Is there another way to get to the data that is after the key?
Comment From: snicoll
The colon is a reserved character so you shouldn't be using that for that use case. It worked thus far by accident, see https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-6.2-Release-Notes#property-placeholder-resolution.