Enterprise Java

Spring Boot & Multi module projects – Adding module specific property files

Hello!

In this post I will show you several ways how you can add module specific properties files in a Spring Boot project. It will cover a manual approach for making the property files profile aware and a semi automatic way that is profile aware. An example project is published on my Github account (https://github.com/coders-kitchen/spring-boot-multi-module-property-files).

Reasons for having dedicated property files per module in a multi-module project are multifold. One is that you would like be able easily cut out the module into a own service. Having own property files per module, will help here as it makes it clear to the user, that he just has to extract all files from the module to have it standalone. Or that you would like to specify defaults per module that can be overwritten by the main properties.

In general there are three options

  • Specifying the additional property files via active profiles
  • Setting up a configuration in the modules that uses @PropertySource annotation
  • Making the spring context aware of additional file patterns

Let’s discuss one after the other:

Property files specified via active profiles

This approach uses the active profiles mechanism of Spring to activate additional property files. For example the active profile local would read also settings from the file application-local.properties.

The benefit of this approach are, that you just use the standard mechanism for adding new property files per module. And they can be specified in the main application.properties or dedicated application-<profile>.properties files.

The drawbacks are that you have to remember every time to add the active profiles in the right order, for example the profile module1 must be come right before module1-production to allow the latter one to overwrite the default profile.

Additionally you must remember that the default profile needs to be applied in all environment aka profiles to have the default settings available.

Manual via @PropertySource

Spring itself ships an annotation for adding additional property files to the context. It’s called @PropertySource and can be used on class level (see the next example).

@Configuration
@PropertySource("classpath:application-module1.properties")
public class MyPropertyConfig {
 
}

To make this approach aware of multiple profiles you can use it this way

@Configuration
public class MyPropertyConfig {
 
  @Configuration
  @PropertySource("classpath:application-module1.properties")
  @Profile("default")
  static class DefaultConfig {}
 
  @Configuration
  @PropertySource("classpath:application-module1-production.properties")
  @Profile("production")
  static class ProductionConfig {}
}

The benefits are, that you must not use dedicated profiles per module in the main application, but you can rely on simple profiles. Additionally it is expressed in the configuration classes itself, which could make it easy to check which profiles are available.

Drawbacks are that this works only very well for a pre-defined set of profiles this approach, but when you would like to add a new profile you have to remember that you must add it also to the MyPropertyConfig class. Additionally when you change the name of the module you must not only change the file names, but also the references in the code.

Adding new property file pattern to the property sources

This way is the most generic one, as it will directly inject the new property file patterns into the context and make it automatically profile aware. To get it working you must use the extension mechanics via ApplicationListener interface.

This allows you to directly listen on the ApplicationEnvironmentPreparedEvent event which is fired after the runtime environment is prepared but before it is loaded. It enables you to add files to the property sources for example. The event provides access to the ConfigurableEnvironment which provides among other things information about the active profiles.

This is an example implementation if the listener, that will first add the profile specific properties files and than the default property file to the ConfigurableEnvironment.

public class PropertyFilePatternRegisteringListener implements ApplicationListener {
 
  public static final String PROPERTY_FILE_PREFIX = &quot;application-module3&quot;;
  private static final String FILE_SUFFIX = &quot;.properties&quot;;
 
  @Override
  public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
    ConfigurableEnvironment environment = event.getEnvironment();
    try {
      loadProfileProperties(environment);
      loadPlainProperties(environment);
    } catch (IOException ex) {
      throw new IllegalStateException(&quot;Unable to load configuration files&quot;, ex);
    }
  }
 
  private void loadProfileProperties(ConfigurableEnvironment environment) throws IOException {
    String[] activeProfiles = environment.getActiveProfiles();
    if(activeProfiles != null && activeProfiles.length > 0)
      loadProfileProperties(environment, activeProfiles);
    else
      loadProfileProperties(environment, environment.getDefaultProfiles());
  }
 
  private void loadProfileProperties(ConfigurableEnvironment environment, String[] profiles) throws IOException {
    for (String activeProfile : profiles) {
      addFileToEnvironment(environment, PROPERTY_FILE_PREFIX + &quot;-&quot; + activeProfile + FILE_SUFFIX);
    }
  }
 
  private void loadPlainProperties(ConfigurableEnvironment environment) throws IOException {
    addFileToEnvironment(environment, PROPERTY_FILE_PREFIX + FILE_SUFFIX);
  }
 
  private void addFileToEnvironment(ConfigurableEnvironment environment, String file) throws IOException {
    ClassPathResource classPathResource = new ClassPathResource(file);
    if (classPathResource.exists()) {
      environment.getPropertySources()
                 .addLast(new ResourcePropertySource(classPathResource));
    }
  }
}

To activate it you must add it as an ApplicationListener when loading the application context, like this

new SpringApplicationBuilder()
        .listeners(new PropertyFilePatternRegisteringListener())
        .main(Application.class)
        .registerShutdownHook(true)
        .run(args);
  }

The benefits of this variant are, that we are agnostic of the active profiles and can easily add new profile specific property files. It also kicks in very early in the boot process so that the application is from the beginning on aware of the properties specified here.

The drawbacks are, that you must add the listener to the main module for each sub module. And using the additional / different property files (or at least the default variant) in the tests aren’t straight forward. At the moment of this writing I’m only aware of using @PropertySource in the integration tests to make this work. Also making it aware of all the cases that the default Spring loader supports is more complex than the approach described above.

Summary

We’ve discussed in this post several ways of adding new property files to a Spring Boot application. All variants have the benefits and drawbacks.

Depending on the use case I would either go for @PropertySource or the usage of ApplicationListener interface. The former one is good enough if you just want to have a common set of properties that could be overwritten for specific profiles or by the main modules properties. The latter one is the most generic approach and should be used, when you absolutely need this flexibility.

Peter Daum

Peter is senior Java developer in the telecommunication industry. He works in the area of business and operations support systems and is always happy to share his knowledge and experience. He is interested in everything related to Java and software craftsmanship.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button