Property Placeholder Functions

The Properties component includes the following functions out of the box:

  • env - A function to lookup the property from OS environment variables

  • sys - A function to lookup the property from Java JVM system properties

  • bean - A function to lookup the property from the return value of bean’s method (requires camel-bean JAR)

  • boolean - A function to evaluate if a property key matches a condition and returns either true or false

  • service - A function to lookup the property from OS environment variables using the service naming idiom

  • service.name - A function to lookup the property from OS environment variables using the service naming idiom returning the hostname part only

  • service.port - A function to lookup the property from OS environment variables using the service naming idiom returning the port part only

These functions are intended to make it easy to lookup values from the environment, as shown in the example below:

  • Java

  • XML

  • YAML

from("direct:start")
  .to("{{env:SOMENAME}}")
  .to("{{sys:MyJvmPropertyName}}");
<route>
    <from uri="direct:start"/>
    <to uri="{{env:SOMENAME}}"/>
    <to uri="{{sys:MyJvmPropertyName}}"/>
</route>
- route:
    from:
      uri: direct:start
      steps:
        - to:
            uri: "{{env:SOMENAME}}"
        - to:
            uri: "{{sys:MyJvmPropertyName}}"

You can use default values as well, so if the property does not exist, you can define a default value as shown below, where the default value is a log:foo and log:bar value.

  • Java

  • XML

  • YAML

from("direct:start")
  .to("{{env:SOMENAME:log:foo}}")
  .to("{{sys:MyJvmPropertyName:log:bar}}");
<route>
    <from uri="direct:start"/>
    <to uri="{{env:SOMENAME:log:foo}}"/>
    <to uri="{{sys:MyJvmPropertyName:log:bar}}"/>
</route>
- route:
    from:
      uri: direct:start
      steps:
        - to:
            uri: "{{env:SOMENAME:log:foo}}"
        - to:
            uri: "{{sys:MyJvmPropertyName:log:bar}}"

The boolean function is intended for more flexibility when enabling or disabling EIP options.

For example given we have a property with key region that has the value EMEA:

region = EMEA

We can then configure EIPs whether they are disabled based on this condition as follows:

  • Java

  • XML

  • YAML

from("direct:start")
    .choice().disabled("{{boolean:region == 'EMEA'}}")
        .when(simple("${header.RetryAttempts} == null"))
            .setProperty("HttpMessageMethod", constant("SET"))
            .process("SetOriginalMessageProcessor")
        .end();
<route>
    <from uri="direct:start"/>
    <choice disabled="{{boolean:region == 'EMEA'}}">
        <when>
            <simple>${header.RetryAttempts} == null</simple>
            <setProperty name="HttpMessageMethod">
                <constant>SET</constant>
            </setProperty>
            <process ref="SetOriginalMessageProcessor"/>
        </when>
    </choice>
</route>
- route:
    from:
      uri: direct:start
      steps:
        - choice:
            disabled: "{{boolean:region == 'EMEA'}}"
            when:
            - expression:
                simple:
                  expression: "${header.RetryAttempts} == null"
              steps:
              - setProperty:
                  name: "HttpMessageMethod"
                  expression:
                    constant: "SET"
              - process:
                  ref: "SetOriginalMessageProcessor"

Property placeholders can also be used in application.properties files, also with the functions such as ENV as shown:

# server name read from ENV and fallback to use localhost as default value
myserver = {{env:MY_SERVER_NAME:localhost}}
The boolean function uses the Simple language which has many functions and operators to build the condition.

The service function is for looking up a service which is defined using OS environment variables using the service naming idiom, to refer to a service location using hostname : port

  • NAME_SERVICE_HOST

  • NAME_SERVICE_PORT

in other words the service uses _SERVICE_HOST and _SERVICE_PORT as prefix. So if the service is named FOO, then the OS environment variables should be set as

export $FOO_SERVICE_HOST=myserver
export $FOO_SERVICE_PORT=8888

For example if the FOO service a remote HTTP service, then we can refer to the service in the Camel endpoint uri, and use the HTTP component to make the HTTP call:

  • Java

  • XML

  • YAML

from("direct:start")
  .to("http://{{service:FOO}}/myapp");
<route>
    <from uri="direct:start"/>
    <to uri="http://{{service:FOO}}/myapp"/>
