Read time: ~

Reliable Publishing: Publisher Confirms and Returns

Guarantee the broker accepted your message with publisher confirms, and catch unroutable messages with returns and the mandatory flag.

Prerequisite:First Producer and ConsumerYou’ll need: A broker running locally and the Spring AMQP producer from the previous section.

Publishing is not reliable by default. convertAndSend returns as soon as the message leaves your app, whether or not the broker actually stored it or could route it. This module closes that gap: confirms prove the broker accepted a message, and returns catch messages that had nowhere to go.


What you’ll be able to do after this module

  • Enable publisher confirms and react to positive and negative acknowledgements.
  • Use the mandatory flag and returns callback to catch unroutable messages.
  • Attach correlation data so you know which message a confirm refers to.
  • Explain why confirms are preferred over transactions.

1. The two ways a publish can silently fail

sequenceDiagram
    participant App as Order Service
    participant Broker as RabbitMQ
    participant Queue as inventory.queue

    App->>Broker: publish (mandatory)
    alt broker accepts and routes
        Broker->>Queue: enqueue
        Broker-->>App: confirm ack
    else broker cannot route (no matching queue)
        Broker-->>App: basic.return (unroutable)
        Broker-->>App: confirm ack
    else broker never received it
        Note over App,Broker: no confirm arrives (timeout)
    end

Two independent failures:

  • The broker never received or stored the message. A publisher confirm detects this.
  • The broker received it but could not route it to any queue. A return (with the mandatory flag) detects this.

You usually want both.


2. Publisher confirms

A publisher confirm is the broker asynchronously telling your app it took responsibility for a message. Enable it in application.yml:

spring:
  rabbitmq:
    publisher-confirm-type: correlated
    publisher-returns: true
    template:
      mandatory: true

correlated lets you attach CorrelationData to each publish so the callback can tell which message a confirm refers to.

@Bean
RabbitTemplate rabbitTemplate(ConnectionFactory cf) {
    RabbitTemplate template = new RabbitTemplate(cf);
    template.setConfirmCallback((correlation, ack, cause) -> {
        if (ack) {
            log.debug("Broker confirmed {}", correlation != null ? correlation.getId() : "?");
        } else {
            log.error("Broker rejected {}: {}", correlation, cause);
            // re-publish or alert
        }
    });
    return template;
}

Publish with correlation data:

CorrelationData correlation = new CorrelationData(orderId);
rabbitTemplate.convertAndSend(EXCHANGE, "order.created", event, correlation);

A negative confirm (ack = false) is rare and signals an internal broker problem, such as being unable to persist to disk. Treat it as a publish failure and retry or alert.


3. Returns and the mandatory flag

A confirm tells you the broker accepted the message. It does not tell you the message reached a queue. If no binding matches and no alternate exchange is configured, the broker drops it. Setting mandatory: true plus a returns callback makes the broker hand unroutable messages back to you.

template.setReturnsCallback(returned ->
    log.error("Unroutable message: exchange={} key={} body={}",
        returned.getExchange(), returned.getRoutingKey(),
        new String(returned.getMessage().getBody())));

An unroutable return almost always means a topology bug: a wrong routing key, or a queue that was never declared or bound. It should be loud in your logs.

Note: An alternate exchange (from Exchanges and Routing) is the broker-side complement: returns bring the message back to the publisher, while an alternate exchange captures it on the broker for later inspection. Use returns for alerting, an alternate exchange for a durable catch-all.


4. Confirms vs transactions

RabbitMQ also supports AMQP transactions (txSelect/txCommit), which make a publish synchronous and atomic. Avoid them for throughput-sensitive publishing: transactions are markedly slower because each commit is a blocking round trip.

AspectPublisher confirmsTransactions
StyleAsynchronous callbackSynchronous commit
ThroughputHighLow (blocking per commit)
RecommendationDefault choiceOnly when you need strict atomic semantics

Prefer confirms. They give you a reliability guarantee at a fraction of the cost.


5. What confirms do not solve

Confirms guarantee the broker accepted the message, not that your application decided to send the right message. The classic gap is the dual-write problem: your database commit succeeds but the publish fails (or vice versa). Solving that needs the transactional outbox pattern, covered in the Event-Driven Architecture section.


Checkpoint

You should now be able to:

  • Enable correlated publisher confirms and handle positive and negative acks.
  • Use the mandatory flag and returns callback to catch unroutable messages.
  • Attach correlation data to identify a confirmed message.
  • Explain why confirms are preferred over transactions, and what confirms do not cover.