Publish-Subscribe Pattern
BeginnerFanout exchange broadcasts events to all bound queues; each consumer group has its own queue, receiving every event independently for fan-out notification scenarios.
Overview
Publish-Subscribe (pub/sub) decouples producers from consumers: a producer publishes an event to an exchange, and every subscriber that has bound a queue to that exchange receives a copy. In RabbitMQ this is achieved with a fanout exchange — the exchange ignores the routing key and delivers the message to every bound queue. Each subscriber (consumer group) has its own dedicated queue, so adding new subscribers never affects existing ones. Topic exchanges extend the pattern by allowing wildcard routing keys (e.g. order.# delivers to any consumer bound to order.*), enabling content-based filtering while still supporting multiple independent consumers. Spring AMQP simplifies pub/sub setup with its FanoutExchange, Queue, and Binding beans.
Fanout exchange — broadcasting to all subscribers
A fanout exchange copies every received message to all bound queues regardless of routing key. Create one queue per subscriber (consumer group). If the same service runs multiple instances, all instances should share the same queue — RabbitMQ will round-robin messages among them. Use auto-delete queues for transient subscribers (e.g., live UI dashboards) and durable queues for persistent consumers (e.g., email notifications).
// Spring AMQP fanout pub/sub configuration
@Configuration
public class PubSubConfig {
@Bean
public FanoutExchange orderEventsExchange() {
return new FanoutExchange("order.events", true, false);
}
@Bean public Queue emailNotificationQueue() {
return QueueBuilder.durable("order.events.email").build();
}
@Bean public Queue inventoryUpdateQueue() {
return QueueBuilder.durable("order.events.inventory").build();
}
@Bean public Queue analyticsQueue() {
return QueueBuilder.durable("order.events.analytics").build();
}
// Bind all queues to the exchange — routing key ignored for fanout
@Bean public Binding emailBinding(FanoutExchange ex, Queue emailNotificationQueue) {
return BindingBuilder.bind(emailNotificationQueue).to(ex);
}
@Bean public Binding inventoryBinding(FanoutExchange ex, Queue inventoryUpdateQueue) {
return BindingBuilder.bind(inventoryUpdateQueue).to(ex);
}
@Bean public Binding analyticsBinding(FanoutExchange ex, Queue analyticsQueue) {
return BindingBuilder.bind(analyticsQueue).to(ex);
}
}Publishing and consuming events independently
Producers send to the exchange without knowing who the subscribers are. Each consumer service listens on its own queue with @RabbitListener. This loose coupling lets you add or remove subscribers without changing the publisher or any other consumer.
// Publisher — sends to exchange, not a queue
@Service
public class OrderEventPublisher {
private final RabbitTemplate rabbitTemplate;
public void publishOrderPlaced(OrderPlacedEvent event) {
rabbitTemplate.convertAndSend(
"order.events", // exchange name
"", // routing key ignored by fanout
event
);
}
}
// Email consumer — independent of all other consumers
@Service
public class EmailNotificationConsumer {
@RabbitListener(queues = "order.events.email")
public void handleOrderPlaced(OrderPlacedEvent event) {
emailService.sendConfirmation(event.getCustomerEmail(), event.getOrderId());
}
}
// Inventory consumer — processes same event independently
@Service
public class InventoryConsumer {
@RabbitListener(queues = "order.events.inventory")
public void handleOrderPlaced(OrderPlacedEvent event) {
inventoryService.reserveStock(event.getItems());
}
}Topic exchange for selective fan-out
A topic exchange allows wildcard routing keys: * matches exactly one word, # matches zero or more words. Consumers bind with patterns like order.# (all order events) or *.placed (any entity placed event). This enables content-based filtering while still allowing multiple consumers to receive the same message.
// Topic exchange — selective fan-out with routing patterns
@Bean
public TopicExchange orderTopicExchange() {
return new TopicExchange("order.topic.events");
}
// Email service subscribes to ALL order events
@Bean
public Binding emailTopicBinding(TopicExchange ex, Queue emailNotificationQueue) {
return BindingBuilder.bind(emailNotificationQueue)
.to(ex).with("order.#");
}
// Fraud service only cares about high-value placed orders
@Bean
public Binding fraudTopicBinding(TopicExchange ex, Queue fraudQueue) {
return BindingBuilder.bind(fraudQueue)
.to(ex).with("order.placed.highvalue");
}
// Producer sets specific routing key matching subscriber patterns
rabbitTemplate.convertAndSend("order.topic.events", "order.placed.highvalue", event);
// email consumer receives it (order.# matches)
// fraud consumer receives it (order.placed.highvalue matches exactly)
// inventory consumer with "order.shipped.#" does NOT receive itKey Points to Remember
- 1Fanout exchange ignores routing keys and delivers to all bound queues — ideal for broadcasting domain events
- 2Each subscriber gets its own queue; multiple instances of the same subscriber share one queue for load balancing
- 3Topic exchanges extend pub/sub with wildcard routing keys: * (one word) and # (zero or more words)
- 4Producers are decoupled from consumers — new subscribers can be added without any publisher changes
- 5Use durable queues + persistent messages for subscribers that must not miss events during restart
- 6Auto-delete queues (autoDelete=true) are useful for transient subscribers like live dashboard WebSocket feeds
Interview Questions
Sign in to ask AriaWhat is the difference between a fanout exchange and a topic exchange in RabbitMQ?
In a pub/sub setup, if the same service runs 3 instances, should they share one queue or each have their own? Why?
How would you ensure a slow subscriber does not cause message build-up that affects other subscribers?
What happens to messages in a fanout exchange if no queues are bound to it at publish time?
How would you implement event replay for new subscribers that join after messages were published?
Ask Aria about Publish-Subscribe Pattern
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.