Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to conditionally make a Spring Boot application terminate at startup based on a property

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.

like image 644
Marko Previsic Avatar asked May 15 '19 14:05

Marko Previsic


People also ask

How do you trigger gracefully shutdown spring boot application?

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.

How do you set a global property that sets on when spring boot application starts?

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.

Which property in application properties file will help to run spring boot application?

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.

What is the preferred way to close an application context does spring boot do this for you?

by the use of the close() method on the context objet the ApplicationContext is closed and the application is destroyed.


2 Answers

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 @AssertTrueconsiders 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);
  }

}
like image 128
Dirk Deyne Avatar answered Oct 21 '22 04:10

Dirk Deyne


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");
        }
    }
}
like image 23
M. Justin Avatar answered Oct 21 '22 05:10

M. Justin