Based on the MicroProfile Config API, Quarkus offers a potent tool to configure an application.
Configuration values can be obtained out-of-the-box from a cascade of configuration sources. And one of the best-known sources is probably the
application.properties file. And values from these sources can be injected into a field of a bean using the annotation
There may be the need to get configuration properties from other sources than the built-in ones of Quarkus. Such sources could be, for example:
- Configuration files from other applications on the system.
- Properties that we can only be calculated dynamically at runtime.
- Standardized configuration files for used libraries.
To achieve this, the MicroProfile Config API provides us with several extension points with which we can provide properties values via a custom configuration source.
Our custom configuration source must implement the interface
MicroProfile will load the implementation of this interface at runtime using Java’s service loader mechanism. For this, we need to write the full name of our class (package name + class name) in a text file with the name
org.eclipse.microprofile.config.spi.ConfigSource, which gets placed into the directory
The interface requires us to implement at least three methods, which we will look at in more detail in the following chapters.
Let’s start with the method
Set<String> getPropertyNames(). This method can return the names of the properties that our configuration source provides. Depending on the usage of our configuration source, it may be sufficient to return only a subset or even an empty set.
For pure injecting of properties, as we saw at the beginning of the article, this set is unnecessary (we will see why in the next chapter).
This method gets primarily used when someone whats to get all the properties of our config source at once. The interface offers for that purpose the method
ConfigSource#getProperties() which returns a
Map<String, String>. The default implementation of this method iterates over all returned
getPropertyNames() to retrieve the individual values.
We should note that
getPropertyNames() (and also
getProperties()) can be called several times. If we always return a fixed set, we should calculate it only once and return the reference to the set.
The next method we need to take care of is
String getValue(String propertyName), which provides the value of a property with the given name. The critical point here is that this method gets called for all properties in the entire application for which MicroProfile could not determine a value yet. Therefore, if we cannot resolve a value ourselves or are not responsible for a property, we must return
We can see a limitation of the MicroProfile Config API based on the declaration of the method: We can only work with simple string property values. MicroProfile’s properties injection is powerful enough to map certain simple data types, but we cannot use more complex objects here. (E.g., the property string “true” can be injected into a
From the architecture of the second method, we can also see another interesting ability: Overriding property values that come from other configuration sources. The evaluation order of configuration sources gets determined by the return value of the method
ConfigSource#getOrdinal(). If we return an integer value higher than that of another configuration source, we are first asked for a value and can override it. The JavaDoc of the
getOrdinal() method has a good discussion about what ordinal number is appropriate in what situation.
The last method we need to implement is
String getName(). It should return a descriptive name of our configuration. This name may be used later by MicroProfile for logging, for example. This name also gets used to determine the ranking order if two sources have the
In the following example, we read a custom properties file and make its names and values available via the MicroProfile Config API:
Sometimes we get into the situation where we want to create an instance of our
ConfigSource implementation depending on certain factors.
The most classic example is that we want to provide different properties depending on the current profile. Or that we want to make our configuration source dependent on other properties.
We can implement a
io.smallrye.config.ConfigSourceFactory to accomplish this, which will create and return our custom configuration source. MircoProfile also loads this factory via Java’s server loader mechanism. For this, we place a file with the name
io.smallrye.config.ConfigSourceFactory into the directory
META-INF/services/, which contains the full name of our factory class (package name + class name). Note here that if we use a factory, the individual
ConfigSources do not have to get loaded via the service loader mechanism.
In the following example factory, we create a custom configuration source based on the current profile:
ConfigSourceContext parameter, we can also access all other properties. For example, the current HTTP port: