skip to content

Spring AMQP / RabbitMQ

Spring AMQP for RabbitMQ: RabbitTemplate, @RabbitListener and its containers, declaring the exchange and queue topology, publisher confirms, and dead-letter and retry handling. Interviewers ask because Rabbit's routing model is where message-design decisions actually get made.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

explore

questions

25

What is dead-lettering in RabbitMQ/Spring AMQP, and how do you configure a dead-letter exchange on a queue?

level: juniorimportance: must knowfreq 70%

answer

  1. x-dead-letter-exchange queue arg
  2. reject/nack requeue=false, TTL, max-length
  3. DLX -> DLQ for inspection
  4. x-death header count/reason
  5. arguments set at declare time, immutable

basics

~10 s

Dead-lettering routes messages that can't be processed to a separate exchange instead of losing them. You set the queue arguments x-dead-letter-exchange (and optionally x-dead-letter-routing-key) when declaring the queue.

solid answer

~30 s

Dead-lettering is RabbitMQ's mechanism for shunting undeliverable messages to a designated 'dead-letter exchange' (DLX) so they aren't silently dropped. You attach it by declaring the main queue with the arguments x-dead-letter-exchange and optionally x-dead-letter-routing-key. A message is dead-lettered when: it's rejected/nacked with requeue=false, its per-message or per-queue TTL expires, or the queue exceeds a length limit. From the DLX it's routed to a dead-letter queue (DLQ) for inspection or reprocessing. In Spring AMQP you set these via QueueBuilder.withArgument(...) or Queue arguments in the bean definition. RabbitMQ adds an x-death header recording why and how often a message was dead-lettered.

code

java · 28 lines
java
@Configuration
class DlxConfig {

    @Bean
    Queue ordersQueue() {
        return QueueBuilder.durable("orders")
            .withArgument("x-dead-letter-exchange", "orders.dlx")
            .withArgument("x-dead-letter-routing-key", "orders.dead")
            .build();
    }

    @Bean
    DirectExchange ordersDlx() {
        return new DirectExchange("orders.dlx");
    }

    @Bean
    Queue ordersDlq() {
        return QueueBuilder.durable("orders.dlq").build();
    }

    @Bean
    Binding dlqBinding() {
        return BindingBuilder.bind(ordersDlq())
            .to(ordersDlx())
            .with("orders.dead");
    }
}

go deeper

for a junior

Know that dead-lettering saves failed messages to a separate queue and it's configured via the x-dead-letter-exchange queue argument.

for a middle

Know the three trigger conditions (reject-no-requeue, TTL, max-length) and that arguments are immutable at declare time.

for a senior

Explain the x-death header, DLX vs DLQ binding, and how the listener's exception maps to the reject that triggers dead-lettering.

for a principal

Design the topology (naming, bindings, alerting) and use x-death for delayed-retry loops and poison-message policy.

