In Java DSL, you declare routes in code, Camel wraps each Exchange in a Unit of Work, and onCompletion is the Java DSL hook that runs a sub‑route when that Unit of Work finishes.
Unit of Work: What Java DSL Developers Should Know
For each incoming Exchange, Camel creates a Unit of Work when it enters a route and tears it down when that route finishes processing the Exchange.
- It represents “this message’s transaction/lifecycle through this route.”
- It manages completion callbacks via
Synchronizationhooks (e.g.onComplete,onFailure). - In Java DSL, this is implicit; you usually just work with
Exchangeand let Camel manage the Unit of Work for you.
You can still access it if you need to register low‑level callbacks:
from("direct:withUoW")
.process(exchange -> {
var uow = exchange.getUnitOfWork();
// uow.addSynchronization(...);
})
.to("mock:result");
Why this exists: Camel needs a clear lifecycle boundary to know when to commit transactions, acknowledge messages, and run “finished” logic once per Exchange.
Core onCompletion Concept in Java DSL
The onCompletion DSL lets you attach a small route that runs when the original Exchange’s Unit of Work completes.
- Camel takes a copy of the Exchange and routes it through the
onCompletionblock. - This behaves “kinda like a Wire Tap”: the original thread can continue or finish while the completion route runs.
Basic Java DSL example:
from("direct:start")
.onCompletion()
.to("log:on-complete")
.to("bean:auditBean")
.end()
.process(exchange -> {
String body = exchange.getIn().getBody(String.class);
exchange.getMessage().setBody("Processed: " + body);
})
.to("mock:result");
Why use it: It centralizes “after everything is done” logic (logging, metrics, notifications) instead of scattering it across processors and finally blocks.
Java DSL Scope: Global vs Route-Level
You can define onCompletion globally (context scope) or inside a specific route (route scope).
Global onCompletion (context scope)
Declared outside any from(...) in configure():
@Override
public void configure() {
// Global onCompletion – applies to all routes unless overridden
onCompletion()
.onCompleteOnly()
.to("log:global-complete");
from("direct:one")
.to("bean:logicOne");
from("direct:two")
.to("bean:logicTwo");
}
- Runs for all routes in this
RouteBuilder(and more generally in the context) when they complete successfully.
Route-level onCompletion
Declared inside a route:
@Override
public void configure() {
// Global
onCompletion()
.onCompleteOnly()
.to("log:global-complete");
// Route-specific
from("direct:start")
.onCompletion().onFailureOnly()
.to("log:route-failed")
.end()
.process(exchange -> {
String body = exchange.getIn().getBody(String.class);
if (body.contains("fail")) {
throw new IllegalStateException("Forced failure");
}
exchange.getMessage().setBody("OK: " + body);
})
.to("mock:result");
}
- Route-level
onCompletionoverrides globalonCompletionfor that route’s completion behavior.
Why the two levels: Global scope gives you cross‑cutting completion behavior (e.g. auditing for all routes), while route scope gives a particular route fine‑grained control.
Controlling When onCompletion Triggers (Java DSL)
The Java DSL offers fine control on when the completion logic is invoked.
Success vs failure
-
Always (default): no extra flags; runs on both success and failure.
-
Only on success:
from("direct:successOnly") .onCompletion().onCompleteOnly() .to("log:success-only") .end() .to("bean:logic"); -
Only on failure:
from("direct:failureOnly") .onCompletion().onFailureOnly() .to("log:failure-only") .end() .process(exchange -> { throw new RuntimeException("Boom"); }) .to("mock:neverReached");
Conditional with onWhen
from("direct:conditional")
.onCompletion().onWhen(body().contains("Alert"))
.to("log:alert-completion")
.end()
.to("bean:normalProcessing");
- The completion route runs only when the predicate evaluates to true on the completion Exchange (e.g. body contains
"Alert").
Why this is useful: It turns onCompletion into a small policy language: “When we’re done, if it failed do X; if it succeeded and condition Y holds, do Z.”
Before vs After Consumer (InOut) in Java DSL
For request–reply (InOut) routes, onCompletion can run either before or after the consumer writes the response back.
Default: after consumer (AfterConsumer)
from("direct:service")
.onCompletion()
.to("log:after-response")
.end()
.process(exchange -> {
exchange.getMessage().setBody(
"Response for " + exchange.getIn().getBody(String.class)
);
});
- The caller receives the response; then the
onCompletionsub‑route runs.
Before consumer: modeBeforeConsumer()
from("direct:serviceBefore")
.onCompletion().modeBeforeConsumer()
.setHeader("X-Processed-By", constant("MyService"))
.to("log:before-response")
.end()
.process(exchange -> {
exchange.getMessage().setBody(
"Response for " + exchange.getIn().getBody(String.class)
);
});
modeBeforeConsumer()makes completion logic run before the consumer is done and before the response is written back, so you can still modify the outgoing message.
Why you’d choose one:
- Use AfterConsumer when completion work is purely side‑effect (logging, metrics, notifications).
- Use BeforeConsumer when completion should influence the final response (headers, correlation IDs, last‑minute logging of the exact response).
Synchronous vs Asynchronous onCompletion in Java DSL
Java DSL lets you decide whether completion runs sync or async.
Synchronous (default)
from("direct:sync")
.onCompletion()
.to("log:sync-completion")
.end()
.to("bean:work");
- Completion runs on the same thread; no extra thread pool is used by default (from Camel 2.14+).
Asynchronous with parallelProcessing()
from("direct:async")
.onCompletion().parallelProcessing()
.to("log:async-completion")
.to("bean:slowAudit")
.end()
.to("bean:fastBusiness");
parallelProcessing()tells Camel to run the completion task asynchronously using a thread pool, so the original thread can complete sooner.
Custom executor with executorServiceRef
@Override
public void configure() {
// Assume "onCompletionPool" is registered in the CamelContext
from("direct:customPool")
.onCompletion()
.parallelProcessing()
.executorServiceRef("onCompletionPool")
.to("bean:expensiveLogger")
.end()
.to("bean:mainLogic");
}
Why async: Completion tasks are often slow but non‑critical (e.g. writing to external systems), so running them on a separate thread avoids holding up the main request path.
How onCompletion and Unit of Work Fit Together (Java DSL View)
Bringing it together for Java DSL:
- Each Exchange in a route is wrapped in a Unit of Work; that’s the lifecycle boundary.
- At the end of that lifecycle, Camel fires completion hooks (
Synchronization) and also activates anyonCompletionroutes defined in Java DSL. onCompletionroutes receive a copy of the Exchange and usually do not affect the already‑completed main route, especially when run async.
Leave a Reply