mp.guides.config.adoc Maven / Gradle / Ivy
///////////////////////////////////////////////////////////////////////////////
Copyright (c) 2019, 2024 Oracle and/or its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
///////////////////////////////////////////////////////////////////////////////
= Helidon MP Config Guide
:description: Helidon configuration
:keywords: helidon, configuration, microprofile, guide
:toc:
:rootdir: {docdir}/../..
include::{rootdir}/includes/mp.adoc[]
This guide describes how to create a sample MicroProfile (MP) project
that can be used to run some basic examples using both default and custom configuration with Helidon MP.
== What You Need
For this 20 minute tutorial, you will need the following:
include::{rootdir}/includes/prerequisites.adoc[tag=prerequisites]
== Getting 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 MP configuration and demonstrates the fundamental concepts using several examples.
Refer to xref:{rootdir}/mp/config/introduction.adoc[Helidon Config] for the full configuration concepts documentation.
=== Create a Sample Helidon MP Project
Use the Helidon MP Maven archetype to create a simple project that can be used for the examples in this guide.
[source,bash,subs="attributes+"]
.Run the Maven archetype:
----
mvn -U archetype:generate -DinteractiveMode=false \
-DarchetypeGroupId=io.helidon.archetypes \
-DarchetypeArtifactId=helidon-quickstart-mp \
-DarchetypeVersion={helidon-version} \
-DgroupId=io.helidon.examples \
-DartifactId=helidon-quickstart-mp \
-Dpackage=io.helidon.examples.quickstart.mp
----
[source,bash]
.The project will be built and run from the `helidon-quickstart-mp` directory:
----
cd helidon-quickstart-mp
----
=== 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 `META-INF/microprofile-config.properties`.
For example, if you specify a custom server port in `META-INF/microprofile-config.properties`
then your server will listen on that port.
A main class is also required to start up the server and run the
application. By default, the Quickstart sample project uses the built-in
Helidon main class. In this guide you want to use your own main class, so you have
more control over the server initialization. First define your own `Main`:
[source,java]
.src/main/java/io/helidon/examples/quickstart/mp/Main.java
----
include::{sourcedir}/mp/guides/ConfigSnippets.java[tag=snippet_1, indent=0]
----
In this class, a `main` method is defined which starts the Helidon MP
server and prints out a message with the listen address.
<1> Notice that
this class has an empty no-args constructor to make sure this class
cannot be instantiated.
<2> The MicroProfile server is started with the default configuration.
Next change the project's `pom.xml` to use your main class:
[source,xml]
.pom.xml
----
io.helidon.examples.quickstart.mp.Main
----
This property will be used to set the `Main-Class` attribute in the application jar's MANIFEST.
In your application code, Helidon uses the default configuration when you create a `Server` object without a custom `Config` object.
See the following code from the project you created.
[source,java]
.View `Main#startServer`:
----
include::{sourcedir}/mp/guides/ConfigSnippets.java[tag=snippet_2, indent=0]
----
<1> There is no `Config` object being used during server creation, so the default configuration is used.
=== 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:
1. Java system properties
2. Environment variables
3. Properties specified in `META-INF/microprofile-config.properties`
Each of these sources specify configuration properties in Java Property format (key/value), like `color=red`. 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.
NOTE: Because environment variable names are restricted to alphanumeric characters and underscores,
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 link:{microprofile-config-spec-url}[Microprofile Config Specifications] 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, `META-INF/microprofile-config.properties`.
There are no environment variable or system property overrides defined.
[source,bash]
.Change `app.greeting` in the `META-INF/microprofile-config.properties` from `Hello` to `HelloFromMPConfig`:
----
app.greeting=HelloFromMPConfig
----
[source,bash]
.Build the application, skipping unit tests, then run it:
----
mvn package -DskipTests=true
java -jar target/helidon-quickstart-mp.jar
----
[source,bash]
.Run the curl command in a new terminal window and check the response:
----
curl http://localhost:8080/greet
----
[source, hocon]
----
{
"message": "HelloFromMPConfig World!" // <1>
}
----
<1> The new `app.greeting` value in `META-INF/microprofile-config.properties` is used.
===== Environment Variable Override
An environment variable has a higher precedence than the configuration properties file.
[source,bash]
.Set the environment variable and restart the application:
----
export APP_GREETING=HelloFromEnvironment
java -jar target/helidon-quickstart-mp.jar
----
[source,bash]
.Invoke the endpoint
----
curl http://localhost:8080/greet
----
[source, hocon]
.JSON response:
----
{
"message": "HelloFromEnvironment World!" // <1>
}
----
<1> The environment variable took precedence over the value in `META-INF/microprofile-config.properties`.
===== System Property Override
A system property has a higher precedence than environment variables.
[source,bash]
.Restart the application with a system property. The `app.greeting` environment variable is still set:
----
java -Dapp.greeting="HelloFromSystemProperty" -jar target/helidon-quickstart-mp.jar
----
[source,bash]
.Invoke the endpoint
----
curl http://localhost:8080/greet
----
[source, hocon]
.JSON response:
----
{
"message": "HelloFromSystemProperty World!" // <1>
}
----
<1> The system property took precedence over both the environment variable and `META-INF/microprofile-config.properties`.
== Accessing Config within an Application
The examples in this section will demonstrate how to access that config data
at runtime. Your application uses the `Config` object to access the in-memory tree, retrieving config data.
The generated project already accesses configuration data in the `GreetingProvider` class as follows:
[source,java]
.View the following code from `GreetingProvider.java`:
----
include::{sourcedir}/mp/guides/ConfigSnippets.java[tag=snippet_3, indent=0]
----
<1> This class is application scoped so a single instance of `GreetingProvider` will be shared across the entire application.
<2> Define a thread-safe reference that will refer to the message member variable.
<3> The value of the configuration property `app.greeting` is injected into the `GreetingProvider`.
constructor as a `String` parameter named `message`.
=== Injecting at Field Level
You can inject configuration at the field level as shown below. Use the `volatile` keyword
since you cannot use `AtomicReference` with field level injection.
[source,yaml]
.Update the `meta-config.yaml` with the following contents:
----
sources:
- type: "classpath"
properties:
resource: "META-INF/microprofile-config.properties" <1>
----
<1> This example only uses the default classpath source.
[source,java]
.Update the following code from `GreetingProvider.java`:
----
include::{sourcedir}/mp/guides/ConfigSnippets.java[tag=snippet_4, indent=0]
----
<1> Inject the value of `app.greeting` into the `GreetingProvider` object.
<2> Define a class member variable to hold the greeting.
[source,bash]
.Build and run the application, then invoke the endpoint
----
curl http://localhost:8080/greet
----
[source, json]
.JSON response:
----
{
"message": "HelloFromMPConfig World!"
}
----
=== Injecting the Config Object
You can inject the `Config` object into the class and access it directly as shown below.
[source,java]
.Replace the `GreetingProvider` class:
----
include::{sourcedir}/mp/guides/ConfigSnippets.java[tag=snippet_5, indent=0]
----
<1> Inject the `Config` object into the `GreetingProvider` object.
<2> Get the `app.greeting` value from the `Config` object and set the member variable.
[source,bash]
.Build and run the application, then invoke the endpoint
----
curl http://localhost:8080/greet
----
[source, json]
.JSON response:
----
{
"message": "HelloFromMPConfig World!"
}
----
=== Navigating the Config Tree
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 the examples to this point. For example `app.greeting` is accessing the `greeting` child node of the `app` parent node.
This simple example below demonstrates how to access a child node as a detached configuration subtree.
[source,yaml]
.Create a file `config-file.yaml` in the `helidon-quickstart-mp` directory and add the following contents:
----
app:
greeting:
sender: Joe
message: Hello-from-config-file.yaml
----
[source,yaml]
.Update the `meta-config.yaml` with the following contents:
----
sources:
- type: "classpath"
properties:
resource: "META-INF/microprofile-config.properties"
- type: "file"
properties:
path: "./config-file.yaml"
----
[source,java]
.Replace `GreetingProvider` class with the following code:
----
include::{sourcedir}/mp/guides/ConfigSnippets.java[tag=snippet_6, indent=0]
----
<1> Get the configuration subtree where the `app.greeting` node is the root.
<2> Get the value from the `message` `Config` node.
<3> Get the value from the `sender` `Config` node.
[source,bash]
.Build and run the application, then invoke the endpoint
----
curl http://localhost:8080/greet
----
[source, json]
.JSON response:
----
{
"message": "Joe says Hello-from-config-file.yaml World!"
}
----
== 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`.
[source,java]
.Update the `Main` class and replace the `buildConfig` method:
----
include::{sourcedir}/mp/guides/ConfigSnippets.java[tag=snippet_7, indent=0]
----
<1> The `app.greeting` value will be fetched from `/etc/config/config-file.properties` within the container.
<2> The server port is specified in `META-INF/microprofile-config.properties` within the `helidon-quickstart-mp.jar`.
[source,java]
.Update the following code from `GreetingProvider.java`:
----
include::{sourcedir}/mp/guides/ConfigSnippets.java[tag=snippet_8, indent=0]
----
[source,bash]
.Build and run the application, then invoke the endpoint
----
curl http://localhost:8080/greet
----
[source, json]
.JSON response:
----
{
"message": "HelloFromConfigFile World!"
}
----
[source,bash]
.Stop the application and build the docker image:
----
docker build -t helidon-config-mp .
----
[source,bash]
.Generate a ConfigMap from `config-file.properties`:
----
kubectl create configmap helidon-configmap --from-file config-file.properties
----
[source,bash]
.View the contents of the ConfigMap:
----
kubectl get configmap helidon-configmap -o yaml
----
[source, yaml]
----
apiVersion: v1
data:
config-file.properties: | # <1> <2>
app.greeting=HelloFromConfigFile
kind: ConfigMap
----
<1> The file `config-file.properties` will be created within the Kubernetes container.
<2> The `config-file.properties` file will have this single property defined.
[source,yaml]
.Create the Kubernetes YAML specification, named `k8s-config.yaml`, with the following contents:
----
kind: Service
apiVersion: v1
metadata:
name: helidon-config # <1>
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 # <2>
selector:
matchLabels:
app: helidon-config
template:
metadata:
labels:
app: helidon-config
version: v1
spec:
containers:
- name: helidon-config
image: helidon-config-mp
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
volumeMounts:
- name: config-volume
mountPath: /etc/config # <3>
volumes:
- name: config-volume
configMap:
# Provide the name of the ConfigMap containing the files you want
# to add to the container
name: helidon-configmap # <4>
----
<1> A service of type `NodePort` that serves the default routes on port `8080`.
<2> A deployment with one replica of a pod.
<3> Mount the ConfigMap as a volume at `/etc/config`. This is where Kubernetes will create `config-file.properties`.
<4> Specify the ConfigMap which contains the configuration data.
[source,bash]
.Create and deploy the application into Kubernetes:
----
kubectl apply -f ./k8s-config.yaml
----
[source,bash]
.Get the service information:
----
kubectl get service/helidon-config
----
[source,bash]
----
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
helidon-config NodePort 10.99.159.2 8080:31143/TCP 8s # <1>
----
<1> A service of type `NodePort` that serves the default routes on port `31143`.
[source,bash]
.Verify the configuration endpoint using port `31143`, your port will likely be different:
----
curl http://localhost:31143/greet
----
[source, hocon]
.JSON response:
----
{
"message": "HelloFromConfigFile World!" // <1>
}
----
<1> The greeting value from `/etc/config/config-file.properties` within the container was used.
You can now delete the Kubernetes resources that were just created during this example.
[source,bash]
.Delete the Kubernetes resources:
----
kubectl delete -f ./k8s-config.yaml
kubectl delete configmap helidon-configmap
----
== Summary
This guide has demonstrated how to use basic Helidon configuration features. For more information about using the advanced Helidon configuration features, including mutability support and extensions, see
xref:{rootdir}/mp/config/introduction.adoc[Helidon Configuration].
== References
Refer to the following references for additional information:
* link:{microprofile-config-spec-url}[MicroProfile Config specification]
* link:{microprofile-config-javadoc-url}[MicroProfile Config Javadoc]
* link:{javadoc-base-url}/index.html?overview-summary.html[Helidon Javadoc]
© 2015 - 2025 Weber Informatics LLC | Privacy Policy