How do you make Spring fail fast when using placeholders with #ConfiguraitonProperties and an environment variable is not set? - spring

When using placeholders to externalise configuration in an application.yaml file, and an associated properties class, how do you make sure Spring fails during startup when it can't resolve a placeholder, instead of just using the placeholder itself as the verbatim value?
For example, given this application.yaml file:
example.key: ${MY_ENV_VAR}
and this properties POJO:
#ConfigurationProperties(prefix="example")
public class AcmeProperties {
public String key;
// Getters, setters, constructors omitted...
}
if MY_ENV_VAR is not set on the system, how do you make Spring throw an exception at startup, instead of setting key to literally ${MY_ENV_VAR}?
Note, Spring doesn't return an empty String, which we could force by defining a default with ${MY_ENV_VAR:defaultValue}, or null (SpEL is not evaluated in #ConfigurationProperties, so this can not be defined as the default, and the behaviour of System.getenv("MY_ENV_VAR") returning null in the case of an undefined environment variable isn't mirrored), it literally just uses the placeholder as the value. We'd rather Spring stopped launching the app altogether instead, for all properties in AcmeProperties. Just using #Validated with Hibernate Validator on the classpath doesn't do it, neither does #ConfigurationProperties(prefix="example", ignoreInvalidFields=false) (which is the default anyway).
A manual check for the value containing ${...} could of course be added to all String properties in the POJO, but this is more error-prone, and would also not work if the actual environment variable was set to a string containing that character sequence. Is there a way to check if the placeholder can be resolved, instead of if the value after resolution is complete?

You can have a custom validator which could look like:
object UnresolvedPropertiesValidator : Validator {
override fun supports(clazz: Class<*>): Boolean {
return clazz == String::class.java
}
override fun validate(target: Any, errors: Errors) {
val stringVal = target as String
if (stringVal.startsWith(SystemPropertyUtils.PLACEHOLDER_PREFIX) && stringVal.endsWith(SystemPropertyUtils.PLACEHOLDER_SUFFIX)) {
errors.reject(
"prop.validation",
"Could not resolve placeholder $target"
)
}
}
}
And then create a bean with the name configurationPropertiesValidator:
class UnresolvedPropertiesValidatorAutoConfiguration {
#Bean
fun configurationPropertiesValidator(): UnresolvedPropertiesValidator {
return UnresolvedPropertiesValidator
}
}
As you can see from these https://github.com/spring-projects/spring-boot/blob/24a52aa66ddb92cd14acb2b41d9f55b957a44829/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/EnableConfigurationProperties.java#L47
https://github.com/spring-projects/spring-boot/blob/9630f853be3183f4872428e2e65b6ef4be7a9b7a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java#L105,
the validator with the name above will be applied after resolving the prop, and it simply ensures the final value doesn't start with ${ and end with }.
This works for us. But hopefully, the issue will be resolved soon, and we will remove this workaround.

Related

Spring calls noargs constructor on java record for #ConfigurationProperties

I'm pulling my hair out here. I want to use a Java record for my #ConfigurationProperties, providing default values to unspecified config properties. Here is a very simple example:
#ConfigurationProperties(prefix = "myconfig")
public record LoggingProperties (
String whatever,
String somethingToDefault
) {
public LoggingProperties(String whatever, String somethingToDefault) {
this.whatever = whatever;
this.somethingToDefault = somethingToDefault;
}
public LoggingProperties(String whatever) {
this(whatever, "whatever was specified, but not somethingToDefault");
}
public LoggingProperties() {
this("neither was specified", "neither was specified");
}
}
It seems, if I declare a noargs constructor, spring always calls that, regardless of what I actually have in my config file (application.yml)
The above will yield an instance, that when logged shows:
LoggingProperties[whatever=neither was specified, somethingToDefault=neither was specified], despite the fact that my config WAS specified.
If I delete the no-args constructor, I get an exception about No default constructor found;
If I add #ConstructorBinding to the allargs constructor I get:
LoggingProperties[whatever=value from file, somethingToDefault=null]. i.e. it just called the annotated constructor, ignoring the one with 1 arg (despite that prop being declared in my yml).
I'm at a loss... Is this even possible?
EDIT: in my application.yml I have:
myconfig:
whatever: "value from file"
Praise the lord for documentation (it pays to read it)
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.typesafe-configuration-properties.constructor-binding
Default values can be specified using #DefaultValue on a constructor parameter or, when using Java 16 or later, a record component. The conversion service will be applied to coerce the String value to the target type of a missing property.
So it seems I can skip the constructor mess, and just annotate the record fields with a #DefaultValue(value = "whatever default"), like so:
#ConfigurationProperties(prefix = "someprefix")
#ConstructorBinding
public record MyRecord (
#DefaultValue(value = "true")
boolean someProperty,
) {}

Using #Value annotation with Spring and SPeL