</route>
- route:
    from:
      uri: direct:start
      steps:
        - to:
            uri: "http://{{service:FOO}}/myapp"

And we can use default values if the service has not been defined, for example to call a service on localhost, maybe for unit testing.

  • Java

  • XML

  • YAML

from("direct:start")
  .to("http://{{service:FOO:localhost:8080}}/myapp");
<route>
    <from uri="direct:start"/>
    <to uri="http://{{service:FOO:localhost:8080}}/myapp"/>
</route>
- route:
    from:
      uri: direct:start
      steps:
        - to:
            uri: "http://{{service:FOO:localhost:8080}}/myapp"

The bean function (you need to have camel-bean JAR on classpath) is for looking up the property from the return value of bean’s method.

Assuming we have registered a bean named 'foo' that has a method called 'bar' that returns a directory name, then we can refer to the bean’s method in the camel endpoint url, and use the file component to poll a directory:

  • Java

  • XML

  • YAML

from("file:{{bean:foo.bar}}")
  .to("direct:result");
<route>
    <from uri="file:{{bean:foo.bar}}"/>
    <to uri="direct:result"/>
</route>
- route:
    from:
      uri: file:{{bean:foo.bar}}
      steps:
        - to:
            uri: "direct:result"
The method must be a public no-arg method (i.e. no parameters) and return a value such as a String, boolean, int.

Using Kubernetes property placeholder functions

The camel-kubernetes component include the following functions:

  • configmap - A function to lookup the string property from Kubernetes ConfigMaps.

  • configmap-binary - A function to lookup the binary property from Kubernetes ConfigMaps.

  • secret - A function to lookup the string property from Kubernetes Secrets.

  • secret-binary - A function to lookup the binary property from Kubernetes Secrets.

The syntax for both functions are:

configmap:name/key[:defaultValue]

Where the default value is optional, for example the following will lookup myKey, and fail if there is no such configmap.

configmap:mymap/mykey

In this example then it would not fail as a default value is provided:

configmap:mymap/mykey:123

If the value stored in the configmap is in binary format, so it is stored as Binary Data, it will be downloaded in a file, and it returns the absolute path of the file

configmap-binary:mymap/mybinkey

it returns a path like /tmp/camel11787545916150467474/mybinkey

Before the Kubernetes property placeholder functions can be used they need to be configured with either (or both)

  • path - A mount path that must be mounted to the running pod, to load the configmaps or secrets from local disk.

  • kubernetes client - Autowired An io.fabric8.kubernetes.client.KubernetesClient instance to use for connecting to the Kubernetes API server.

Camel will first use mount paths (if configured) to lookup, and then fallback to use the KubernetesClient.

Configuring mount paths for ConfigMaps and Secrets

The configuration of the mount path are used by the given order:

  1. Reading configuration property with keys camel.kubernetes-config.mount-path-configmaps and camel.kubernetes-config.mount-path-secrets.

  2. Use JVM system property with key camel.k.mount-path.configmaps and camel.k.mount-path.secrets (Camel K compatible).

  3. Use OS ENV variable with key CAMEL_K_MOUNT_PATH_CONFIGMAPS and CAMEL_K_MOUNT_PATH_SECRETS (Camel K compatible).

For example to use /etc/camel/resources/ as mount path, you can configure this in the application.properties:

camel.kubernetes-config.mount-path-configmaps = /etc/camel/myconfig/
camel.kubernetes-config.mount-path-secrets = /etc/camel/mysecrets/

Configuring Kubernetes Client

Camel will autowire the KubernetesClient if a single instance of the client exists in the running application (lookup via the Registry). Otherwise, a new KubernetesClient is created. The client can be configured from either

  • Using camel.kubernetes-config.client. properties (see below for example)

  • Attempt to auto-configure itself by a combination of OS Environment variables, reading from ~./kube/config configuration, and service account token file. For more details see the https://github.com/fabric8io/kubernetes-client documentation.

You most likely only need to explicit configure the KubernetesClient when you want to connect from a local computer to a remote Kubernetes cluster, where you can specify various options, such as the masterUrl and oauthToken as shown:

