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.
In the Spring Boot Document, they said that 'Each SpringApplication will register a shutdown hook with the JVM to ensure that the ApplicationContext is closed gracefully on exit. ' When I click ctrl+c on the shell command, the application can be shutdown gracefully.
The easiest way is to define a @Component class with field of type Map for these properties. Then populate it at the start of your application with information retrieved from database. Then, whenever you want to use these properties inject these using Spring Boot 's DI mechanism.
Spring Boot application converts the command line properties into Spring Boot Environment properties. Command line properties take precedence over the other property sources. By default, Spring Boot uses the 8080 port number to start the Tomcat. Let us learn how change the port number by using command line properties.
by the use of the close() method on the context objet the ApplicationContext is closed and the application is destroyed.
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);
}
}
}
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);
}
}
One option would be to create a bean which checks for the presence of the property, and throw an exception during bean creation if the property is not set.
@Component
public static EnsureApplicationActive {
@Value("${myapp.active}")
private Boolean active;
@PostConstruct
public void ensureApplicationActive() {
if (!Boolean.TRUE.equals(active)) {
throw new IllegalStateException("TODO: Meaningful message goes here");
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With