back to all blogsSee all blog posts

MicroProfile Config 2.0

image of author
Joseph Cass on Mar 31, 2021
Post available in languages:

MicroProfile Config 2.0 is a major version release jam-packed with new features to help make configuring your microservices a breeze.

For an overview of MicroProfile Config and a walkthrough of all the new features in version 2.0, check out the MicroProfile Config demo video on IBM Developer.

Released as part of MicroProfile 4.0, Config 2.0 introduces:

  • Config Profile- A special property, called mp.config.profile, that can be used to determine which set of config property values are used.

  • ConfigValue- An API class that holds a variety of useful information about a specified config property.

  • @ConfigProperties- An annotation that provides a way to retrieve a number of related config property values, with a shared specified prefix, into a CDI bean.

  • Property Expression- A way of embedding config property values inside of other config property values by using the ${} syntax.

Prepare For Liftoff

MicroProfile Config 2.0 is new to Open Liberty 21.0.0.3 - so make sure you are using the latest version of Liberty.

Then, you need only 2 steps to launch:

  1. Tell your build where all the awesome new features are defined.

    The easiest way to do this is to point to the MicroProfile 4.0 API artifact that includes the MicroProfile Config 2.0 API artifact.

    • If you’re using Maven, add the following dependency to your pom.xml:

      pom.xml
      <dependency>
              <groupId>org.eclipse.microprofile</groupId>
              <artifactId>microprofile</artifactId>
              <version>4.0.1</version>
              <type>pom</type>
              <scope>provided</scope> <!-- This package is built and released with Open Liberty -->
      </dependency>
    • Or, if you’re using Gradle:

      build.gradle
      dependencies {
          compileOnly group: 'org.eclipse.microprofile', name: 'microprofile', version: '4.0.1'
      }
  2. Enable the feature in Open Liberty.

    Add mpConfig-2.0 (MicroProfile Config 2.0’s feature name in Open Liberty) to the feature list in your server.xml. Additionally cdi-2.0 is recommended to enable all the cool injection functionally:

    server.xml
    <featureManager>
        <feature>mpConfig-2.0</feature>
        <feature>cdi-2.0</feature>
    </featureManager>

    Alternatively, using the microProfile-4.0 convenience feature will enable all of the awesome MicroProfile 4.0 features at once, including mpConfig-2.0 and cdi-2.0.

And just like that, you’re ready for take-off!

Getting Up To Speed

If you’re a little unfamiliar with MicroProfile Config (no worries if you are), the Open Liberty Configuration guides are a great place to start. The MicroProfile spec and the Open Liberty Docs are good to have at hand for reference too.

In essence, MicroProfile Config allows you to define configuration values, which are referred to as "config property" values, in a huge range of locations that are known as ConfigSources. These values can be easily retrieved anywhere in your code with CDI. For example, you could define the following in your Open Liberty server.xml file:

<variable name="port" value="9080"/>

And inject it into your application code with:

@Inject
@ConfigProperty(name="port")
int port;

New Horizons

Now let’s blast off into the new features in MicroProfile Config 2.0.

The examples below use the config property values defined in this example META-INF/microprofile-config.properties file (A default ConfigSource) which would be found on the classpath:

META-INF/microprofile-config.properties
%testing.server.host=example.test.org
%production.server.host=example.prod.org
server.host=example.org
server.port=9080
server.url=http://${server.host}

Config Profile

Config Profiles allow configuration for different environments and development stages while only one of them is active (e.g. dev, test, prod). The active Config Profile is specified using the mp.config.profile property, which can be set in any ConfigSource. Once it is set, the corresponding config property values associated with the active profile are used. The mp.config.profile property can also be set at application startup, for example if you’re using Maven and the liberty-maven-plugin you can start your app in Dev mode with:

mvn liberty:dev -Dliberty.var.mp.config.profile="testing"

The -Dliberty.var.mp.config.profile argument sets a Maven property which the liberty-maven-plugin uses to configure the Liberty server. In this case, the mp.config.profile property is set to testing.

