- Helidon SE Config Guide
This guide describes how to create a sample Helidon SE project that can be used to run some basic examples using both default and custom configuration.
What you need
For this 20 minute tutorial, you will need the following:
| A Helidon SE Application | You can use your own application or use the Helidon SE Quickstart to create a sample application. |
| Java SE 17 (Open JDK 17) | Helidon requires Java 17+. |
| Maven 3.6.1+ | Helidon requires Maven 3.6.1+. |
| Docker 18.09+ | You need Docker if you want to build and deploy Docker containers. |
| Kubectl 1.16.5+ | If you want to deploy to Kubernetes, you need kubectl and a Kubernetes cluster (you can install one on your desktop. |
java -version
mvn --version
docker --version
kubectl version# On Mac
export JAVA_HOME=`/usr/libexec/java_home -v 17`
# On Linux
# Use the appropriate path to your JDK
export JAVA_HOME=/usr/lib/jvm/jdk-17Getting Started with Configuration
Helidon provides a very flexible and comprehensive configuration system, offering you many application configuration choices. You can include configuration data from a variety of sources using different formats, like JSON and YAML. Furthermore, you can customize the precedence of sources and make them optional or mandatory. This guide introduces Helidon SE configuration and demonstrates the fundamental concepts using several examples. Refer to Helidon Config for the full configuration concepts documentation.
Create a Sample Helidon SE Project
Use the Helidon SE Maven archetype to create a simple project that can be used for the examples in this guide.
mvn -U archetype:generate -DinteractiveMode=false \
-DarchetypeGroupId=io.helidon.archetypes \
-DarchetypeArtifactId=helidon-quickstart-se \
-DarchetypeVersion=3.2.16 \
-DgroupId=io.helidon.examples \
-DartifactId=helidon-quickstart-se \
-Dpackage=io.helidon.examples.quickstart.sehelidon-quickstart-se directory:cd helidon-quickstart-seConfiguration Formats
Helidon configuration sources can use different formats for the configuration data. You can specify the format on a per-source bases, mixing and matching formats as required. Here are the supported formats, each with the extension name you should use. By default, Helidon will determine the media type based on the extension name.
Java Property (.properties)
JSON (.json)
YAML (.yaml)
HOCON (.conf)
The remainder of this document will use these formats in examples and show you how to configure Helidon to parse them.
Default Configuration
Helidon has an internal configuration, so you are not required to provide any configuration data for your application, though in practice you most likely would. By default, that configuration can be overridden from three sources: system properties, environment variables, and the contents of application.yaml in the classpath. For example, if you specify a custom server port in application.yaml then your server will listen on that port.
In your application code, Helidon uses the default configuration when you create a default Config object. See the following code from the project you created.
Main.startServer:Config config = Config.create(); - The
Configobject is created with default settings.
Source Precedence for Default Configuration
In order to properly configure your application using configuration sources, you need to understand the precedence rules that Helidon uses to merge your configuration data. By default, Helidon will use the following sources in precedence order:
- Java system properties
- Environment variables
- Configuration specified in
application.yaml
If any of the Helidon required properties are not specified in one of these source, like server.port, then Helidon will use a default value.
Because environment variable names are restricted to alphanumeric characters and underscore, Helidon adds aliases to the environment configuration source, allowing entries with dotted and/or hyphenated keys to be overridden. For example, this mapping allows an environment variable named "APP_GREETING" to override an entry key named "app.greeting". In the same way, an environment variable named "APP_dash_GREETING" will map to "app-greeting". See Advanced Config for more information.
The following examples will demonstrate the default precedence order.
Default Configuration Resource
Change a configuration parameter in the default configuration resource file, application.yaml. There are no environment variable or system property overrides defined.
app.greeting in resources/application.yaml as follows:app:
greeting: HelloFrom-application.yamlmvn package -DskipTests=true
java -jar target/helidon-quickstart-se.jarcurl http://localhost:8080/greet{
"message": "HelloFrom-application.yaml World!"
}- The new
app.greetingvalue inapplication.yamlis used.
System Property Override
A system property has a higher precedence than application.yaml.
app.greeting environment variable is still set:java -Dapp.greeting="HelloFromSystemProperty" -jar target/helidon-quickstart-se.jarcurl http://localhost:8080/greet{
"message": "HelloFromSystemProperty World!"
}- The system property took precedence over
application.yaml.
Environment Variable Override
An environment variable has a higher precedence than the system property.
export APP_GREETING=HelloFromEnvironment
java -Dapp.greeting="HelloFromSystemProperty" -jar target/helidon-quickstart-se.jarcurl http://localhost:8080/greet{
"message": "HelloFromEnvironment World!"
}- The environment variable
APP_GREETINGtook precedence over the system property and the value inapplication.yaml.
Custom Configuration Sources
To use custom configuration sources, your application needs to specify the sources when it creates Config object. By doing this, you are in full control of all configuration sources and precedence. By default, the environment variable and system property sources are enabled, but you can disable them using the disableEnvironmentVariablesSource and disableSystemPropertiesSource methods.
This section will show you how to use a custom configuration with various sources, formats, and precedence rules.
Full List of Configuration Sources
Here is the full list of external config sources that you can use programmatically.
- Environment variables - the property is a name/value pair.
- Java system properties - the property is a name/value pair.
- Resources in the classpath - the contents of the resource is parsed according to its inferred format.
- File - the contents of the file is parsed according to its inferred format.
- Directory - each non-directory file in the directory becomes a config entry: the file name is the key. and the contents of that file are used as the corresponding config String value.
- A URL resource - contents is parsed according to its inferred format.
You can also define custom sources, such as Git, and use them in your Helidon application. See Advanced Config for more information.
Classpath Sources
The first custom resource example demonstrates how to add a second internal configuration resource that is discovered in the classpath. The code needs to build a Config object, which in turn is used to build the Server object. The Config object can be built using a Config.Builder, which lets you inject any number of sources into the builder. Furthermore, you can set precedence for the sources. The first source has highest precedence, then the next has second highest, and so forth.
config.properties to the resources directory with the following contents:app.greeting=HelloFrom-config.propertiesMain class; 1) Add new imports, 2) Replace the Config.create() call with buildConfig(), and 3) Add buildConfig method:import static io.helidon.config.ConfigSources.classpath;
//...
static WebServer startServer() throws IOException {
//...
Config config = buildConfig();
private static Config buildConfig() {
return Config.builder()
.disableEnvironmentVariablesSource()
.sources(
classpath("config.properties"),
classpath("application.yaml"))
.build();
}- Add new import statement.
- Call the new
buildConfigmethod to build aConfigobject. - Disable the environment variables as a source.
- Specify the new config.properties resource that is in the
classpath. - You must specify the existing
application.yamlor Helidon will not use it as a configuration source even though it is considered a default source.
curl http://localhost:8080/greet
...
{
"message": "HelloFrom-config.properties World!"
}- The greeting was picked up from
config.properties, overriding the value inapplication.yaml.
It is important to remember that configuration from all sources is merged internally. If you have the same configuration property in multiple sources, then only the one with highest precedence will be used at runtime. This is true even the same property comes from sources with different formats.
Swap the source order and run the test again.
Main class and replace the buildConfig method: private static Config buildConfig() {
return Config.builder()
.disableEnvironmentVariablesSource()
.sources(
classpath("application.yaml"),
classpath("config.properties"))
.build();
}- Swap the source order, putting
application.yamlfirst.
curl http://localhost:8080/greet
...
{
"message": "HelloFrom-application.yaml World!"
}- The file
application.yamlwas used to get the greeting since it now has precedence overconfig.properties.
External File Sources
You can move all or part of your configuration to external files, making them optional or mandatory. The obvious advantage to this approach is that you do not need to rebuild your application to change configuration. In the following example, the app.greeting configuration property will be added to config-file.properties.
disableEnvironmentVariablesSource doesn’t need to be called:unset APP_GREETINGconfig-file.properties in the helidon-quickstart-se directory with the following contents:app.greeting=HelloFrom-config-file.propertiesMain class; 1) Add new import and 2) Replace the buildConfig method:import static io.helidon.config.ConfigSources.file;
...
private static Config buildConfig() {
return Config.builder()
.sources(
file("config-file.properties"),
classpath("application.yaml"))
.build();
}- Add a mandatory configuration file.
curl http://localhost:8080/greet
...
{
"message": "HelloFrom-config-file.properties World!"
}- The configuration property from the file
config-file.propertiestakes precedence.
If you want the configuration file to be optional, you must use the optional method with sources, otherwise Helidon will generate an error during startup as shown below. This is true for both file and classpath sources. By default, these sources are mandatory.
Main class and replace the buildConfig method: private static Config buildConfig() {
return Config.builder()
.sources(
file("missing-file"),
classpath("application.yaml"))
.build();
}- Specify a file that doesn’t exist.
Exception in thread "main" io.helidon.config.ConfigException: Cannot load data from mandatory source FileConfig[missing-file]. File `missing-file` not found.To fix this, use the optional method as shown below, then rerun the test.
...
file("missing-file").optional(), - The
missing-fileconfiguration file is now optional.
Directory Source
A directory source treats every file in the directory as a key, and the file contents as the value. The following example includes a directory source as highest precedence.
helidon-quickstart-se/conf then create a file named app.greeting in that directory with the following contents:HelloFromFileInDirectoryConfMain class; 1) Add new import and 2) Replace the buildConfig method:import static io.helidon.config.ConfigSources.directory;
...
private static Config buildConfig() {
return Config.builder()
.sources(
directory("conf"),
classpath("config.properties").optional(),
classpath("application.yaml"))
.build();
}- Add a mandatory configuration directory.
curl http://localhost:8080/greet
...
{
"message": "HelloFromFileInDirectoryConf World!"
}- The greeting was fetched from the file named
app.greeting.
Exceeding Three Sources
If you have more than three sources, you can use the addSource method as shown below.
Main class and replace the buildConfig method: private static Config buildConfig() {
return Config.builder()
.addSource(directory("conf"))
.addSource(file("config-file.properties"))
.addSource(classpath("config.properties").optional())
.addSource(classpath("application.yaml"))
.build();
}- Add each config source using the
addSourcemethod.
curl http://localhost:8080/greet
...
{
"message": "HelloFromFileInDirectoryConf World!"
}Configuration Profiles
Instead of directly specifying the configuration sources in your code, you can use a profile file that declares the configuration sources and their attributes.
Simplest way to use a profile is to define a config-profile.yaml (and possible other files, such as config-profile-dev.yaml for dev profile) on classpath or on file system, and create config using Config.create(). The profile can be changed by a system property config.profile, or using an environment variable HELIDON_CONFIG_PROFILE.
Profile file can use any supported format, following example is using YAML.
config-profile.yaml in the helidon-quickstart-se directory with the following contents:sources:
- type: "classpath"
properties:
resource: "application.yaml" - The source type.
- The name of the mandatory configuration resource.
Main class and replace the buildConfig method: private static Config buildConfig() {
return Config.create();
}- Will use
config-profile.yamlby default
curl http://localhost:8080/greet
...
{
"message": "HelloFrom-application.yaml World!"
}- The
application.yamlresource file was used to get the greeting.
The source precedence order in a profile file is the order of appearance in the file. This is demonstrated below where the config-file.properties has highest precedence.
config-profile.yaml file:sources:
- type: "file"
properties:
path: "./config-file.properties"
- type: "classpath"
properties:
resource: "application.yaml"
- type: "file"
properties:
path: "optional-config-file"
optional: true - The source type specifies a file.
- The name of the mandatory configuration file.
- Specify that the
optional-config-filefile is optional.
curl http://localhost:8080/greet
...
{
"message": "HelloFrom-config-file.properties World!"
}- The
config-file.propertiessource now takes precedence.
When using a profile file, you need to explicitly include both environment variables and system properties as a source if you want to use them.
config-profile.yaml file:sources:
- type: "environment-variables"
- type: "system-properties"
- type: "classpath"
properties:
resource: "application.yaml"
- type: "file"
properties:
path: "./config-file.properties"- Environment variables are now used as a source.
- System properties are now used as a source.
You can re-run the previous tests that exercised environment variables and system properties. Swap the two types to see the precedence change. Be sure to unset APP_GREETING after you finish testing.
Accessing Config within an Application
You have used Helidon to customize configuration behavior from your code using the Config and Config.Builder classes. As discussed previously, Helidon reads configuration from a config source, which uses a config parser to translate the source into an in-memory tree which represents the configuration’s structure and values. Helidon offers a variety of methods to access in-memory configuration. These can be categorized as key access or tree navigation. You have been using key access for all of the examples to this point. For example app.greeting is accessing the greeting child node of the app parent node. There are many options for access this data using navigation methods as described in Hierarchical Config and Advanced Config>.
Accessing Config Using Keys or Navigation
The simplest way to access configuration data is using a key, as shown below in the GreetService class. The key can be composite as shown below:
GreetService constructor: GreetService(Config config) {
greeting.set(config.get("app.greeting").asString().orElse("Ciao"));
}- Get the
app.greetingnode using a composite key.
You can also access the same greeting by navigating the nodes.
GreetService constructor with the following code: GreetService(Config config) {
greeting.set(config.get("app").get("greeting").asString().orElse("Ciao"));
}- Get the
appnode, then get the child node,greeting.
curl http://localhost:8080/greet
...
{
"message": "HelloFrom-application.yaml World!"
}Using Filters and Collections
The Helidon Config class provides several methods that allow you to filter and customize the traversal of the configuration tree. The example below shows how to get the greeting node when you only know it is somewhere in the app subtree.
config-profile.yaml file:sources:
- type: "classpath"
properties:
resource: "application.yaml"application.yaml resource file:app:
child1: child1-node
child2:
child2a:
greeting: HelloFrom-application.yaml under child2a
child3: child3-nodeGreetService.java file; 1) Add new imports and 2) Replace the GreetService constructor with the following:
import java.util.List;
import java.util.stream.Collectors;
GreetService(Config config) {
List<Config> appGreetings = config.get("app")
.traverse()
.filter(node -> node.name().equals("greeting"))
.collect(Collectors.toList());
greeting.set(appGreetings.get(0).asString().get());
}- Add new imports.
- Traverse the entire subtree of the
appnode. - Include only nodes that have the name
greeting. - Add the
greetingnode to the collection.
curl http://localhost:8080/greet
...
{
"message": "HelloFrom-application.yaml under child2a World!"
}Reacting to Configuration Updates
Even though in-memory config trees are immutable, the config system internally records configuration source metadata that allows it to watch sources for changes. Your application listens for updates to the underlying config sources and reacts to the changes. See Config Mutability Support for a full discussion on this topic. The following example demonstrates how to listen and react to configuration changes.
config-profile.yaml file:sources:
- type: "file"
properties:
path: "./config-file.properties"
change-watcher:
type: "file"
- type: "classpath"
properties:
resource: "application.yaml"GreetService class; 1) Add new import and 2) Replace the GreetService constructor:import java.util.function.Consumer;
...
GreetService(Config config) {
Config greetingConfig = config.get("app.greeting");
greeting.set(greetingConfig.asString().orElse("Ciao"));
greetingConfig.onChange((Consumer<Config>) cfg -> greeting.set(cfg.asString().orElse("Ciao")));
}- Get the greeting
Confignode. - Register a listener that will get called by Helidon when the configuration changes. The listener will update the greeting with the new value.
curl http://localhost:8080/greet
...
{
"message": "HelloFrom-config-file.properties World!"
}config-file.properties with the following contents:app.greeting=Updated HelloFrom-config-file.propertiescurl http://localhost:8080/greet
...
{
"message": "Updated HelloFrom-config-file.properties World!"
}- The application reacted to the change and updated the greeting.
Integration with Kubernetes
The following example uses a Kubernetes ConfigMap to pass the configuration data to your Helidon application deployed to Kubernetes. When the pod is created, Kubernetes will automatically create a local file within the container that has the contents of the configuration file used for the ConfigMap. This example will create the file at /etc/config/config-file.properties.
application.yaml resource file:app:
greeting: "Hello"Main class and replace the buildConfig method: private static Config buildConfig() {
return Config.builder()
.sources(
file("/etc/config/config-file.properties").optional(),
classpath("application.yaml"))
.build();
}- The
app.greetingvalue will be fetched from/etc/config/config-file.propertieswithin the container. - The server port is specified in
application.yamlwithin thehelidon-quickstart-se.jar.
GreetService constructor with the following code: GreetService(Config config) {
greeting.set(config.get("app.greeting").asString().orElse("Ciao"));
}curl http://localhost:8080/greet
...
{
"message": "Hello World!"
}- The greeting came from
application.yamlsince/etc/config/config-file.propertiesdoesn’t exist.
docker build -t helidon-config-se .config-file.properties:kubectl create configmap helidon-configmap --from-file config-file.propertieskubectl get configmap helidon-configmap -o yaml
...
apiVersion: v1
data:
config-file.properties: |
app.greeting=Updated HelloFrom-config-file.properties
kind: ConfigMap
...- The file
config-file.propertieswill be created within the Kubernetes container. - The
config-file.propertiesfile will have this single property defined.
k8s-config.yaml, with the following contents:kind: Service
apiVersion: v1
metadata:
name: helidon-config
labels:
app: helidon-config
spec:
type: NodePort
selector:
app: helidon-config
ports:
- port: 8080
targetPort: 8080
name: http
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: helidon-config
spec:
replicas: 1
selector:
matchLabels:
app: helidon-config
template:
metadata:
labels:
app: helidon-config
version: v1
spec:
containers:
- name: helidon-config
image: helidon-config-se
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
# Provide the name of the ConfigMap containing the files you want
# to add to the container
name: helidon-configmap - A service of type
NodePortthat serves the default routes on port8080. - A deployment with one replica of a pod.
- Mount the ConfigMap as a volume at
/etc/config. This is where Kubernetes will createconfig-file.properties. - Specify the ConfigMap which contains the configuration data.
kubectl apply -f ./k8s-config.yamlkubectl get service/helidon-configNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
helidon-config NodePort 10.99.159.2 <none> 8080:31143/TCP 8s - A service of type
NodePortthat serves the default routes on port31143.
31143, your port will likely be different:curl http://localhost:31143/greet
...
{
"message": "Updated HelloFrom-config-file.properties World!"
}- The greeting value from
/etc/config/config-file.propertieswithin the container was used.
You can now delete the Kubernetes resources that were just created during this example.
kubectl delete -f ./k8s-config.yaml
kubectl delete configmap helidon-configmapSummary
This guide has demonstrated how to use basic Helidon configuration features. The full configuration documentation, starting with the introduction section at Helidon Config has much more information including the following:
Architecture
Parsers
Extensions
Filters
Hierarchical Access
Property Mapping
Mutability Support
and more…
Refer to the following references for additional information: