Injecting configuration properties like JDBC URLs, passwords, usernames, or hostnames from external sources is a common requirement for every application. Inspired by the twelve-factor app principles you should store configuration in the environment (e.g. OS environment variables or config maps in Kubernetes). These external configuration properties can then be replaced for your different stages (dev/prod/test) with ease. Using MicroProfile Config you can achieve this in a simple and extensible way.
Learn more about the MicroProfile Config specification and how to use it in this blog post.
Specification profile: MicroProfile Config
- Current version: 1.4
- GitHub repository
- Latest specification document
- Basic use case: Inject configuration properties from external sources (like property files, environment, or system variables)
Injecting configuration properties
At several parts of your application, you might want to inject configuration properties to configure for example the base URL of a JAX-RS Client
. With MicroProfile Config you can inject a Config
object using CDI and fetch a specific property by its key:
1 2 3 4 5 6 7 8 9 10 | public class BasicConfigurationInjection { @Inject private Config config; public void init(@Observes @Initialized(ApplicationScoped.class) Object init) { System.out.println(config.getValue("message", String.class)); } } |
In addition, you can inject a property value to a member variable with the @ConfigProperty
annotation and also specify a default value:
1 2 3 4 5 6 7 | public class BasicConfigurationInjection { @Inject @ConfigProperty(name = "message", defaultValue = "Hello World") private String message; } |
If you don't specify a defaultValue
, and the application can't find a property value in the configured ConfigSources, your application will throw an error during startup:
1 2 3 | The [BackedAnnotatedField] @Inject @ConfigProperty private de.rieckpil.blog.BasicConfigurationInjection.value InjectionPoint dependency was not resolved. Error: java.util.NoSuchElementException: CWMCG0015E: The property not.existing.value was not found in the configuration. at com.ibm.ws.microprofile.config.impl.AbstractConfig.getValue(AbstractConfig.java:175) at [internal classes] |
For a more resilient behavior, or if the config property is optional, you can wrap the value with Java's Optional<T>
class and check its existence during runtime:
1 2 3 4 5 6 7 | public class BasicConfigurationInjection { @Inject @ConfigProperty(name = "my.app.password") private Optional<String> password; } |
Furthermore, you can wrap the property with a Provider<T>
for a more dynamic injection. This ensures that each invocation of Provider.get()
resolves the latest value from the underlying Config
and you are able to change it during runtime.
1 2 3 4 5 6 7 8 9 10 11 | public class BasicConfigurationInjection { @Inject @ConfigProperty(name = "my.app.timeout") private Provider<Long> timeout; public void init(@Observes @Initialized(ApplicationScoped.class) Object init) { System.out.println(timeout.get()); } } |
For the key of the configuration property you might use the dot notation to prevent conflicts and separate domains: my.app.passwords.twitter
.
Configuration sources
The default ConfigSources are the following:
- System property (default ordinal: 400): passed with
-Dmessage=Hello
to the application - Environment variables (default ordinal: 300): OS variables like
export MESSAGE=Hello
- Property file (default ordinal: 100): file
META-INF/microprofile-config.properties
Once the MicroProfile Config runtime finds a property in two places (e.g. property file and environment variable), the value with the higher ordinal source is chosen.
These default configuration sources should cover most of the use cases and support writing cloud-native applications. However, if you need any additional custom ConfigSource
, you can plug-in your own (e.g. fetch configurations from a database or external service).
To provide you an example of a custom ConfigSource
, I'm creating a static source that serves just two properties. Therefore you just need to implement the ConfigSource
interface and its methods
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | public class CustomConfigSource implements ConfigSource { public static final String CUSTOM_PASSWORD = "CUSTOM_PASSWORD"; public static final String MESSAGE = "Hello from custom ConfigSource"; @Override public int getOrdinal() { return 500; } @Override public Map<String, String> getProperties() { Map<String, String> properties = new HashMap<>(); properties.put("my.app.password", CUSTOM_PASSWORD); properties.put("message", MESSAGE); return properties; } @Override public String getValue(String key) { if (key.equalsIgnoreCase("my.app.password")) { return CUSTOM_PASSWORD; } else if (key.equalsIgnoreCase("message")) { return MESSAGE; } return null; } @Override public String getName() { return "randomConfigSource"; } } |
To register this new ConfigSource
you can either bootstrap a custom Config
object with this source:
1 2 3 4 5 6 7 | Config config = ConfigProviderResolver .instance() .getBuilder() .addDefaultSources() .withSources(new CustomConfigSource()) .addDiscoveredConverters() .build(); |
or add the fully-qualified name of the class of the configuration source to the org.eclipse.microprofile.config.spi.ConfigSource
file in /src/main/resources/META-INF/services
:
1 | de.rieckpil.blog.CustomConfigSource |
Using the file approach, the custom source is now part of the ConfigSources
by default.
Configuration converters
Internally the mechanism for MicroProfile Config is purely String–based, type–safety is achieved with Converter
classes. The specification provides default Converter
for converting the configuration property into the known Java types: Integer
, Long
, Float
, Boolean
, Byte
, Short
, Character
, Double
and their primitive counterparts. Furthermore, you can also define a config value with the type java.lang.Class
. All these built-in converters have the priority of 1.
In addition, there are built-in providers for converting properties into Arrays, Lists, Optional<T>
and Provider<T>
.
If the default Converter
doesn't match your requirements and you want e.g. to convert a property into a domain object, you can plug-in a custom Converter<T>
.
For example, I'll convert a config property into a Token
instance:
1 2 3 4 5 6 7 8 9 10 11 12 | public class Token { private String name; private String payload; public Token(String name, String payload) { this.name = name; this.payload = payload; } // getter & setter } |
The custom converter needs to implement the Converter<Token>
interface. The converter method accepts a raw string value and returns the custom domain object, in this case, an instance of Token
:
1 2 3 4 5 6 7 8 9 | public class CustomConfigConverter implements Converter<Token> { @Override public Token convert(String value) { String[] chunks = value.split(","); Token result = new Token(chunks[0], chunks[1]); return result; } } |
To register this converter you can either build your own Config instance and add the converter manually:
1 2 3 4 5 6 7 8 9 | int PRIORITY = 100; Config config = ConfigProviderResolver .instance() .getBuilder() .addDefaultSources() .addDiscoveredConverters() .withConverter(Token.class, PRIORITY, new CustomConfigConverter()) .build(); |
or you can add the fully–qualified name of the class of the converter to the org.eclipse.microprofile.config.spi.Converter
file in /src/main/resources/META-INF/services
:
1 | de.rieckpil.blog.CustomConfigConverter |
Once your converter is registered, you can start using it:
1 | my.app.token=TOKEN_1337, SUPER_SECRET_VALUE |
1 2 3 4 5 6 7 | public class BasicConfigurationInjection { @Inject @ConfigProperty(name = "my.app.token") private Token token; } |
YouTube video for using MicroProfile Config
Watch the following YouTube video of my Getting started with MicroProfile series to see MicroProfile Config in action:
You can find the source code for this blog post on GitHub.
Have fun using MicroProfile Config,
Phil