Spring Boot - Using EnvironmentPostProcessor Examples

This tutorial shows you how to use the EnvironmentPostProcessor in Spring Boot.

Spring Boot has an functional interface called EnvironmentPostProcessor, which allows customization of the Environment object before the application context is refreshed. There are several things you can do using the interface. For example, it can be used to set profiles, set property sources based on the values from environment variables, as well as validating environment properties. In this tutorial, I'm going to show you how to use the interface.

Using EnvironmentPostProcessor

To define a EnvironmentPostProcessor, you need to create a class that implements EnvironmentPostProcessor. A non-abstract class that implements the interface has to override the following method.

  void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application);

Below are several things you can do inside the method.

Set Profiles

The first parameter type, ConfigurableEnvironment, has two methods for setting the active and default profiles. You can set the active and default profiles using the setActiveProfiles and setDefaultProfiles methods respectively. Both methods allow you to pass multiple values. There is another method named addActiveProfile which is used to add a profile to the current set of active profiles.

  void setActiveProfiles(String... profiles);
  void addActiveProfile(String profile);
  void setDefaultProfiles(String... profiles);

Examples:

  environment.setActiveProfiles("profileA", "profileB");
  environment.addActiveProfile("profileC");
  environment.setDefaultProfiles("profileD");

If you only want to set the profiles, there are other ways to set active profiles in Spring Boot which is easier than this one.

Get and Validate Profiles

Using the ConfirugrableEnvironment object, you can get the active profiles by using the getActiveProfiles. Then, you can validate the profiles by using your own logic.

  private static final Set<String> ENV_PROFILES = Set.of("stg", "prod");

  @Override
  public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
    Set<String> activeProfiles = new HashSet<>(Arrays.asList(environment.getActiveProfiles()));
    activeProfiles.retainAll(ENV_PROFILES);

    if (activeProfiles.size() > 1) {
      throw new IllegalStateException(String.format("Can only use one profile of %s", ENV_PROFILES));
    }
  }

Add Property Sources

It's also possible to add a property source inside the postProcessEnvironment method. For example, the application needs to read a property value from the system environment variable.

  WOOLHA="{\"id\": 100, \"name\":\"myname\", \"secret\": \"mysecret\"}"

We want to read and parse the value above and map each field to a property with a prefix.

  com.woolha.id=100
  com.woolha.name=myname
  com.woolha.secret=mysecret

To read a value from an environment property, you can call the getProperty method of the ConfigurableEnvironment object. Since the value above is in JSON format, it has to be parsed first. Then, map the values to properties with a custom prefix.

After that, get the object that holds the property sources by calling the ConfigurableEnvironment's getPropertySources method, which returns MutablePropertySources. On the MutablePropertySources object, call the addAfter method, which can be used to add a given property source object whose precedence is below the passed relative property source name.

  public void addAfter(String relativePropertySourceName, PropertySource<?> propertySource

Example:

  @Override
  public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
    JsonParser jsonParser = JsonParserFactory.getJsonParser();
    Map<String, Object> woolhaProperties = jsonParser.parseMap(environment.getProperty("WOOLHA"));
    Map<String, Object> prefixedProperties = woolhaProperties.entrySet().stream()
        .collect(Collectors.toMap(
            entry -> "com.woolha." + entry.getKey(),
            Map.Entry::getValue
        ));
    environment.getPropertySources()
        .addAfter(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, new MapPropertySource("woolha", prefixedProperties));
  }

By doing so, it becomes possible to access the properties with the prefix.

  @Value("${com.woolha.id}")
  int woolhaId;

  @Value("${com.woolha.name}")
  String woolhaName;

  @Value("${com.woolha.secret}")
  String woolhaSecret;

Validate Properties

Another thing that you can do is validate that certain properties must be present and not null. Since the ConfigurableEnvironment extends the ConfigurablePropertyResolver interface, it has setRequiredProperties and validateRequiredProperties methods.

  void setRequiredProperties(String... requiredProperties);
  void validateRequiredProperties() throws MissingRequiredPropertiesException;

You have to set the required properties using the setRequiredProperties method. Then, call validateRequiredProperties to perform the validation.

  public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
    environment.setRequiredProperties("MYPROP1", "MYPROP2");
    environment.validateRequiredProperties();
  }

Register EnvironmentPostProcessor in spring.factories

After you create the class, it's not automatically registered. Therefore, you may find that it's not loaded by Spring when the application is being started. The solution is you have to register it in the spring.factories file. The file must be placed in src/main/resources/META-INF directory. If it doesn't exist, you have to create it.

Inside the file, you have to define a key named org.springframework.boot.env.EnvironmentPostProcessor. The value must be the fully qualified name of the class which includes the package name.

  org.springframework.boot.env.EnvironmentPostProcessor=\
    com.woolha.spring.example.config.MyPostProcessor

If there are multiple classes, use a comma as the delimiters.

  org.springframework.boot.env.EnvironmentPostProcessor=\
    com.woolha.spring.example.config.MyPostProcessor,\
    com.woolha.spring.environmentpostprocessor.config.MyAnotherPostProcessor

Set EnvironmentPostProcessor Order

If there are multiple EnvironmentPostProcessor objects, you may need to define the processing order. The recommended way to set the order is by using the @Order annotation. Spring loads classes with higher precedence first. The lower the integer value passed to the annotation, the higher the precedence. If there are some classes with the same @Order value or not using the annotation, the order how they are defined in the spring.factories determines the order Spring loads them.

  @Order(Ordered.LOWEST_PRECEDENCE)
  public class MyPostProcessor implements EnvironmentPostProcessor {
    //  class content
  }

Add Constructor Parameters

Since Spring Boot 2.4, it's also possible to add some parameters to the constructor. The first supported parameter type is DeferredLogFactory, which is a factory for creating DeferredLog instances. It can be used to create loggers whose messages are deferred until the logging system is fully initialized. Another parameter type that you can add is ConfigurableBootstrapContext, which is used to store expensive objects or objects to be shared.

  public class MyPostProcessor implements EnvironmentPostProcessor {
  
    private final Log logger;
  
    private final ConfigurableBootstrapContext configurableBootstrapContext;
  
    public MyPostProcessor(
        DeferredLogFactory logFactory,
        ConfigurableBootstrapContext configurableBootstrapContext
    ) {
      this.logger = logFactory.getLog(MyPostProcessor.class);
      this.configurableBootstrapContext = configurableBootstrapContext;
    }
  }

Summary

In this tutorial, we have learned how to create and register EnvironmentPostProcessor. Basically, you have to create a class that extends the interface and implement the postProcessEnvironment. The class must be registered in the spring.factories file.

You can also read about: