Camunda 8 · Zeebe
Camunda 8 message correlation explained: names, keys and TTL
How Camunda 8 message correlation works — message names, correlation keys, subscriptions and TTL — and how to debug messages that never reach their process instance.
You publish a message, the call succeeds — and the process instance just keeps waiting. Message correlation is where many Camunda 8 projects get stuck, because a successful publish doesn’t mean the message reached anything. Here is how correlation really works, and how to debug it.
The basics: name and correlation key
A process waits for a message at a message catch event, a receive task or a message boundary event. Each one references a message with two parts:
- Message name — the type of message, for example
payment-received. It must match exactly, including case. - Correlation key — an expression evaluated for each instance, for example
= orderId, that identifies which instance the message is for.
<bpmn:message id="Message_Payment" name="payment-received">
<bpmn:extensionElements>
<zeebe:subscription correlationKey="=orderId" />
</bpmn:extensionElements>
</bpmn:message>When an instance reaches the catch event, Zeebe evaluates the correlation key and opens a subscription: “instance 2251799813685249 is waiting for payment-received with key ORD-1042”. A published message with the same name and key completes that wait.
Publishing a message
client.newPublishMessageCommand()
.messageName("payment-received")
.correlationKey(orderId) // e.g. "ORD-1042"
.messageId("payment-" + paymentId) // optional: rejects duplicates while buffered
.timeToLive(Duration.ofMinutes(10)) // keep it if nobody is waiting yet
.variables(Map.of("paymentStatus", "PAID"))
.send()
.join();The correlation key must evaluate to a string or a number, and the value you publish must match it exactly — ORD-1042 and ord-1042 are different keys.
Timing and time-to-live
This is the most common source of “lost” messages. What happens depends on whether a subscription exists when the message is published:
- A matching subscription exists — the message correlates immediately.
- No subscription yet, TTL greater than zero — Zeebe buffers the message. If an instance reaches the matching catch event before the TTL expires, it correlates then.
- No subscription and TTL of zero — the message is discarded. It only correlates if an instance is already waiting.
So if your service publishes the payment confirmation a moment before the process reaches the “Wait for payment” event, a TTL of zero loses the message every time. Use a TTL that covers the realistic gap.
Correlation rules worth knowing
- Publishing is not correlating. The publish call succeeds whether or not any instance is waiting. Newer Camunda 8 releases also offer a “correlate message” API that reports whether the message matched a waiting instance — useful when you need to know immediately.
- Message IDs make publishing idempotent: while a message is buffered, a duplicate with the same ID is rejected — handy when the sender may retry.
- One message, one instance per process: a message can correlate with waiting instances of different processes, but only once per process.
- The key is evaluated on entry: if the variable behind the correlation key doesn’t exist when the instance reaches the event, an incident is raised instead of a subscription.
- Message variables are merged into the instance at the catching element; use output mappings to control exactly what lands in process scope.
Debugging a message that doesn’t correlate
- Find the instance in Operate. Is it actually waiting at the message event — or is it stuck earlier, or at an incident?
- Check the correlation key value in the instance’s variables. Compare it character by character with what was published: case, whitespace, prefixes, number versus string formatting.
- Check the message name in the deployed model against the name in your code.
- Check timing: was the message published before the instance reached the event, with a TTL of zero or a TTL that had expired?
- Check the version: instances started on an older process version wait on the older model’s message definition.
Patterns that work well
- Use a business identifier as the correlation key — order ID, application number — never a technical key that the sender doesn’t know.
- Pair waiting events with a timer boundary event so an instance doesn’t wait forever if the message never arrives.
- For “start a process from an event” use a message start event; for “continue a running process” use a catch event or receive task.
- Log the message name, correlation key and message ID on every publish — you will need them.