Topic Exchange
IntermediateRoutes 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.*.
Overview
The topic exchange is the most flexible exchange type in RabbitMQ. It routes messages by matching the message's routing key (a dot-delimited string, e.g., order.payment.failed) against binding patterns on queues. Two wildcards power the pattern matching: `*` matches exactly one word (one dot-delimited segment), and `#` matches zero or more words. This makes it the go-to choice for complex routing scenarios — for example, a log aggregation system where different queues receive error-only logs, payment-specific events, or all events from a given service. A single topic exchange can replace a combination of direct and fanout exchanges for most real-world routing needs.
Wildcard Patterns — * and #
Routing keys for topic exchanges are dot-delimited strings (e.g., `order.payment.failed`). Binding patterns use:
`*` — matches exactly one word: `order.*` matches `order.created` and `order.cancelled` but NOT `order.payment.failed` (two words after dot).
`#` — matches zero or more words: `order.#` matches `order`, `order.created`, and `order.payment.failed`. `#` alone matches every routing key (behaves like fanout).
A queue can have multiple bindings on a topic exchange, receiving messages that match any of its patterns (OR semantics).
# 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)Declaring a Topic Exchange — Low-Level Java
Declare the exchange as type `topic`, then bind queues with their respective patterns. A queue can have multiple bindings; messages matching any binding are delivered to that queue.
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// Declare a durable topic exchange
channel.exchangeDeclare("events", BuiltinExchangeType.TOPIC, true);
// Declare queues
channel.queueDeclare("payment-events-queue", true, false, false, null);
channel.queueDeclare("all-order-events-queue", true, false, false, null);
channel.queueDeclare("failed-events-queue", true, false, false, null);
// Bind queues with patterns
channel.queueBind("payment-events-queue", "events", "order.payment.*");
channel.queueBind("all-order-events-queue", "events", "order.#");
channel.queueBind("failed-events-queue", "events", "#.failed");
// Publish a message — the routing key determines which queues receive it
String routingKey = "order.payment.failed";
channel.basicPublish("events", routingKey, null, payload.getBytes());
// → delivered to: payment-events-queue (order.payment.* ✓)
// → delivered to: all-order-events-queue (order.# ✓)
// → delivered to: failed-events-queue (#.failed ✓)Spring AMQP — TopicExchange Configuration
Spring AMQP provides clean builder-style beans for exchanges, queues, and bindings. Declare a TopicExchange, Queues, and BindingBuilder.bind(...).to(exchange).with(pattern).
@Configuration
public class MessagingConfig {
@Bean
public TopicExchange eventsExchange() {
return new TopicExchange("events", true, false);
}
@Bean
public Queue paymentEventsQueue() {
return QueueBuilder.durable("payment-events-queue").build();
}
@Bean
public Queue allOrderEventsQueue() {
return QueueBuilder.durable("all-order-events-queue").build();
}
@Bean
public Binding paymentBinding(Queue paymentEventsQueue, TopicExchange eventsExchange) {
return BindingBuilder.bind(paymentEventsQueue)
.to(eventsExchange)
.with("order.payment.*");
}
@Bean
public Binding allOrderBinding(Queue allOrderEventsQueue, TopicExchange eventsExchange) {
return BindingBuilder.bind(allOrderEventsQueue)
.to(eventsExchange)
.with("order.#");
}
}
// Publisher
@Service
@RequiredArgsConstructor
public class EventPublisher {
private final RabbitTemplate rabbitTemplate;
public void publishPaymentFailed(String orderId) {
rabbitTemplate.convertAndSend(
"events",
"order.payment.failed", // routing key
new PaymentFailedEvent(orderId)
);
}
}Key Points to Remember
- 1`*` matches exactly one word; `#` matches zero or more words in a dot-delimited routing key.
- 2A single routing key can match multiple queue bindings simultaneously — messages are delivered to all matching queues.
- 3Binding pattern `#` alone turns a topic exchange into a fanout — use sparingly.
- 4Topic exchanges combine the flexibility of routing keys with pattern-based subscription — one exchange can serve many consumer use cases.
- 5In Spring AMQP, use BindingBuilder.bind(queue).to(topicExchange).with("pattern") for each binding.
- 6Design routing key namespaces upfront: domain.entity.event (e.g., order.payment.failed) scales well to new event types.
Interview Questions
Sign in to ask AriaWhat is the difference between * and # wildcards in a RabbitMQ topic exchange?
How is a topic exchange different from a direct exchange?
A message with routing key "order.payment.failed" is published. Which queues with bindings "order.#", "*.payment.*", and "order.created" receive it?
How would you design a routing key namespace for a multi-service event bus?
Can one queue have multiple bindings on a topic exchange? What are the semantics?
Ask Aria about Topic Exchange
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.