Exchanges — Cheat Sheet
RabbitMQ · 5 topics. Download the PDF or the Instagram carousel and share it.
Direct Exchange
Routes messages to queues whose binding key exactly matches the message's routing key; ideal for simple unicast routing such as task dispatch.
- ✓Direct exchange routes messages where routing key = binding key (exact match).
- ✓The default exchange is a pre-declared direct exchange — routing key must equal the queue name.
- ✓One exchange can route to multiple queues with different binding keys.
- ✓Multiple consumers on the same queue receive messages in round-robin (competing consumers).
- ✓Set prefetch (QoS) to limit unacked messages per consumer — prevents starvation of slow workers.
- ✓Direct exchange is ideal for simple task dispatch, worker pools, and point-to-point routing.
@Configuration
public class DirectExchangeConfig {
@Bean
public DirectExchange notificationExchange() {
return new DirectExchange("notifications");
}
// Email queue — bound with routing key "email"
@Bean public Queue emailQueue() {
return QueueBuilder.durable("notifications.email").build();
}
@Bean public Binding emailBinding(Queue emailQueue, DirectExchange exchange) {
return BindingBuilder.bind(emailQueue).to(exchange).with("email");
}
// SMS queue — bound with routing key "sms"
@Bean public Queue smsQueue() {
return QueueBuilder.durable("notifications.sms").build();
}
@Bean public Binding smsBinding(Queue smsQueue, DirectExchange exchange) {
return BindingBuilder.bind(smsQueue).to(exchange).with("sms");
}
}
// Producer — route to email or sms based on notification type
public void sendNotification(Notification n) {
rabbitTemplate.convertAndSend("notifications", n.getType().name().toLowerCase(), n);
// type=EMAIL → routing key "email" → notifications.email queue
// type=SMS → routing key "sms" → notifications.sms queue
}Fanout Exchange
Broadcasts every message to all bound queues regardless of routing key; perfect for publish-subscribe where all consumers need every event (e.g., cache invalidation).
- ✓Fanout exchange broadcasts every message to all bound queues — the routing key is completely ignored.
- ✓Each subscriber creates its own queue and binds to the fanout exchange; the publisher does not need to know how many subscribers exist.
- ✓Classic use cases: cache invalidation, broadcast notifications, audit logging, event fan-out to multiple pipelines.
- ✓For non-persistent events (cache invalidation), use non-durable fanout exchange + exclusive auto-delete queues per consumer instance.
- ✓Fanout is the most efficient exchange type (no routing key matching); use it over topic when all subscribers need all messages.
- ✓In Spring AMQP, BindingBuilder.bind(queue).to(fanoutExchange) — no .with() key needed.
Channel channel = connection.createChannel();
// Declare a durable fanout exchange
channel.exchangeDeclare("order-events", BuiltinExchangeType.FANOUT, true);
// Each subscriber declares its own queue and binds to the fanout exchange
// Routing key is ignored — pass "" by convention
channel.queueDeclare("order-events.notifications", true, false, false, null);
channel.queueDeclare("order-events.analytics", true, false, false, null);
channel.queueDeclare("order-events.audit-log", true, false, false, null);
channel.queueBind("order-events.notifications", "order-events", ""); // routing key ignored
channel.queueBind("order-events.analytics", "order-events", "");
channel.queueBind("order-events.audit-log", "order-events", "");
// Publisher — routing key irrelevant, all bound queues receive every message
AMQP.BasicProperties props = new AMQP.BasicProperties.Builder()
.deliveryMode(2).build(); // persistent
channel.basicPublish(
"order-events", // exchange
"", // routing key — ignored by fanout
props,
serialize(orderEvent)
);
// → delivered to notifications, analytics, AND audit-log queues simultaneouslyTopic Exchange
Routes by pattern-matching the routing key using * (one word) and # (zero or more words); enables flexible multi-level routing such as logs.error.# or payments.*.
- ✓`*` matches exactly one word; `#` matches zero or more words in a dot-delimited routing key.
- ✓A single routing key can match multiple queue bindings simultaneously — messages are delivered to all matching queues.
- ✓Binding pattern `#` alone turns a topic exchange into a fanout — use sparingly.
- ✓Topic exchanges combine the flexibility of routing keys with pattern-based subscription — one exchange can serve many consumer use cases.
- ✓In Spring AMQP, use BindingBuilder.bind(queue).to(topicExchange).with("pattern") for each binding.
- ✓Design routing key namespaces upfront: domain.entity.event (e.g., order.payment.failed) scales well to new event types.
# Routing key structure: <domain>.<entity>.<event> # Examples: # order.payment.succeeded # order.payment.failed # order.shipment.dispatched # notification.email.sent # audit.*.created # Binding patterns and what they match: # order.# → all order events (payment, shipment, etc.) # order.payment.* → order.payment.succeeded, order.payment.failed # *.payment.* → anything with "payment" in the middle # #.failed → any event ending in "failed" from any domain # # → every message (fanout behaviour)
Headers Exchange
Routes based on AMQP message header attributes instead of routing keys; supports x-match=all (AND) or x-match=any (OR) semantics for complex attribute-based routing.
- ✓Headers exchanges route based on message header key-value pairs, not the routing key.
- ✓x-match=all (AND): all specified headers must match; x-match=any (OR): at least one must match.
- ✓The routing key is ignored by headers exchanges — pass an empty string.
- ✓Set headers via MessageProperties.setHeader() on the producer side.
- ✓Headers exchanges are slower than direct/topic exchanges due to header evaluation overhead.
- ✓Use headers exchanges for multi-dimensional routing; prefer topic exchanges for most use cases.
@Configuration
public class HeadersExchangeConfig {
@Bean
public HeadersExchange notificationExchange() {
return new HeadersExchange("notifications.headers");
}
// Queue 1: receives messages with format=pdf AND priority=high
@Bean public Queue pdfHighQueue() {
return QueueBuilder.durable("notifications.pdf.high").build();
}
@Bean public Binding pdfHighBinding(Queue pdfHighQueue,
HeadersExchange exchange) {
return BindingBuilder.bind(pdfHighQueue).to(exchange)
.whereAll("format", "priority") // AND semantics
.matches(Map.of("format", "pdf", "priority", "high"));
}
// Queue 2: receives messages with format=email OR format=sms (any)
@Bean public Queue emailOrSmsQueue() {
return QueueBuilder.durable("notifications.email-or-sms").build();
}
@Bean public Binding emailOrSmsBinding(Queue emailOrSmsQueue,
HeadersExchange exchange) {
return BindingBuilder.bind(emailOrSmsQueue).to(exchange)
.whereAny("format") // OR semantics
.matches(Map.of("format", "email")); // OR format=sms (separate binding)
}
}Default Exchange
Every queue is automatically bound to the default (nameless) exchange with a routing key equal to its name; publishing directly to a queue name uses this exchange implicitly.
- ✓Default exchange is nameless ("") and pre-exists in every vhost — you cannot delete it
- ✓Every queue is auto-bound to the default exchange; routing key = queue name
- ✓convertAndSend(queueName, message) on RabbitTemplate uses the default exchange implicitly
- ✓Default exchange only supports exact-name routing — no wildcards, no fanout
- ✓For production, prefer named exchanges for clarity, flexibility, and testability
- ✓Default exchange bindings cannot be removed or modified — they are broker-managed
// Java AMQP client — explicit default exchange usage
channel.basicPublish(
"", // empty string = default exchange
"order.queue", // routing key = queue name
null,
message.getBytes()
);
// Spring RabbitTemplate shorthand (same behaviour)
@Autowired RabbitTemplate rabbitTemplate;
rabbitTemplate.convertAndSend("order.queue", orderEvent);
// Equivalent to: exchange="", routingKey="order.queue"