I am trying to find a way to do the following in my spring boot 1.5 application.
I have a variable who's value is dynamic meaning it comes in from an external system.
String name = "abc"; //gets set externally
I want to try and use the name's value to lookup my property file and see if there is a matching property defined. something like..
#Value("#{myClassName.name.concat('something')}")
String propertyValue;
Now my application.property file has the following property set
assume name has the value "abc"
property file contents:
abc.something:abcValue
Now, when i try to access the value of the variable propertyValue it gets set to the value abc.something and not abcValue.
I probably think I cannot use #Value with #{} to get to that, I was wondering if there was a way to to use #{} inside ${} so that I goes and fetches the property value after calculating the name of the property using #{}.
Let me know if you need more details please.
A bean life-cycle requires properties to be resolved at compile time. So, #Value requires constant parameter.
You can use Environment bean to access your properties programmatically.
import org.springframework.core.env.Environment;
#Service
public class Serivce {
#Autowired
private Environment environment;
public String getProperty(final String keyPart) {
String key = "build.your." + keyPart;
return environment.getProperty(key)
}
}
By the way you can use #('${spring.some.property}') in SpEL to access placeholder.
// This is valid access to property
#Value("#('${spring.some.property}')")
private String property;

Override Property value in PropertyPlaceholderConfigurer

I need to override a property value given in my property file while loading my JBOSS Application server.
I tried out with below code overriding processProperties() method in PropertyPlaceholderConfigurer.
My property file has this entry
base.url="defaultUrl"
public class CustomPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
protected String convertPropertyValue(String originalValue) {
return (originalValue != null) ? originalValue.trim() : originalValue;
}
#Override
protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props)
throws BeansException {
super.processProperties(beanFactory, props);
for (Enumeration names = props.propertyNames(); names.hasMoreElements();) {
String key = (String) names.nextElement();
props.put("base.url", getUpdatedUrl());
}
}
}
I am injecting base.url value in a placeholder ${base.url} in application context.
How should I update the value of given property in run time. The above code always take the value in the property file not the updated value.
Blowing the dust off from this question. This can be done using PropertyPlaceholderConfigurer and adding a new property file (at the end of the list), where you put the properties you want to override. (The name/file path of the property file can contain an environment variable you pass at build time). Here's the javadoc of PropertiesLoaderSupport#setLocations:
Note: Properties defined in later files will override properties
defined earlier files, in case of overlapping keys. Hence, make sure
that the most specific files are the last ones in the given list of
locations.
As of Spring 5.2 this was deprecated in favor of PropertySourcesPlaceholderConfigurer:
Specialization of PlaceholderConfigurerSupport that resolves ${...}
placeholders within bean definition property values and #Value
annotations against the current Spring Environment and its set of
PropertySources.
Some examples here

Spring Boot - Detect and terminate if property not set?

