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/getVariableor 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
maxRetriesorthreshold.
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
Messagebody 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
originalBodyvariable.
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/serviceusing 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
toVremain 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:discountServiceroute 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 thediscountReplyvariable; the main message body is preserved.- We then combine
originalOrderanddiscountReplyexplicitly in the final processor, making the variable usage and flow of data very clear.
Leave a Reply