**Dead-lettering** is RabbitMQ's built-in safety net for messages that cannot be processed. Instead of discarding them, the broker re-routes them to a **dead-letter exchange (DLX)** — an ordinary exchange you nominate — which typically fans them into a **dead-letter queue (DLQ)** where you can inspect, alert on, or replay them. **How you configure it:** A DLX is not a special server-wide setting; it's a *queue argument* set when the queue is **declared** (arguments are immutable afterward — you must delete and recreate the queue to change them): - `x-dead-letter-exchange` — the name of the exchange dead-lettered messages are published to. - `x-dead-letter-routing-key` — optional; overrides the routing key used when republishing to the DLX. If omitted, the message's *original* routing key is reused. **When does a message get dead-lettered?** Three triggers: 1. The consumer **rejects** it with `basic.reject` or `basic.nack` and `requeue=false`. 2. The message's **TTL expires** (per-message `expiration` or the queue's `x-message-ttl`). 3. The queue hits a **length limit** (`x-max-length` / `x-max-length-bytes`) and the message is dropped from the head. **The x-death header:** When RabbitMQ dead-letters a message it stamps (or updates) an `x-death` header — an array of entries per queue/reason recording the `count`, `reason` (rejected / expired / maxlen), `queue`, `time`, original `exchange`, and `routing-keys`. This is how you can build retry-count logic or delayed-retry loops. **Spring AMQP specifics:** You declare the arguments on the `Queue` bean. Modern idiom uses `QueueBuilder`: ```java QueueBuilder.durable("orders") .withArgument("x-dead-letter-exchange", "orders.dlx") .withArgument("x-dead-letter-routing-key", "orders.dead") .build(); ``` Spring's `RabbitAdmin` declares these at startup. Importantly, the *application* usually decides *when* a message is unrecoverable — by having the listener throw an exception that the container translates into a reject-without-requeue (e.g. `AmqpRejectAndDontRequeueException`), which then triggers the broker's dead-lettering. **Gotcha:** Declaring `x-dead-letter-exchange` does nothing by itself unless something actually rejects-without-requeue, sets a TTL, or a length limit is hit. Many candidates think adding the argument alone routes failures — it must be paired with the right rejection behavior. Also, the DLX and DLQ must exist and be bound, or dead-lettered messages are silently dropped (unroutable).

  • You added x-dead-letter-exchange but messages never appear in the DLQ. Name two likely causes.
    Either nothing is actually rejecting with requeue=false (the listener requeues instead, e.g. defaultRequeueRejected=true and no AmqpRejectAndDontRequeueException), or the DLX has no binding to the DLQ so republished messages are unroutable and dropped.
  • Can you change a queue's dead-letter exchange after it's created?
    No — queue arguments are fixed at declaration. You must delete and redeclare the queue (or use a new queue), because RabbitMQ rejects a redeclare with different arguments as a channel-level PRECONDITION_FAILED error.

context

open as a page

What are RabbitMQ publisher confirms in Spring AMQP, and what problem do they solve?

level: juniorimportance: must knowfreq 62%

basics

~20 s

By default, sending a message via RabbitTemplate doesn't tell you if the broker actually received it. Publisher confirms make the broker send back an async ack (or nack) per message, so the producer knows the send succeeded.

open as a page

What do @RabbitListener and @EnableRabbit do, and how do you turn a method into a RabbitMQ consumer with Spring AMQP?

level: juniorimportance: must knowfreq 78%

basics

~20 s

@RabbitListener on a method makes it consume messages from a queue. @EnableRabbit (auto-configured by Spring Boot) switches on the machinery that scans for those annotations and starts listener containers that deliver each message to your method.

open as a page

What is RabbitTemplate and what does convertAndSend(exchange, routingKey, payload) do?

level: juniorimportance: must knowfreq 70%

basics

~10 s

RabbitTemplate is Spring AMQP's helper for sending and receiving RabbitMQ messages. convertAndSend takes a Java object, converts it to a message, and publishes it to the named exchange with the given routing key.

open as a page

In Spring AMQP, what are an Exchange, a Queue, and a Binding, and how do you declare each as a Spring bean?

level: juniorimportance: must knowfreq 70%

basics

~20 s

A Queue holds messages. An Exchange receives published messages and routes them. A Binding links an exchange to a queue with a routing rule. In Spring you declare each as a @Bean (Queue, DirectExchange, Binding).

open as a page

Explain nack-requeue vs reject-without-requeue in a Spring AMQP listener, and how the wrong choice creates a poison-message loop.

level: middleimportance: must knowfreq 75%

basics

~20 s

Nack with requeue=true puts the message back on the queue to be redelivered; reject (requeue=false) discards or dead-letters it. If a message always fails and you keep requeuing it, it's redelivered forever — a poison-message loop. Reject-without-requeue breaks the loop.

open as a page

How does the mandatory flag together with ReturnsCallback handle unroutable messages, and why can't publisher confirms alone detect them?

level: middleimportance: must knowfreq 55%

basics

~20 s

Set the mandatory flag on RabbitTemplate. If a published message can't be routed to any queue, the broker returns it and your ReturnsCallback fires. Confirms can't catch this because the broker still acks unroutable messages.

open as a page

Compare SimpleMessageListenerContainer and DirectMessageListenerContainer. How is their threading/channel model different and when would you choose each?

level: middleimportance: must knowfreq 66%

basics

~20 s

SimpleMessageListenerContainer uses a fixed pool of consumer threads, each with its own channel; you scale by adding consumers. DirectMessageListenerContainer dispatches messages onto a shared task executor, so one consumer per queue can serve many concurrent handlers. Direct scales more dynamically; Simple is the older default.

open as a page

How does MessageConverter work, and why configure Jackson2JsonMessageConverter?

level: middleimportance: must knowfreq 65%

basics

~10 s

A MessageConverter turns your Java objects into AMQP message bytes and back. The default uses Java serialization; Jackson2JsonMessageConverter serializes to/from JSON, which is portable across languages and human-readable.

open as a page

Compare DirectExchange, TopicExchange, and FanoutExchange in Spring AMQP. How does routing differ, and how do you build the binding for each?

level: middleimportance: must knowfreq 75%

basics

~20 s

Direct matches the routing key exactly. Topic matches wildcard patterns (* = one word, # = zero+ words). Fanout ignores the routing key and copies to every bound queue. You bind Direct/Topic with .with(key); Fanout binds with no key.

open as a page

Explain AcknowledgeMode AUTO vs MANUAL in Spring AMQP. When do you use MANUAL and how do you correctly ack/nack a message?

level: seniorimportance: must knowfreq 70%

basics

~20 s

AUTO means Spring acks the message for you after your listener returns normally, and nacks if it throws. MANUAL means you inject the Channel and delivery tag and call channel.basicAck / basicNack / basicReject yourself, giving full control over when a message is confirmed or requeued.

open as a page

Show how to configure a RabbitTemplate for correlated publisher confirms and returns, and explain what each piece does.

level: middleimportance: should knowfreq 38%

basics

~10 s

Set ConfirmType.CORRELATED and publisherReturns(true) on the CachingConnectionFactory, then on the RabbitTemplate call setMandatory(true), setConfirmCallback(...) for acks/nacks, and setReturnsCallback(...) for unroutable messages.

open as a page

How do the exchange and routing key arguments to convertAndSend interact with exchange types?

level: middleimportance: should knowfreq 40%

basics

~20 s

The exchange receives the message; the routing key is matched against the exchange's bindings to pick queues. Direct exchanges need an exact key match, topic exchanges use wildcard patterns, and fanout exchanges ignore the routing key entirely.

open as a page

How does @QueueBinding on a @RabbitListener declare topology, and when would you prefer it over separate @Bean declarations?

level: middleimportance: should knowfreq 60%

basics

~10 s

@RabbitListener's bindings attribute takes @QueueBinding, which nests @Queue, @Exchange, and a routing key. Spring declares that queue, exchange, and binding on the broker automatically, co-located with the consumer method — handy for listener-owned topology.

open as a page

What is RepublishMessageRecoverer and why prefer it over the default RejectAndDontRequeueRecoverer?

level: seniorimportance: should knowfreq 60%

basics

~20 s

It's a MessageRecoverer that, after retries are exhausted, republishes the failed message to a dead-letter exchange/queue and adds headers with the exception message and stack trace. Unlike the default recoverer, you get diagnostic context in the DLQ.

open as a page

Compare stateful vs stateless retry interceptors in Spring AMQP. When must you use stateful retry?

level: seniorimportance: should knowfreq 50%

basics

~20 s

Stateless retry loops in memory on the same delivery without going back to the broker between attempts. Stateful retry rejects and lets the broker redeliver, tracking attempts by a message key across redeliveries. Use stateful when each attempt needs a fresh transaction/redelivery.

open as a page

How do you correlate an asynchronous ack/nack back to the specific message that was sent, using CorrelationData?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Attach a CorrelationData (with a unique id) as an extra argument when you send. Because confirms are async and unordered, the ConfirmCallback receives that same CorrelationData, letting you look up which message the ack/nack belongs to. You can also await its CompletableFuture.

open as a page

How do concurrency settings and prefetch (prefetchCount) affect throughput, ordering, and fairness in a @RabbitListener? How do they interact?

level: seniorimportance: should knowfreq 58%

basics

~20 s

Concurrency sets how many consumer threads process in parallel; prefetch (basic.qos) sets how many unacked messages the broker sends each consumer before waiting for acks. More concurrency + higher prefetch = higher throughput but weaker ordering and worse load fairness; low prefetch spreads work evenly.

open as a page

How does RabbitTemplate implement request/reply RPC with sendAndReceive over reply-to?

level: seniorimportance: should knowfreq 45%

basics

~20 s

sendAndReceive (and convertSendAndReceive) sends a request and blocks waiting for a reply. RabbitTemplate sets a reply-to queue and a correlation id, the server replies to that queue, and the template matches the reply back to the caller.

open as a page

Explain how AmqpAdmin / RabbitAdmin auto-declares the broker topology from Spring beans. When does declaration happen, and what are the failure modes?

level: seniorimportance: should knowfreq 55%

basics

~20 s

RabbitAdmin (an AmqpAdmin) scans the context for Queue, Exchange, and Binding beans and declares them on the broker when a connection is first established, not at startup. Declaration is idempotent, but a property mismatch with an existing object fails with PRECONDITION_FAILED.

open as a page

Design a production retry-and-dead-letter topology that gives delayed (backoff) retries without blocking consumer threads, and caps attempts before parking to a DLQ.

level: principalimportance: should knowfreq 40%

basics

~20 s

Send failures to a 'wait' queue that has a TTL and no consumer; its dead-letter exchange routes expired messages back to the main queue after the delay. Count redeliveries via the x-death header and, once a cap is hit, route to a terminal parking DLQ instead of retrying.

open as a page

Design a reliable producer path: what do publisher confirms guarantee, what edge cases remain, and how would you achieve at-least-once publishing?

level: principalimportance: should knowfreq 30%

basics

~20 s

Confirms tell you the broker accepted a message, mandatory+returns catch unroutable ones. But confirms are async with no timeout and can be lost, and retries create duplicates. For at-least-once, persist an outbox row, publish, mark confirmed on ack, sweep/retry unconfirmed, and make consumers idempotent.

open as a page

What is a RabbitListenerContainerFactory and how would you architect multiple factories, error handling, retry, and dead-lettering for @RabbitListener across a service?

level: principalimportance: should knowfreq 42%

basics

~20 s

A RabbitListenerContainerFactory builds the listener container for each @RabbitListener. You define one (or several) as beans to centrally set connection, concurrency, prefetch, ack mode, converters, error handlers, and retry. Listeners pick one via containerFactory="name", letting you standardize error/DLQ policy per class of consumer.

open as a page

What is the AmqpTemplate abstraction and why should you depend on it rather than RabbitTemplate?

level: principalimportance: should knowfreq 30%

basics

~10 s

AmqpTemplate is the broker-agnostic interface that defines the core send/receive operations. RabbitTemplate is its RabbitMQ implementation. Depending on the interface decouples your code from the specific broker/implementation and eases testing.

open as a page

You need to declare many similarly-structured queues and bindings computed at runtime (e.g. one per shard), plus a dead-letter setup. How do you model this topology cleanly in Spring AMQP, and what trade-offs do you weigh?

level: principalimportance: nice to knowfreq 35%

basics

~20 s

Build the queues, exchange, and bindings in a loop and return them as a single Declarables @Bean so RabbitAdmin declares them all. For queues known only at runtime, inject AmqpAdmin and call declareQueue/declareBinding imperatively. Add DLX/DLQ via queue arguments.

open as a page