Is there any way for a Spring Boot web application to abort at startup if a required property is not set anywhere (neither in the application.properties file nor the other property sources)? Right now, if the property is included in another property, it seem that Spring Boot simply avoids substitution.
For example, in my application.properties file, I have the line:
quartz.datasource.url=jdbc:hsqldb:${my.home}/database/my-jobstore
Right now, if "my.home" is not set elsewhere, Spring Boot is setting the url literally to "jdbc:hsqldb:${my.home}/database/my-jobstore" (no substitution).
I would like to have the application fail to start if the property my.home were not set anywhere else.
To throw a friendly exceptions just put a default null value in property, check and throw a exception in afterProperty method.
#Component
public static class ConfigurationGuard implements InitializingBean {
#Value("${my.home:#{null}}")
private String myHomeValue;
public void afterPropertiesSet() {
if (this.myHomeValue == null or this.myHomeValue.equals("${my.home}") {
throw new IllegalArgumentException("${my.home} must be configured");
}
}
}
Create a bean with a simple #Value(${my.home}) annotated field. - Then Spring will try to inject that value and will fail and therefore stop when the value is not there.
Just #Value(${my.home}) private String myHomeValue; is enough for normal (not Boot) Spring applications for sure! But I do not know whether Boot has some other configuration to handle missing values: If there is an other failure management than you could check that value in an PostCreation method.
#Component
public static class ConfigurationGuard implements InitializingBean {
#Value(${my.home})
private String myHomeValue;
/**
* ONLY needed if there is some crude default handling for missing values!!!!
*
* So try it first without this method (and without implements InitializingBean)
*/
public void afterPropertiesSet() {
if (this.myHomeValue == null or this.myHomeValue.equals("${my.home}") {
throw new IllegalArgumentException("${my.home} must be configured");
}
}
}
The default behaviour in current versions of Spring Boot (1.5.x, 2.0.x, 2.1.x) is to throw an exception if a placeholder can not be resolved.
There will a be an exception like this one :
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'app.foo.undefined' in value "${app.foo.undefined}"
It works because a bean of type PropertySourcesPlaceholderConfigurer (from spring-context) is automatically registered in Spring Boot, in this class : PropertyPlaceholderAutoConfiguration. And by default, the property ignoreUnresolvablePlaceholders in PropertySourcesPlaceholderConfigurer is set to false, which means an exception must be thrown if a placeholder is unresolved (be it nested or not).
Although they work, I think the approach in the foremost answer is somewhat brittle, as it only works for the predefined name(s), and will silently stop checking the when someone changes quartz.datasource.url in the configs to use a different expansion.
Ideally, I want this value of ignoreUnresolvablePlaceholders to be false to get wholesale expansion checking when parsing my configs such as application.properties or its YAML variants, but it's hard-coded to true for these cases. This unfortunately leaves strings such as ${FOO} in its unexpanded form if FOO cannot be found, making troubleshooting extremely painful. This is especially the case for fields that don't readily appear in the logs such as passwords.
While I couldn't find a way of changing ignoreUnresolvablePlaceholders short of modifying Spring Boot's classes, I did find an alternative of using a custom PropertySource implementation and defining a new syntax such as "${!FOO}" to indicate FOO must exist as an environment variable or die. (The OP didn't mention whether my.home is an environment variable but the code below is for environment variables.)
First, an EnvironmentPostProcessor implementation is required for registering the custom PropertySource. This StrictSystemEnvironmentProcessor.java does this as well as holds the implementation of the custom PropertySource:
package some.package;
#Order(Ordered.LOWEST_PRECEDENCE)
class StrictSystemEnvironmentProcessor implements EnvironmentPostProcessor {
private static final String PROPERTY_SOURCE_NAME = "STRICT_" + StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME;
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
if (environment.getPropertySources().contains(PROPERTY_SOURCE_NAME)) {
return;
}
SystemEnvironmentPropertySource delegate = (SystemEnvironmentPropertySource)environment.getPropertySources()
.get(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);
environment.getPropertySources().addLast(new StrictSystemEnvironmentPropertySource(delegate));
}
private static class StrictSystemEnvironmentPropertySource extends SystemEnvironmentPropertySource {
public StrictSystemEnvironmentPropertySource(SystemEnvironmentPropertySource delegate) {
super(PROPERTY_SOURCE_NAME, delegate.getSource());
}
#Override
public Object getProperty(String name) {
if (name.startsWith("!")) {
String variableName = name.substring(1);
Object property = super.getProperty(variableName);
if (property != null) {
return property;
}
throw new IllegalStateException("Environment variable '" + variableName + "' is not set");
}
return null;
}
}
}
Instead of returning null, an exception is thrown for names that start with !.
This META-INF/spring.factories is also required so that Spring initializes our EnvironmentPostProcessor:
org.springframework.boot.env.EnvironmentPostProcessor=some.package.StrictSystemEnvironmentProcessor
Then henceforth, I can write all environment variables substitutions in my configs as ${!FOO} to get strict existance checking.
You can also create a #ConfigurationProperties bean, and decorate it with #Validated and #NotNull. This will throw an exception during startup when the value is not present (or null), e.g.
#Validated
#ConfigurationProperties("my")
public class MyProperties {
#NotNull
private String home;
// getter/setter, or constructor. See #ConstructorBinding.
}
For reference: Spring Boot 2.6 - #ConfigurationProperties Validation.
Note that you may need to add spring-boot-starter-validation, or another validator, depending on your project.
Then, you can just supply it as a dependency when needed, e.g.
#Component
public class AnotherBean {
private final MyProperties myProps;
public AnotherBean(MyProperties myProps) {
this.myProps = myProps;
}
// some code that uses myProps.getHome()
}

#Value annotation in child context

I think there are some problems when using #Value annotation and default values. I am using the following annotation on a variable. I expect to see the variable propAVar set with the property value of propA however, its always set to the default value -10
#Component
public class SomeClass {
#Value("${propA:-10}")
public String propAVar;
}
I am using PropertyPlaceholderConfigurer in the parent and child context. The child context has the context:component-scan element which scans the SomeClass. On digging into the Spring code i found the following method where the issue might lie.
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
//...
public String resolveEmbeddedValue(String value) {
String result = value;
for (StringValueResolver resolver : this.embeddedValueResolvers) {
if (result == null) {
return null;
}
result = resolver.resolveStringValue(result);
}
return result;
}
//...
}
Looks like resolveEmbeddedValue will go through all embeddedValueResolvers and ascertain value of that property. However, if we define a default it will look for that property in the first embeddedValueResolvers and return the default value if its not found there. Shouldn't it go through all resolvers and then return the default value? Would like to mention that this works fine without the default value.
Would be great if someone could help explain the expected behavior of #Value with default value in case of parent-child context's.
[cross-posted on spring forum]
I believe you are seeing this bug. There are some workarounds mentioned by other users in there. Also, I would suggest voting for fixing it, in case your situation is the same as the one described there.

Resources