If you deploy your microservices to Kubernetes, you can set the property one of two ways:

  • In a ConfigMap, which then maps to an environment variable (Refer to this blog for more information on how to use ConfigMap to store properties); or

  • Directly as an environment variable in your deployment YAML file.

When specifying this property as an environment variable, you should use MP_CONFIG_PROFILE so that it works on all Operating Systems.

The mp.config.profile property can be used:

  • At the property level: config property names can be set in the following format so that they are used for specific selected profiles:

    %<mp.config.profile>.<original property name>

    For example, with mp.config.profile set to testing, retrieving the config value for "server.host" would use the config property %testing.server.host from the example ConfigSource rather than server.host. The value of the property would resolve to example.test.org.

    Similarly, if mp.config.profile was set to production, retrieving "server.host" would resolve to example.prod.org. If mp.config.profile was not set, retrieving "server.host" would resolve to example.org.

  • At the ConfigSource level: multiple microprofile-config.properties files can be provided in the following format so they can be used for specific selected profiles:

    microprofile-config-<mp.config.profile>.properties

    For example, if a file called microprofile-config-testing.properties was provided on the classpath, with mp.config.profile set to testing, the file would be loaded "on top of" the default microprofile-config.properties file. The config property values from microprofile-config-testing.properties would take precedence.

With Config Profiles, your microservices are configured appropriately based on the project stage without changing any code or needing to update a bunch of config values manually.

@ConfigProperties

If you’re Injecting plenty of related config property values into the same class, things could start getting a little out of hand:

@Inject
@ConfigProperty(name="server.port")
int port;

@Inject
@ConfigProperty(name="server.host")
String host;

@Inject
@ConfigProperty(name="server.url")
String url;

Wouldn’t it be great if you could Inject these related values all at once? Well now you can! You can define a @ConfigProperties bean for config property values which share a common prefix. For example, you can define a bean annotated with @ConfigProperties called ServerDetailsBean:

@ConfigProperties(prefix="server")
@Dependent
public class ServerDetailsBean {
   String host;
   int port;
   int url;
}

And inject the bean into another class:

@Inject
@ConfigProperties
ServerDetailsBean serverDetails;

Where the config property values can be easily retrieved within the class the bean was injected into with:

serverDetails.host;  // Returns: example.org (retrieves the value, as a String, for the config property named server.host)
serverDetails.port;  // Returns: 9080 (retrieves the value, as an int, for the config property named server.port)

ConfigValue

Have you ever wondered where a config property value comes from? If the value is not what you want, you might want to figure out where you can change the it.

The new ConfigValue API class allows you to retrieve details about a given config property into one convenient ConfigValue object. And it’s super easy to get hold of. All you have to do is inject the config property you’d like, as usual, only this time define the type as ConfigValue:

@Inject
@ConfigProperty(name="server.host")
ConfigValue serverNameConfigValue;

With this configuration, you can retrieve all the useful values with the get methods defined in the Javadoc. For example, you can determine which ConfigSource was the “winning” one (the ConfigSource with the highest ordinal) for a config property defined in multiple locations by calling:

serverNameConfigValue.getSourceName(); // Returns: PropertiesConfigSource[source=file:/<path-to-file>/META-INF/microprofile-config.properties]
serverNameConfigValue.getSourceOrdinal(); // Returns: 100 (the default ordinal value for META-INF/microprofile-config.properties)

Property Expression

Property Expressions provide a way to set and expand variables in property values using the ${} syntax. For example, the config property server.url defined in the example ConfigSource as http://${server.host} will be resolved to http://example.org since server.host is defined as example.org:

@Inject
@ConfigProperty(name="server.url")
String url; // Returns: http://example.org (or http://example.test.org if mp.config.profile is set to “testing”)

You can also implement some funky expressions, such as defining default values, composed expressions, and multiple expressions. The spec covers these really well.

Note: Previously working configurations might now behave differently if the configuration happens to contain values with the Property Expressions syntax (${}) in them.

Some Extra Info For The Return Journey

