Nugget Friday - How To Load Different MicroProfile Config Values Based On Active Maven Profile

Photo of Luqman Saeed by Luqman Saeed

 

For our next Nugget Friday, we're diving into a question that was posed in last week’s blog post: "How can I load different MicroProfile config values based on the active Maven profile?"

If you've been working with MicroProfile and Maven, you know how crucial it is to manage configurations across different environments. This week, we'll walk you through how to easily switch between configuration values by leveraging Maven profiles, ensuring your applications run seamlessly across various environments.

The Problem

So you started using MicroProfile Config to externalise your application configuration. Great. However, your Maven managed application uses different profiles that each require its own configuration values at runtime. For instance, you have dev, test, preprod and prod profiles that each should have different configuration values at runtime. Your question now is, how do I load different MicroProfile Config values based on the active Maven profile? 

The Solution

The MicroProfile Config specification has support for profiles. What do I mean? Chapter 7 of the specification states “Config Profile indicates the project phase, such as dev, testing, live, etc.” In essence, the spec has provided a way to read config values based on the profile of the project, be it dev, testing, live or whatever. The profiles feature supports profiles at two levels, the properties level and config source level. This blog post covers how to use the second option. You can read more of the properties level profile feature in the spec. 

How It Works

To use the profile feature of the MicroProfile Config API, we need to pass the profile to the runtime through the mp.config.profile config property. At runtime if the value of that property is set to dev for instance, then the configuration values relating to dev are going to be returned when a configuration is requested. To use this property in a Maven project, we need a way to pass the currently active Maven profile to the MicroProfile Config runtime dynamically. So to start, set resource filtering in the pom.xml file of the project as follows.

Filter Maven Resources

<resources>
       <resource>
           <directory>src/main/resources</directory>
           <filtering>true</filtering>
       </resource>
</resources>

This tells Maven to filter variables declared in files found in the resources folder of the project. This way, we can pass variables from Maven through the ${} construct to files like the microprofile-config.properties file. 

Set Profile Property

Set the mp.config.profile in the microprofile-config.properties file to the following:

mp.config.profile=${active.profile}

What you’re doing with the above is passing the value of a Maven variable to the property. The actual value of the ${active.profile} variable will be set by Maven at build time. 

Create/Update Profiles With Properties

Now create your profiles if they don’t exist, or amend them if they do with the active.profile property as follows:

<profiles>
       <profile>
           <id>dev</id>
           <activation>
               <activeByDefault>true</activeByDefault>
           </activation>
           <properties>
                               <active.profile>dev</active.profile>
                           </properties>
       </profile>
      
<profile>
           <id>prod</id>
           <properties>
                               <active.profile>prod</active.profile>
           </properties>
       </profile>
    </profiles>


We create two  profiles, dev and prod, with dev being active by default. In the properties element of each profile, we set the value that Maven should pass to the mp.config.profile MicroProfile Config property. From the above profiles, if the application is started without defining an explicit profile, the value of mp.config.profile=dev, as that  profile is active by default. With that, the MicroProfile Config runtime will know the application is running in dev mode and thus, read configuration values from the dev config source.

Create Profile Specific Config Sources

Now define the config sources relating to each profile that your application needs. In the above example, with two profiles, dev and prod, create two files in the resources/META-INF folder (same place as the microprofile-config.properties file) as follows.

First file microprofile-config-dev.properties and in it

profile.property=Hi! From Dev Properties

Second file microprofile-config-prod.properties and in it

profile.property=Hi! From Prod Properties

These files are profile specific config sources. As stated above, the MicroProfile Config specification supports profiles at the property and config source levels. The way to specify a profile specific config source is having the profile name in the name of the config source. In the two files above, the dev and prod in the names of the files tells the runtime which config source pertains to which profile value passed to mp.config.profile. If the application is run in dev, configuration values will be read from the microprofile-config-dev.properties file, and same for prod. 

 Use The Properties

With everything in place, you can now inject the profile.property value in your Java classes as follows.

   @Inject
   @ConfigProperty(name = "profile.property")
    private String myConfigValue;

When you run the application without passing an explicit profile, the value that will be injected into the myConfiValue variable will be 

And when you run the application with mvn clean package payara-micro:dev -Pprod, the value that will be read for the same property will be

And with that in place, you now have a way to read MicroProfile Config values based on the active maven profile.

Why You Should Care

Profile-based configuration management offers several benefits that can significantly improve your development workflow and application flexibility:

  1. Environment-specific configurations: Easily manage different settings for development, testing, and production environments without code changes.
  2. Simplified deployment: Reduce the risk of deploying incorrect configurations to different environments by automating the process.
  3. Improved development efficiency: You can switch between different configurations quickly, speeding up the development and testing process.
  4. Better security practices: Keep sensitive information (like production database credentials) separate from development configurations, reducing the risk of accidental exposure.
  5. Consistency across team members: Ensure all team members are using the same configuration for each environment, minimising "it works on my machine" issues.

Advanced Features and Considerations

Custom Config Source Priorities 

Remember that MicroProfile Config uses a prioritized approach to resolve configuration values. You can use this to create a more nuanced configuration hierarchy. For instance, you can set the config_ordinal value for each of the profile specific config sources we created above to a high value to make values defined in those files take precedence over the same values defined in config sources with lower config_ordinal numbers. 

Fallback Mechanism

Implement a fallback mechanism for when a specific profile doesn't define a particular property. This can be having a generic value in the broadest config source, or passing a default value when injecting the property in your Java code as follows.

    @Inject
   @ConfigProperty(name = "profile.property", defaultValue = "dev")
    private String myConfigValue;

Remember, injecting MicroProfile Config values that cannot be resolved without a default value will cause application deployment failures.

Profile Information Is Read Once

The value of the mp.config.profile property is read only once after the application is started. If you update it afterwards through dynamic configuration, the spec states the expected behavior is undefined and implementation specific. So best to avoid changing the value of that property at runtime after the application has started. 

Caveats

  1. Build-time coupling: Be aware that this approach ties your configuration to the build process. Be sure your CI/CD pipeline correctly handles profile activation.
  2. Property precedence: Understand the precedence rules of MicroProfile Config to avoid unexpected value overrides.
  3. Profile proliferation: Resist the temptation to create too many profiles. Stick to a manageable number to avoid configuration hell.
  4. Testing considerations: Make sure your tests can run with different profiles to catch profile-specific issues early.
  5. Documentation: Maintain clear documentation of what each profile represents and which properties are expected to change between profiles.

Conclusions

Using MicroProfile Config with Maven profiles provides a powerful way to manage environment-specific configurations in your Jakarta EE applications. This approach allows you to maintain a clean separation between your code and its configuration, making your applications more flexible and easier to deploy across different environments.

Through the steps outlined in this blog post, you can set up a sophisticated configuration management system that adapts to your build process and runtime environment. Remember to use this feature judiciously though, keeping your configurations organised and well-documented.

As you implement this in your projects, you'll likely find that it simplifies many aspects of configuration management, from local development to production deployment. It's another tool in your toolkit for building more maintainable and adaptable Jakarta EE applications.

Happy coding, and may your configurations always be in order!

 

Related Posts

Comments