Variables

Available from Camel 4.4

In Camel 4.4, we have introduced the concept of variables.

A variable is a key/value that can hold a value that can either be private per Exchange, or shared per route, or per CamelContext.

You can also use exchange properties as variables, but the exchange properties are also used internally by Camel, and some EIPs and components. With the newly introduced variables then these are exclusively for end users.

Variable Repository

The variables are stored in one or more org.apache.camel.spi.VariableRepository. By default, there are the following repositories

  • ExchangeVariableRepository - A private repository per Exchange that holds variables that are private for the lifecycle of each Exchange.

  • RouteVariableRepository - Uses route as id. A single repository, that holds variables per Route.

  • GlobalVariableRepository - Uses global as id. A single global repository for the entire CamelContext.

The ExchangeVariableRepository is special as its private per exchange and is the default repository when used during routing. The RouteVariableRepository is a single repository that holds variables that are route scoped.

There is also org.apache.camel.spi.BrowsableVariableRepository which is an extension to VariableRepository that has APIs to browse the current variables. Camel uses this with Camel JBang, and JMX to be able to see the current variables from management tooling, CLI, and the developer console.

Custom variable repositories

You can implement custom org.apache.camel.spi.VariableRepository and plugin to be used out of the box with Apache Camel. For example, you can build a custom repository that stores the variables in a database, so they are persisted.

Each repository must have its own unique id. However, it’s also possible to replace the default global, or route repositories with another.

The id exchange and header is reserved by Camel internally and should not be used as id for custom repositories.

Getting and setting variables from Java API

To get a local variable from the current exchange, you can do this via Java API:

String myValue = "...";
exchange.setVariable("myKey", myValue);

// and later to get the variable
Object val = exchange.getVariable("myKey");

// you can get the value as a specific type
String str = exchange.getVariable("myKey", String.class);

The API on Exchange will by default get the variables from its local private repository. However, you can also get variables from other repositories, such as the global as show:

Object val = exchange.getVariable("global:myGlobalKey");

And you can also assign a global variable by prefixing with global: as follows:

exchange.setVariable("global:myGlobalKey", someObjectHere);

There is also API on CamelContext to get variables. However, this API will by default get from the global repository, as it’s not possible to get variables from any inflight Exchange currently being routed.

Object val = context.getVariable("myGlobalKey");

// you can get the value as a specific type
String str = context.getVariable("myGlobalKey", String.class);

You can also assign a variable to a specific route with route: as follows:

exchange.setVariable("route:myRouteId:myRouteKey", someObjectHere);

And you can get route variables as well:

Object val = context.getVariable("route:myRouteId:myRouteKey");

// you can get the value as a specific type
String str = context.getVariable("route:myRouteId:myRouteKey", String.class);

Setting and getting variables from DSL

It is also possible to use variables in Camel routes using the setVariable, removeVariable, and convertVariableTo EIPs.

These EIPs make it possible to set and remove variables from routes. And you can also access variables from the Simple language.

In the following route, we set a variable on the exchange which we use later to build a human-readable event message:

  • Java

  • XML

  • YAML

from("kafka:order.in")
  .setVariable("customerId", jq(".customer.custId"))
  .setVariable("country", jq(".customer.address.co"))
  .transform().simple("Order received from customer ${variable.customerId} in ${variable.country}")
  .to("kafka:order.event");
<route>
    <from uri="kafka:order.in"/>
    <setVariable name="customerId">
        <jq>.customer.custId</jq>
    </setVariable>
    <setVariable name="country">
        <jq>.customer.address.co</jq>
    </setVariable>
    <transform>
        <simple>Order received from customer ${variable.customerId} in ${variable.country}</simple>
    </transform>
    <to uri="kafka:order.event"/>
</route>
- route:
    from:
      uri: kafka:order.in
      steps:
        - setVariable:
            name: customerId
            jq:
              expression: .customer.custId
        - setVariable:
            name: country
            jq:
              expression: .customer.address.co
        - transform:
            simple:
              expression: "Order received from customer ${variable.customerId} in ${variable.country}"
        - to:
            uri: kafka:order.event

When route variables in Camel routes, then the routeId is implied as the current route, if not explicit declared. For example, the following example the first route will set a variable (route:second:foo) in the second route. Then the second route can get hold of the variable without having to specify its route id route:foo:

  • Java

from("direct:start").routeId("first")
    // sets variable in another route
    .setVariable("route:second:foo").constant("Hello World")
    .to("mock:end");

from("direct:second").routeId("second")
    // use variable from this route
    .setBody().variable("route:foo");

Configuring initial variables on startup

When Camel is starting then it’s possible to configure initial variables for global and route repositories only.

This can be done in application.properties as shown below:

camel.variable.greeting = Random number
camel.variable.random = 999

The variables are default set on the global repository, but you can set route scoped variables, using route. as prefix. As we cannot use colon (:) in property keys, then dot is used to separate the route id from the variable name, eg myRoute.gold.

camel.variable.route.myRoute.gold = true
camel.variable.greeting = Random number
camel.variable.random = 999

Here the gold variable is set on the route repository, and the other variables are set on the global repository.

The value of a variable can also be loaded from the file system, such as a JSon file. To do this, you should prefix the value with resource:file: or resource:classpath: to load from the file system or classpath, as shown below:

