Issue
I want to implement the following use case - my Spring Boot application should start only if a certain property in application.yaml
is set:
myapp:
active: true
If the property is not set, the context initialization should fail with a message that the property is missing.
I found in this topic how to achieve it: Spring Boot - Detect and terminate if property not set? but the problem why I can't follow this approach is that it is possible that the context initialization fails before the bean, that checks this property, is loaded.
For example, if some other bean fails to load because another property is missing, the context initialization will fail at that point and my bean, that checks the desired property, won't be loaded. This is not OK for me because I want the myapp.active
property to be checked first, before any other beans get loaded.
The reason why I want to have it that way is that a certain profile should be set when running the app - the application-[profile].yaml
contains both myapp.active: true
and some other mandatory properties that are required to load the context.
I want my app always to fail because of myapp.active not being true so that I can output a meaningful message telling that the profile is missing and that the app must be run with one of the profiles (from given list of profiles). Some guys that aren't developers are running the app so I want them to know why the app didn't run, otherwise they will think there is some bug in the app.
How can I achieve this? Is it possible somehow to read the property before the beans are being loaded? I would like to avoid setting @DependsOn
on all beans (or doing the same through a BeanPostProcesser
) and am seeking for a more elegant solution.
Solution
The application won't start if you use a condition-on-property. Fast enough?
@SpringBootApplication
@ConditionalOnProperty(name = "myapp.active")
public class FastFailWhenPropertyNotPresentApplication {
public static void main(String[] args) {
SpringApplication.run(FastFailWhenPropertyNotPresentApplication.class, args);
}
}
Basically @SpringBootApplication
is just a @Configuration
class.
You have an option matchIfMissing
that you can use to specify if the condition should match if the property is not set. Defaults to false.
EDIT:
A better solution is to configure your property via a @ConfigurationProperties
combined with @Validated
, so you can use the javax.validation.constraints annotations.
package stackoverflow.demo;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotNull;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
@Component
@ConfigurationProperties(prefix = "myapp")
@Validated
public class MyAppProperties {
@AssertTrue
@NotNull
private Boolean active;
public Boolean getActive() {
return active;
}
public void setActive(Boolean active) {
this.active = active;
}
}
note: you can leave out @ConditionalOnProperty(name = "myapp.active")
use @AssertTrue
in combination with @NotNull
because of @AssertTrue
considers null elements as valid.
and spring-boot generates a nice error-message for free:
***************************
APPLICATION FAILED TO START
***************************
Description:
Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'myapp' to stackoverflow.demo.MyAppProperties failed:
Property: myapp.active
Value: false
Origin: class path resource [application.properties]:1:16
Reason: must be true
Action:
Update your application's configuration
EDIT (after updated question)
A faster way: your application won't start, nor an application-context will be loaded
package stackoverflow.demo;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.io.ClassPathResource;
@SpringBootApplication
public class FastFailWhenPropertyNotPresentApplication {
static Boolean active;
static {
YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
yaml.setResources(new ClassPathResource("application.yaml"));
active = (Boolean) yaml.getObject().getOrDefault("myapp.active", false);
}
public static void main(String[] args) {
if (!active) {
System.err.println("your fail message");
} else {
SpringApplication.run(FastFailWhenPropertyNotPresentApplication.class, args);
}
}
}
EDIT
another solution that probably fits your needs best...
By listening to the ApplicationEnvironmentPreparedEvent
Event published when a {@link SpringApplication} is starting up and the * {@link Environment} is first available for inspection and modification. *
note: you cannot use a @EventListener
, but you have add the Listener to the SpringApplication
package stackoverflow.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationListener;
@SpringBootApplication
public class FastFailWhenPropertyNotPresentApplication {
static class EnvironmentPrepared implements ApplicationListener<ApplicationEnvironmentPreparedEvent>{
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
Boolean active = event.getEnvironment().getProperty("myapp.active",Boolean.class,Boolean.FALSE);
if(!active) {
throw new RuntimeException("APPLICATION FAILED TO START: ACTIVE SHOULD BE TRUE ");
}
}
};
public static void main(String[] args) throws Exception {
SpringApplication springApplication = new SpringApplication(FastFailWhenPropertyNotPresentApplication.class);
springApplication.addListeners(new FastFailWhenPropertyNotPresentApplication.EnvironmentPrepared());
springApplication.run(args);
}
}
Answered By - Dirk Deyne