In Camel’s Java DSL, a variable is a key–value pair you associate with an Exchange, a route, or the CamelContext, and VariableReceive is a mode where incoming data is written into variables instead of the Message body and headers. Used properly, these give you a clean, explicit way to manage routing state without polluting payloads or headers.


1. Variables in Java DSL: the mental model

  • A variable is a named value stored on the exchange, route, or CamelContext, accessed with setVariable / getVariable or via EIPs.
  • Variables are separate from the message body, headers, and legacy exchange properties, and are meant as a scratchpad for state you need while routing.

Conceptually, you choose the scope based on who should see the value and how long it should live.


2. Local, global, and route‑scoped variables

2.1 Exchange‑local variable

Exchange‑local variables live only during processing of a single message.

from("direct:local-variable")
    .process(exchange -> {
        // store some state for this exchange only
        exchange.setVariable("customerId", 12345L);
    })
    .process(exchange -> {
        Long id = exchange.getVariable("customerId", Long.class);
        System.out.println("Customer ID = " + id);
    });

Rationale: use exchange‑local variables for state that is logically tied to a single message and must not leak to other messages or routes.


2.2 Global and route‑scoped variables

Global variables live in the CamelContext, and route‑scoped variables are global variables whose keys include a route identifier.

from("direct:global-and-route")
    .routeId("routeA")
    .process(exchange -> {
        CamelContext context = exchange.getContext();

        // global variable (shared across all routes)
        context.setVariable("globalFlag", true);

        // route‑scoped variable using naming convention
        exchange.setVariable("route:routeA:maxRetries", 3);
    })
    .process(exchange -> {
        CamelContext context = exchange.getContext();

        Boolean globalFlag =
            context.getVariable("globalFlag", Boolean.class);

        Integer maxRetries =
            context.getVariable("route:routeA:maxRetries", Integer.class);

        System.out.println("Global flag   = " + globalFlag);
        System.out.println("Max retries A = " + maxRetries);
    });

You can also read a global variable from an exchange with the global: prefix (if you prefer that naming scheme).

Rationale:

  • Global variables are convenient for application‑level flags or configuration computed at runtime.
  • Route‑scoped variables avoid collisions when different routes use similar semantic names like maxRetries or threshold.

3. SetVariable and SetVariables EIPs in Java DSL

While you can always call exchange.setVariable(...) directly, the Java DSL provides EIPs that make variable usage declarative in your routes.

3.1 Set a single variable

from("direct:set-variables")
    // single variable from constant
    .setVariable("status", constant("NEW"))

    // multiple variables in one go
    .setVariables(
        "randomNumber", simple("${random(1,100)}"),
        "copyOfBody",  body()
    )

    .process(exchange -> {
        String status  = exchange.getVariable("status", String.class);
        Integer random = exchange.getVariable("randomNumber", Integer.class);
        String bodyCopy = exchange.getVariable("copyOfBody", String.class);

        System.out.printf(
            "status=%s, random=%d, copy=%s%n",
            status, random, bodyCopy
        );
    });

Rationale: setVariable and setVariables keep the “I am defining variables here” logic inside the DSL layer rather than hiding it in processors, which makes routes easier to read and reason about.


4. VariableReceive on the consumer: fromV

VariableReceive changes how Camel receives data from endpoints: instead of populating the message body and headers, Camel stores them into variables.

Using it on the consumer side is done with fromV (the Java DSL variant of from):

fromV("direct:incoming", "originalBody")
    // body is empty here because the incoming payload is stored in variable "originalBody"
    .transform().simple("Processed: ${body}") // -> "Processed: "
    .process(exchange -> {
        String original =
            exchange.getVariable("originalBody", String.class);
        String processed =
            exchange.getMessage().getBody(String.class);

        System.out.println("Original  = " + original);
        System.out.println("Processed = " + processed);
    });

What actually happens:

  • On entry, Camel stores the incoming body in variable originalBody.
  • The Message body is empty (or at least not the original payload), so ${body} in the transform step produces "Processed: ".
  • Later, you can inspect or restore the original payload by reading the originalBody variable.