camel.kubernetes-config.client.masterUrl = https://127.0.0.1:50179/
camel.kubernetes-config.client.oauthToken = eyJhbGciOiJSUzI1NiIsImtpZCI...
The KubernetesClient has many options, see the Kubernetes Client documentation for more information for these options.

If you only use mount paths, then it is good practice to disable KubernetesClient which can be done by setting enabled to false as show:

camel.kubernetes-config.client-enabled = false

When running your Camel applications inside an existing Kubernetes cluster, then you often would not need to explicit configure the KubernetesClient and can rely on default settings.

If you use Camel Quarkus, then it is recommended to use their https://quarkus.io/guides/kubernetes-config which automatic pre-configure the KubernetesClient which Camel then will reuse.

Using configmap with Kubernetes

Given a configmap named myconfig in Kubernetes that has two entries:

drink = beer
first = Carlsberg

Then these values can be used in your Camel routes such as:

  • Java

  • XML

  • YAML

from("direct:start")
  .log("What {{configmap:myconfig/drink}} do you want?")
  .log("I want {{configmap:myconfig/first}}");
  <route>
    <from uri="direct:start"/>
    <log message="What {{configmap:myconfig/drink}} do you want?"/>
    <log message="I want {{configmap:myconfig/first}}"/>
  </route>
- route:
    from:
      uri: direct:start
      steps:
        - log:
            message: "What {{configmap:myconfig/drink}} do you want?"
        - log:
            message: "I want {{configmap:myconfig/first}}"

You can also provide a default value in case a key does not exist, such as Heiniken being the default value:

  • Java

  • XML

  • YAML

from("direct:start")
  .log("What {{configmap:myconfig/drink}} do you want?")
  .log("I want {{configmap:myconfig/second:Heineken}}");
  <route>
    <from uri="direct:start"/>
    <log message="What {{configmap:myconfig/drink}} do you want?"/>
    <log message="I want {{configmap:myconfig/second:Heineken}}"/>
  </route>
- route:
    from:
      uri: direct:start
      steps:
        - log:
            message: "What {{configmap:myconfig/drink}} do you want?"
        - log:
            message: "I want {{configmap:myconfig/second:Heineken}}"

Using secrets with Kubernetes

Camel reads ConfigMaps from the Kubernetes API Server. And when RBAC is enabled on the cluster, the ServiceAccount that is used to run the application needs to have the proper permissions for such access.

A secret named mydb could contain username and passwords to connect to a database such as:

myhost = killroy
myport = 5555
myuser = scott
mypass = tiger

This can be used in Camel with for example the Postgres Sink Kamelet:

  • Java

  • XML

  • YAML

String sink =
"""
 kamelet:postgresql-sink?serverName={{secret:mydb/myhost}}?
 &serverPort={{secret:mydb/myport}}
 &username={{secret:mydb/myuser}}
 &password={{secret:mydb/mypass}}
 &databaseName=cities
 &query=INSERT INTO accounts (username,city) VALUES (:#username,:#city)
""";

