RabbitMQ Publisher Confirms: Enabling, Using, and Avoiding Pitfalls
Learn how to enable RabbitMQ Publisher Confirms for reliable message publishing, track unconfirmed messages, avoid common pitfalls, and verify durability in a production‑ready setup.
31 Jul 2026, 21:20 UTC

Use publisher confirms to know when RabbitMQ has safely stored your message
Publisher Confirms give you an asynchronous acknowledgement from RabbitMQ that a published message has been written to disk (for persistent messages) or accepted into the queue (for transient messages). They replace the heavyweight tx.select/tx.commit transaction model with a lightweight, non‑blocking mechanism that scales better in high‑throughput producers.
How Publisher Confirms work
When you call confirmSelect() on a channel, RabbitMQ switches that channel into confirm mode. Each subsequent basic.publish does not block; instead the broker will later send a Basic.Ack or Basic.Nack on the same channel. The acknowledgement includes a delivery‑tag that matches the sequence number assigned to the publish. Your application must correlate those tags with the original messages, typically by keeping a map of sequence‑number → message (or idempotency key).
Configuration example (Java client 5.12+)
import com.rabbitmq.client.*;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\npublic class ConfirmPublisher {\n public static void main(String[] args) throws Exception {\n ConnectionFactory factory = new ConnectionFactory();\n // Enable confirms via URI query param (alternative: factory.setPublisherConfirms(true))\n factory.setUri("amqp://guest:guest@localhost/myvhost?confirm=true");\n try (Connection conn = factory.newConnection();\n Channel channel = conn.createChannel()) {\n // Ensure confirms are active (explicit call works even if URI already set)\n channel.confirmSelect();\n\n // Declare a durable queue (persistent messages need this)\n channel.queueDeclare("durable.q", true, false, false, null);\n\n // Track unconfirmed publishes\n Map outstanding = new ConcurrentHashMap<>();\n channel.addConfirmListener((deliveryTag, multiple) -> {\n if (multiple) {\n outstanding.keySet().removeIf(tag -> tag <= deliveryTag);\n } else {\n outstanding.remove(deliveryTag);\n }\n System.out.println("Acked: " + deliveryTag);\n }, (deliveryTag, multiple) -> {\n System.err.println("Nacked: " + deliveryTag);\n // Example: re‑publish from your own outbox using the message body stored elsewhere\n if (!multiple) {\n String msg = outstanding.remove(deliveryTag);\n // republish logic goes here\n }\n });\n\n for (int i = 0; i < 10_000; i++) {\n String body = "msg-" + i;\n // Persistent message property\n channel.basicPublish("", "durable.q",\n MessageProperties.PERSISTENT_TEXT_PLAIN,\n body.getBytes(StandardCharsets.UTF_8));\n long nextSeq = channel.getNextPublishSeqNo();\n outstanding.put(nextSeq - 1, body); // seqNo‑1 is the tag just used\n }\n\n // Wait for all outstanding confirms (timeout 5 s)\n if (!channel.waitForConfirmsOrDie(5_000)) {\n System.err.println("Timeout waiting for confirms");\n }\n }\n }\n}\n
The example shows:
- Enabling confirms via the connection URI (confirm=true).
- Calling
confirmSelect()on the channel. - Declaring a durable queue so persistent messages survive broker restarts.
- Using an asynchronous
ConfirmListenerto handle acks and nacks without blocking the I/O thread. - Tracking each publish with a concurrent map keyed by delivery‑tag.
- Calling
waitForConfirmsOrDieat shutdown to drain the pipeline.
Tracking unconfirmed messages in practice
If you prefer a synchronous approach, you can call waitForConfirms(timeout) after a batch of publishes. This blocks until all outstanding confirms arrive or the timeout expires. For high‑throughput systems, the asynchronous listener combined with a bounded outbox (e.g., a disk‑backed queue) provides back‑pressure without stalling the producer thread.
Limits
- Per‑channel scope: Confirms are tied to a single channel. If the channel or connection fails, all unconfirmed sequence numbers are lost and the application must republish from its own durable outbox.
- Memory overhead: You must retain information for every unconfirmed publish until its ack/nack arrives. Publishing faster than confirms can lead to
OutOfMemoryErrorif the outbox grows unbounded. - Latency impact: For persistent messages the broker waits until the message is written to disk; for quorum or mirrored queues it also waits for replication to reach the configured quorum, which adds round‑trip time.
- No end‑to‑end guarantee: A confirm only tells you the broker has accepted the message. It does not guarantee that a consumer has received, processed, or acknowledged the message.
Common mistakes and how to avoid them
- Forgotten persistence: Using the default transient properties means messages live only in RAM; a broker restart loses them even though you got a confirm. Always set
MessageProperties.PERSISTENT_TEXT_PLAIN(or equivalent) for durable queues. - Publishing faster than confirms: Without back‑pressure the client’s unconfirmed map can grow indefinitely. Apply flow control (e.g., pause publishing when the map exceeds a threshold) or use a bounded outbox.
- Ignoring
Basic.Return: When you publish withmandatory=trueand no queue matches, the broker returns the message via aBasic.Returncallback. If you only listen for confirms, you will miss the drop and assume the message is safely stored. - Assuming confirms survive restarts: Confirms are in‑memory per channel. After a connection failure you must resend any messages that were not acknowledged.
- Mixing AMQP versions: AMQP 1.0 clients use a different settlement model; calling
confirmSelecton an AMQP 1.0 connection has no effect. Ensure you are using an AMQP 0‑9‑1 client (e.g., the official RabbitMQ Java client) when relying on confirms.
Verification steps
- Start a RabbitMQ node (or use a test container).
- Enable the management plugin (
rabbitmq-plugins enable rabbitmq_management) if not already on. - Run the publisher code above against a durable queue (e.g.,
durable.q). - While publishing, check the management UI: Overview → Channels → look for the channel’s “confirm” mode and the “Unconfirmed” count.
- After the publisher finishes, stop the broker node abruptly (
rabbitmqctl stop_appor kill the process). - Restart the node, then consume from
durable.qand count messages. If confirms were working and messages were persistent, you should see the same number of messages that were published. - Optionally, monitor Prometheus metrics:
rabbitmq_channel_confirm_count(total confirms) andrabbitmq_channel_confirm_latency_secondshistogram to spot latency spikes caused by quorum replication.
These steps let you confirm that publisher confirms are functioning as expected and that your durability guarantees hold under a broker failure scenario.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.