Rationale: this is useful when you want to separate transport reception from business payload, or when you want to ensure that early processors do not accidentally work on the raw incoming data.


5. VariableReceive on the producer: toV

You can apply VariableReceive to producer calls with toV, which works like to but puts the response into a variable instead of overwriting the message body.

from("direct:call-service")
    // main message body is here
    .toV("http://localhost:8080/service", null, "serviceReply")
    .process(exchange -> {
        // original body is preserved
        String original =
            exchange.getMessage().getBody(String.class);

        // HTTP response body from the service
        String reply =
            exchange.getVariable("serviceReply", String.class);

        // response header X-Level as header variable
        String level =
            exchange.getVariable("header:serviceReply.X-Level", String.class);

        String combined = "Original=" + original
                        + ", Reply=" + reply
                        + ", Level=" + level;

        exchange.getMessage().setBody(combined);
        System.out.println(combined);
    });

Semantics:

  • Request is sent to http://localhost:8080/service using the current body and headers.
  • Response body is placed into variable serviceReply.
  • Response headers are placed into variables named header:serviceReply.<HeaderName>.[camel.apache]
  • The exchange’s message body and headers after toV remain what they were before the call.

Rationale: this is an enrichment‑style pattern where you treat external responses as additional data points rather than as replacements for your main message.


6. Comparing body, headers, properties, and variables

For Java DSL routes you’ll typically use all four concepts; the key is to pick the right tool for the job.

Kind Scope / lifetime Java access (simplified) Typical use
Body Current message only exchange.getMessage().getBody() Business payload
Header Current message only exchange.getMessage().getHeader("X", T.class) Protocol / metadata (HTTP, JMS, etc.)
Property Current exchange (legacy pattern) exchange.getProperty("key", T.class) Cross‑processor flags, routing metadata
Variable Exchange / route / global repositories exchange.getVariable("key", T.class) or context API Extra state, external responses, config‑like

Rationale: using variables for scratchpad and enrichment state avoids overloading headers or properties with routing concerns, which improves maintainability and refactorability.


7. End‑to‑end Java program example (using only direct:)

This example shows how variables and toV/from can work together without any external HTTP endpoint. We simulate a “discount service” with another direct: route.

import org.apache.camel.CamelContext;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.DefaultCamelContext;

void main() throws Exception {
    CamelContext context = new DefaultCamelContext();

    context.addRoutes(new RouteBuilder() {
        @Override
        public void configure() {

            // 1) "Discount service" route, purely in-memory
            from("direct:discountService")
                .process(exchange -> {
                    String originalOrder =
                        exchange.getMessage().getBody(String.class);

                    // pretend we call an external system and compute a discount
                    String discountReply =
                        "discount=10% for order " + originalOrder;

                    exchange.getMessage().setBody(discountReply);
                });

            // 2) Main route using variables + VariableReceive with toV
            from("direct:start")
                // keep the original order body in a variable
                .setVariable("originalOrder", body())

                // call the in-memory discountService; response -> variable "discountReply"
                .toV("direct:discountService", null, "discountReply")

                // build combined response
                .process(exchange -> {
                    String originalOrder =
                        exchange.getVariable("originalOrder", String.class);
                    String discountReply =
                        exchange.getVariable("discountReply", String.class);

                    String result = "Order=" + originalOrder
                                  + ", DiscountService=" + discountReply;

                    exchange.getMessage().setBody(result);
                    System.out.println(result);
                });
        }
    });

    context.start();

    // Send a test order into the main route
    context.createProducerTemplate()
           .sendBody("direct:start", "{\"id\":1,\"total\":100}");

    Thread.sleep(2000);
    context.stop();
}
  • The direct:discountService route stands in for an external dependency but uses only the in‑memory direct component.
  • toV("direct:discountService", null, "discountReply") still demonstrates VariableReceive on a producer: the reply body goes into the discountReply variable; the main message body is preserved.
  • We then combine originalOrder and discountReply explicitly in the final processor, making the variable usage and flow of data very clear.