from("direct:rome")
  .setBody(constant("{ \"username\":\"oscerd\", \"city\":\"Rome\"}"))
  .to(sink);
  <route>
    <from uri="direct:rome"/>
    <setBody>
      <constant>{ "username":"oscerd", "city":"Rome"}</constant>
    </setBody>
    <to uri="kamelet:postgresql-sink?serverName={{secret:mydb/myhost}}
             &amp;serverPort={{secret:mydb/myport}}
             &amp;username={{secret:mydb/myuser}}
             &amp;password={{secret:mydb/mypass}}
             &amp;databaseName=cities
             &amp;query=INSERT INTO accounts (username,city) VALUES (:#username,:#city)"/>
  </route>
- route:
    from:
      uri: direct
      parameters:
        name: rome
      steps:
        - setBody:
            expression:
              constant:
                expression: "{ \"username\":\"oscerd\", \"city\":\"Rome\"}"
        - to:
            uri: kamelet
            parameters:
              templateId: postgresql-sink
              serverName: "{{secret:mydb/myhost}}"
              serverPort: "{{secret:mydb/myport}}"
              username: "{{secret:mydb/myuser}}"
              password: "{{secret:mydb/mypass}}"
              databaseName: 'cities'
              query: "INSERT INTO accounts (username,city) VALUES (:#username,:#city)"

The postgres-sink Kamelet can also be configured in application.properties which reduces the configuration in the route above:

camel.component.kamelet.postgresql-sink.databaseName={{secret:mydb/myhost}}
camel.component.kamelet.postgresql-sink.serverPort={{secret:mydb/myport}}
camel.component.kamelet.postgresql-sink.username={{secret:mydb/myuser}}
camel.component.kamelet.postgresql-sink.password={{secret:mydb/mypass}}

Which reduces the route to:

  • Java

  • XML

  • YAML

from("direct:rome")
  .setBody(constant("{ \"username\":\"oscerd\", \"city\":\"Rome\"}"))
  .to("kamelet:postgresql-sink?databaseName=cities&query=INSERT INTO accounts (username,city) VALUES (:#username,:#city)");
  <route>
    <from uri="direct:rome"/>
    <setBody>
      <constant>{ "username":"oscerd", "city":"Rome"}</constant>
    </setBody>
    <to uri="kamelet:postgresql-sink?databaseName=cities
             &amp;query=INSERT INTO accounts (username,city) VALUES (:#username,:#city)"/>
  </route>
- route:
    from:
      uri: direct:rome
      steps:
        - setBody:
            expression:
              constant:
                expression: "{ \"username\":\"oscerd\", \"city\":\"Rome\"}"
        - to:
            uri: kamelet
            parameters:
              templateId: postgresql-sink
              databaseName: cities
              query: "INSERT INTO accounts (username,city) VALUES (:#username,:#city)"

Using configmap or secrets in local-mode

During development, you may want to run in local mode where you do not need acces to a Kubernetes cluster, to lookup the configmap. In the local mode, then Camel will lookup the configmap keys from local properties, eg:

For example the example above with the Postgres kamelet, that was configured using a secret:

camel.component.kamelet.postgresql-sink.databaseName={{secret:mydb/myhost}}
camel.component.kamelet.postgresql-sink.serverPort={{secret:mydb/myport}}
camel.component.kamelet.postgresql-sink.username={{secret:mydb/myuser}}
camel.component.kamelet.postgresql-sink.password={{secret:mydb/mypass}}

Now suppose we have a local Postgres database we want to use, then we can turn on local mode and specify the credentials in the same properties file:

camel.kubernetes-config.local-mode = true
mydb/myhost=localhost
mydb/myport=1234
mydb/myuser=scott
mydb/mypass=tiger
Notice how the key is prefixed with the name of the secret and a slash, eg name/key. This makes it easy to copy/paste from the actual use of the configmap/secret and into the application.properties file.

Using custom property placeholder functions

The Properties component allow to plugin 3rd party functions which can be used during parsing of the property placeholders. These functions are then able to do custom logic to resolve the placeholders, such as looking up in databases, do custom computations, or whatnot. The name of the function becomes the prefix used in the placeholder.

This is best illustrated in the example route below, where we use beer as the prefix:

  • Java

  • XML

  • YAML

from("direct:start")
  .to("{{beer:FOO}}")
  .to("{{beer:BAR}}");
<route>
    <from uri="direct:start"/>
    <to uri="{{beer:FOO}}"/>
    <to uri="{{beer:BAR}}"/>
</route>
- route:
    from:
      uri: direct:start
      steps:
        - to:
            uri: "{{beer:FOO}}"
        - to:
            uri: "{{beer:BAR}}"

The implementation of the function is only two methods as shown below:

@org.apache.camel.spi.annotations.PropertiesFunction("beer")
public class MyBeerFunction implements PropertiesFunction {

    @Override
    public String getName() {
        return "beer";
    }

    @Override
    public String apply(String remainder) {
        return "mock:" + remainder.toLowerCase();
    }
}

The function must implement the org.apache.camel.spi.PropertiesFunction interface. The method getName is the name of the function (beer). And the apply method is where we implement the custom logic to do. As the sample code is from a unit test, it just returns a value to refer to a mock endpoint.

You also need to have camel-component-maven-plugin Maven plugin as part of building the component will then ensure that this custom properties function has necessary source code generated that makes Camel able to automatically discover the function.

If the custom properties function need logic to startup and shutdown, then the function can extend ServiceSupport and have this logic in doStart and doStop methods.
For an example see the camel-base64 component.