RabbitMQ publisher confirms: detect undeliverable messages
Use publisher confirms to detect when RabbitMQ cannot route a published message, and combine them with mandatory publishing to avoid silent drops.
20 Dec 2025, 06:44 UTC

The problem: silent message loss on unroutable publishes
A publisher can send a message to RabbitMQ and receive no error even when the broker cannot route it to any queue. Without publisher confirms, the application has no broker-side evidence that a message was accepted, routed, or persisted, so undeliverable messages disappear silently.
Useful takeaway: Enable confirms on the publishing channel with confirmSelect(), publish with mandatory=true, and handle basic.return plus basic.nack so unroutable messages are returned to the publisher instead of being dropped. Track delivery tags asynchronously to avoid waiting for a network round-trip on every message.
How publisher confirms work
Publisher confirms are a RabbitMQ protocol extension. When a channel enters confirm mode, the broker assigns a sequential delivery tag to each publish. After the message is accepted by all matching queues, the broker sends basic.ack. If the message cannot be routed, the broker sends basic.nack. With mandatory=true, unroutable messages are also returned to the publisher through basic.return.
A confirm verifies broker acceptance and routing only. It does not confirm that a consumer has processed the message. Separate consumer acknowledgment is still required to prevent loss after dispatch.
Worked example: confirm channel with mandatory publishing
Run this code in the publisher application, not on the broker. The connecting RabbitMQ user needs write and configure permissions on the target virtual host. Replace EXCHANGE_NAME and ROUTING_KEY with your values. This is an illustrative Java-style example; adapt the method signatures to your client library.
Channel channel = connection.createChannel();
channel.confirmSelect();
// Tags the broker has not yet confirmed.
Set<Long> outstandingTags = ConcurrentHashMap.newKeySet();
channel.addConfirmListener(
(deliveryTag, multiple) -> {
if (multiple) {
outstandingTags.stream()
.filter(tag -> tag <= deliveryTag)
.forEach(outstandingTags::remove);
} else {
outstandingTags.remove(deliveryTag);
}
},
(deliveryTag, multiple) -> {
outstandingTags.remove(deliveryTag);
handleUnroutable(deliveryTag);
}
);
channel.addReturnListener((message, replyCode, replyText, exchange, routingKey) -> {
logReturnedMessage(message, replyCode, replyText, exchange, routingKey);
});
channel.basicPublish(
'EXCHANGE_NAME',
'ROUTING_KEY',
true, // mandatory
MessageProperties.PERSISTENT_TEXT_PLAIN,
'message body'.getBytes(StandardCharsets.UTF_8)
);
// Record the tag according to your client's publish contract.
outstandingTags.add(nextDeliveryTag);
Expected checks: A routable message produces basic.ack for its delivery tag. A message published to an exchange with no matching queue, using mandatory=true, produces basic.return and basic.nack. Risks: Waiting for every confirm synchronously adds round-trip latency; basic.nack may arrive out of order relative to other confirms.
High-throughput pattern: batch confirms
Do not block on every publish. Track outstanding delivery tags in a set and wait for confirms in batches, for example every N messages or after a timeout. Asynchronous confirm listeners let the publisher keep sending while the broker acknowledges earlier messages.
Limits and common mistakes
- A confirm is not consumer delivery. The broker may acknowledge a message before any consumer receives it. Consumer acknowledgment remains the safety net for processing loss.
- Mandatory publishing without a return listener or dead-letter exchange can still lose messages. Confirm and return handling must both be present if you need unroutable messages back.
basic.nackordering is not guaranteed relative to other confirms. Republish or dead-letter logic must handle unordered delivery tags.- Publishing without confirms or transactions gives no broker-side evidence. The socket may succeed while the broker discards the message.
Practical verification
- Enable confirms on a test channel in a non-production environment.
- Publish to an exchange with no matching queue, using
mandatory=true. - Observe
basic.returnandbasic.nackfor that message in the client. - Compare delivery tags in the confirm listener against sent messages to verify the broker acknowledges after queue acceptance, not after socket write.
- Check the RabbitMQ management UI or client logs to confirm publish rates drop when waiting for synchronous confirms, showing the round-trip overhead.
No broker rollback is required. Enabling confirms changes channel behavior in the publisher application; disabling it simply returns the channel to unconfirmed publishing.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.