skip to content

Exchanges, Queues & Bindings

Declaring exchanges, queues and bindings as beans lets the application create its own broker topology at startup, direct, topic or fanout. Interviewers ask you to pick an exchange type for a scenario, which is really a question about routing design.

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

explore

questions

5

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%

answer

  1. publish to exchange, not queue
  2. Binding = exchange -> queue + key
  3. org.springframework.amqp.core beans
  4. RabbitAdmin declares on first connection
  5. new Queue() = durable by default

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).

solid answer

~40 s

In RabbitMQ, producers never publish to a queue directly — they publish to an Exchange, which routes the message to Queues according to Bindings. A Queue buffers messages for consumers; a Binding connects an exchange to a queue and (for most exchange types) carries a routing key that decides which messages match. In Spring AMQP (org.springframework.amqp.core), you model this topology as Spring beans: a Queue bean, an Exchange bean (DirectExchange, TopicExchange, or FanoutExchange), and a Binding bean built with BindingBuilder.bind(queue).to(exchange).with(routingKey). When these beans exist in the context, Spring's RabbitAdmin declares them on the broker automatically the first time a connection opens, so you rarely call the broker API by hand.

code

java · 23 lines
java
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.*;

@Configuration
public class OrdersTopology {

    @Bean
    Queue ordersQueue() {
        return new Queue("orders.q"); // durable=true, exclusive=false, autoDelete=false
    }

    @Bean
    DirectExchange ordersExchange() {
        return new DirectExchange("orders.x");
    }

    @Bean
    Binding ordersBinding(Queue ordersQueue, DirectExchange ordersExchange) {
        return BindingBuilder.bind(ordersQueue)
                             .to(ordersExchange)
                             .with("orders.created"); // routing key
    }
}

go deeper

for a junior

Must know the exchange -> binding -> queue chain and that you declare each as a bean.

for a middle

Should know default queue properties and that RabbitAdmin does the declaring.

for a senior

Should discuss idempotent declaration and the PRECONDITION_FAILED mismatch gotcha.

for a principal

Frames topology-as-code ownership, version control of the broker model, and drift between beans and live broker state.

## The AMQP model RabbitMQ follows the AMQP 0-9-1 model, which separates *where you publish* from *where messages land*: - **Exchange** — the entry point. Producers publish a message plus a **routing key** to an exchange. The exchange itself stores nothing; it only decides which queues (if any) get a copy. - **Queue** — an ordered buffer that actually stores messages until a consumer acknowledges them. - **Binding** — a rule that connects an exchange to a queue. For most exchange types the binding includes a **binding key** (a.k.a. routing pattern); a message is delivered to the queue when its routing key matches the binding key according to that exchange's rules. A message with no matching binding is simply dropped (unless the publisher set the `mandatory` flag or the exchange has an alternate-exchange configured — details outside this leaf). ## Declaring topology in Spring AMQP All topology types live in `org.springframework.amqp.core`. You express the broker model as Spring beans: ```java @Bean Queue ordersQueue() { return new Queue("orders.q"); } // durable, non-exclusive, non-auto-delete by default @Bean DirectExchange ordersExchange() { return new DirectExchange("orders.x"); } @Bean Binding ordersBinding(Queue ordersQueue, DirectExchange ordersExchange) { return BindingBuilder.bind(ordersQueue).to(ordersExchange).with("orders.created"); } ``` - `new Queue(name)` defaults to **durable=true, exclusive=false, autoDelete=false**. Use the overloaded constructors or `QueueBuilder` (`QueueBuilder.durable(name).build()`) to change these or add arguments (TTL, DLX, max-length). - `BindingBuilder` is a fluent API: `bind(queue).to(exchange).with(key)`. ## How the beans reach the broker Spring does **not** create anything on the broker just because the beans exist. A `RabbitAdmin` (an `AmqpAdmin` implementation) scans the application context for `Queue`, `Exchange`, and `Binding` beans and **declares them on the broker when the connection is first established**. Spring Boot auto-configures a `RabbitAdmin` whenever a `ConnectionFactory` is present, so this usually just works. ## Key terms defined - **Routing key**: a string set by the publisher on each message. - **Binding key / pattern**: the string on the binding the exchange compares against. - **Durable**: survives a broker restart (the definition persists; message persistence is a separate per-message concern). - **Exclusive**: usable by only the declaring connection. - **Auto-delete**: removed when its last consumer/binding goes away. ## When to use Always declare topology as beans for anything your app owns, so it is created idempotently on startup and version-controlled in code. Fall back to declaring it on the listener with `@QueueBinding` when the queue is listener-specific. ## Gotcha If a queue/exchange already exists on the broker with **different properties** than your bean, redeclaration fails with a broker `PRECONDITION_FAILED` (channel-level) error — Spring will log it and the connection recovery loop may keep retrying. Keep bean definitions in sync with what is actually on the broker.

  • Who actually creates these queues and exchanges on the broker, and when?
    A RabbitAdmin (AmqpAdmin) bean — auto-configured by Spring Boot — scans the context for Queue/Exchange/Binding beans and declares them on the broker the first time a connection is opened, not at bean-instantiation time.
  • What are the default properties of new Queue("x")?
    Durable, non-exclusive, non-auto-delete. Use QueueBuilder or the multi-arg constructor to change them or add arguments like TTL or a dead-letter exchange.

saying these in an interview costs you the question

  • Saying producers publish directly to a queue (they publish to an exchange)
  • Thinking the beans are declared on the broker at context startup rather than on first connection
  • Assuming new Queue(name) is non-durable

context

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

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

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

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