For the following examples, we’ll use a slightly more rogue example ConfigSource (let’s call it "example ConfigSource v2"):

META-INF/microprofile-config.properties
empty.property=
empty.array.prop=,
ports=9080,9081,9082
server.port=9080

Empty And Special Values Behaviour Updates

The behavior for "empty" and "special" config property values is updated:

  • The easiest way to get your head around this is to look at the conversion rule examples.

  • A value is considered to be "empty" if the Converter being used considers it to be "empty". For example:

    • All Converters consider "", the empty String, to be empty.

    • The built-in Converter for String[] considers "," to be empty (because it is "special").

  • From MicroProfile Config 2.0, these "empty" values are no longer valid. Retrieving the values natively, without defaultValues or Optionals, now throws a NoSuchElementException. E.g. for the values defined in the example ConfigSource v2:

    @Inject
    @ConfigProperty(name = "empty.property")
    String emptyProperty; // Throws: `DeploymentException` (caused by a `NoSuchElementException`)
    
    @Inject
    @ConfigProperty(name = "empty.array.property")
    String[] emptyArrayProperty; // Throws: `DeploymentException` (caused by a `NoSuchElementException`)

    and

    Config config = ConfigProvider.getConfig();
    config.getValue("empty.property", String.class); // Throws: `NoSuchElementException`
    config.getValue("empty.array.property", String[].class); // Throws: `NoSuchElementException`

    However these values can be retrieved "optionally":

    @Inject
    @ConfigProperty(name = "empty.property")
    Optional<String> emptyProperty; // Returns: Optional.empty
    
    @Inject
    @ConfigProperty(name = "empty.array.property")
    Optional<String[]> emptyArrayProperty; // Returns: Optional.empty

    and

    Config config = ConfigProvider.getConfig();
    config.getOptionalValue("empty.property", String.class); // Returns: Optional.empty
    config.getOptionalValue("empty.array.property", String[].class); // Returns: Optional.empty
  • This means that Config.getValue() never returns null. A NoSuchElementException is thrown if the property is:

    • not defined

    • defined as an empty String ("")

    • converted to null (considered to be "empty") by its Converter

Expanding The Config API

Two new methods have been added to the Config API class:

With these methods, you can retrieve multi-valued config property values as a List instead of an array. The methods return the resolved property values for the specified propertyName with the specified propertyType. For example, when retrieving "ports" from example ConfigSource v2:

Config config = ConfigProvider.getConfig();
config.getValues("ports", Integer.class) // Returns: [9080, 9081, 9082] (a List<Integer>)
config.getOptionalValues("ports", Integer.class) // Returns: Optional[[9080, 9081, 9082]] (an Optional<List<Integer>>)

More Optional Converters

OptinalInt, OptionalLong and OptionalDouble are now provided as built-in Converters. The new Converters can be used like any of the other built-in Converters; converting injected config property values to a defined type:

@Inject
@ConfigProperty(name = "server.port")
OptionalInt optionalServerPort; // Returns: OptionalInt[9080]

Heads Up! Incompatibility Changes

If you move up from MicroProfile Config 1.x to 2.0, please take care of the following incompatible changes:

  • ConfigSource.getPropertyNames() is no longer a default method. Any implementations of a ConfigSource must implement this method.

  • Previous versions of MP Config don’t evaluate property expressions. As such, a previous working configuration may behave differently (if the configuration contains values with property expressions syntax, e.g. ${var.name}). You can disable property expressions by setting the property mp.config.property.expressions.enabled with the value of false.

  • As mentioned here, the behavior of retrieving "empty" and "special" config property values is changed. In previous releases, an "empty" value was considered valid. Now, unless retrieved "optionally", a NoSuchElementException is thrown.

MicroProfile Config 2.0 is part of the larger MicroProfile 4.0 release. If you’d like to learn more about the other technologies in MicroProfile 4.0, check out this deep dive blog post.

Thank You For Joining The Ride

Thank you for reading! As always, we’d love to hear any feedback you’d like to share. You can message our mailing list, ask questions on StackOverflow, and raise any issues on our GitHub page.