Reputation: 1625
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.
Upvotes: 18
Views: 25702
Reputation: 3412
I wanted a more generic, plug-and-play solution to this issue. This is what I came up with:
@Component
@Slf4j
public class UnresolvedPlaceholderDetector {
@EventListener
public void handleContextRefreshed(ContextRefreshedEvent event) {
var env = (ConfigurableEnvironment) event.getApplicationContext().getEnvironment();
env.getPropertySources()
.stream()
.filter(ps -> ps instanceof MapPropertySource)
.map(MapPropertySource.class::cast)
.map(MapPropertySource::getPropertyNames)
.flatMap(Stream::of)
.forEach(pn -> {
// This throws IllegalArgumentException on unresolved placeholders
env.getRequiredProperty(pn);
log.debug("Property '{}' is resolved", pn);
});
}
}
Still needs a bit more testing for corner cases (e.g. non-MapPropertySource
sources), but seems to work well to hard crash the app on any unresolvede placeholder anywhere from properties in config files. Example:
java.lang.IllegalArgumentException: Could not resolve placeholder 'DB_HOST' in value "jdbc:mysql://${DB_HOST}:${DB_PORT}/test"
Upvotes: 1
Reputation: 632
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()
}
Upvotes: 4
Reputation: 20869
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.
Upvotes: 3
Reputation: 4385
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).
Upvotes: 3
Reputation: 349
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");
}
}
}
Upvotes: 17
Reputation: 120881
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");
}
}
}
Upvotes: 5