camel.variable.user-template = resource:file:/etc/user.json

Camel (Camel 4.8) will automatically convert the value to appropriate type:

  • all digits are converted to an int or long

  • true/false are converted to a boolean

  • otherwise string value

There is also support for referring to other existing beans, using the #bean: syntax:

camel.variable.cheese = #bean:myCheeseBean

Or create a new bean via the #class: or #type: syntax:

camel.variable.cheese = #class:com.foo.MyClassName

Or if the value must be of a special type, you can specify this via #valueAs as follows:

camel.variable.amount = #valueAs(float):1.23

Using Variables with EIPs

The following commonly used EIPs for sending and receiving, and transforming messages, have special support for choosing to use variables over the current Exchange:

  • from

  • to

  • toD

  • enrich

  • poll

  • pollEnrich

  • wireTap

  • unmarshal

  • marshal

The intention is to make it more convenient and easy to gather data from other systems without any ceremony to keep existing data by using techniques such as storing the data temporary using headers, exchange properties, or with the Claim Check EIP.

Important concept when using variables and EIPs

It is important to understand that the variables focus the use of the message body only. This is on purpose to keep it simpler and primary work with the message body as the user data.

The following table summarizes what the EIP supports with variables:

EIP

VariableSend

VariableReceive

From

yes

To

yes

yes

ToD

yes

yes

Enrich

yes

yes

Poll

yes

PollEnrich

yes

WireTap

yes

Unmarshal

yes

yes

Marshal

yes

yes

The EIPs listed above have support for using variables when sending and receiving data. This is done by using the variableSend and variableReceive options to specify the name of the variable.

The EIPs works in two modes where variableSend and variableReceive are a little bit different, so pay attention to the following table:

VariableSend

VariableReceive

Sending Headers: Message

Received Headers: Variable

Sending Body: Variable

Received Body: Variable

The VariableSend is intended for sending as regular Camel where the sending headers are from the current Message and the body is from the variable. In other words it’s only the message body taken from the variable instead of the current Message body.

The VariableReceive works in a different mode. The idea is that all the received data is stored as variables. This means the current Message is not changed at all. The received body is stored in the variable, and the received headers (transport headers etc.) are stored as read-only headers as variables as well. The names of the variable is header:variableName.headerName. For example, if the variable is myVar and the header is Content-Type then the header is stored as a variable with the full name header:myVar.Content-Type.

Example using Variable Receive

When the EIP is using VariableReceive, then the Message on the Exchange is not in use, but the body and headers will be from the variable. For example given the following Message containing:

header.foo=123
header.bar=456
body=Hello World

And a remote service is called via the route below, and this service returns a new header (level) and body:

  • Java

  • XML

  • YAML

from("direct:service")
  .to("http:myservice")
  .to("log:after");
<route>
  <from uri="direct:service"/>
  <to uri="http:myservice"/>
  <to uri="log:after"/>
</route>
from:
  uri: "direct:service"
  steps:
    - to: "http:myservice"
    - to: "log:after"

Then calling this route, then the Message is updated:

header.foo=123
header.bar=456
header.level=gold
body=Bye World

However, if you use VariableReceive=myVar to store the returned data from calling the remote service, into a variable, then the dynamics changes as follows:

  • Java

  • XML

  • YAML

from("direct:service")
  .toV("http:myservice", null, "myVar")
  .to("log:after");
<route>
  <from uri="direct:service"/>
  <to uri="http:myservice" variableReceive="myVar"/>
  <to uri="log:after"/>
</route>
from:
  uri: "direct:service"
  steps:
    - to:
        uri: http:myservice
        variableReceive: myVar
    - to: "log:after"

Then the Message on the current Exchange is not changed:

header.foo=123
header.bar=456
body=Hello World

And the variable contains all the data received from the remote HTTP service separated into two variables:

myVar=Bye World
header:myVar.level=gold
Notice the headers are stored with the syntax header:variable.key. In the example above the variable name is myVar, and the header key is level, which gives: header:myVar.level.

Using variable to store incoming message body

You can configure the from to store the message body into a variable, instead of the Message. This makes it easy to have quick access to the original incoming message body via the variable. Notice that the body on the Message will be null.

The following example from a unit test shows how to do this. Notice how Java DSL uses fromV to make it possible to specify the name of the variable. In XML and YAML DSL you specify this using the variableReceive parameter.

  • Java

  • XML

  • YAML

fromV("direct:start", "myKey")
    .transform().simple("Bye ${body}")
    .to("mock:foo")
    .setBody(variable("myKey"))
    .to("mock:result");
<route>
  <from uri="direct:start" variableReceive="myKey"/>
  <transform>
    <simple>Bye ${body}</simple>
  </transform>
  <to uri="mock:foo"/>
  <setBody>
    <variable>myKey</variable>
  </setBody>
  <to uri="mock:result"/>
</route>
from:
  uri: "direct:start"
  variableReceive: "myKey"
  steps:
    - transform:
        simple: "Bye ${body}"
    - to: "mock:foo"
    - setBody:
        variable: "myKey"
    - to: "mock:result"
In the examples above the transform Bye ${body} will result as Bye ` because the `Message has no message body, as the incoming message body is stored in the variable myKey instead.