An alternate exchange is a powerful tool for handling messages that can’t be routed to any queue, preventing them from being silently dropped.

Let’s see this in action. Imagine a scenario where we have a primary exchange, direct_exchange, and we want any messages published to it that don’t match any existing bindings to be sent to a fallback mechanism.

First, we declare our primary exchange and the alternate exchange.

# Declare the primary direct exchange
curl -X PUT \
  -H "Content-Type: application/json" \
  -d '{"type": "direct", "durable": true, "auto_delete": false, "internal": false, "arguments": {}}' \
  http://localhost:15672/api/exchanges/%2f/direct_exchange

# Declare the alternate exchange (could be any type, here a topic exchange)
curl -X PUT \
  -H "Content-Type: application/json" \
  -d '{"type": "topic", "durable": true, "auto_delete": false, "internal": false, "arguments": {}}' \
  http://localhost:15672/api/exchanges/%2f/alternate_exchange

Next, we set the alternate-exchange policy on the direct_exchange. This tells RabbitMQ to use alternate_exchange for any unroutable messages.

curl -X PUT \
  -H "Content-Type: application/json" \
  -d '{"description": "Set alternate exchange for direct_exchange", "policy": "ha-mode: all", "pattern": "^direct_exchange$", "apply-to": "exchanges", "definition": {"alternate-exchange": "alternate_exchange"}}' \
  http://localhost:15672/api/policies/%2f/set_alternate_exchange

Now, let’s create a queue and bind it to direct_exchange with a routing key.

# Declare a queue
curl -X PUT \
  -H "Content-Type: application/json" \
  -d '{"auto_delete": false, "durable": true, "arguments": {}}' \
  http://localhost:15672/api/queues/%2f/my_queue

# Bind the queue to the direct_exchange with a routing key
curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"routing_key": "important_key", "arguments": {}}' \
  http://localhost:15672/api/bindings/%2f/direct_exchange/queues/my_queue

We’ll also create a queue bound to alternate_exchange to catch the unroutable messages.

# Declare a queue for unroutable messages
curl -X PUT \
  -H "Content-Type: application/json" \
  -d '{"auto_delete": false, "durable": true, "arguments": {}}' \
  http://localhost:15672/api/queues/%2f/unroutable_messages_queue

# Bind the unroutable_messages_queue to the alternate_exchange with a wildcard routing key
curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"routing_key": "#", "arguments": {}}' \
  http://localhost:15672/api/bindings/%2f/alternate_exchange/queues/unroutable_messages_queue

Now, when we publish a message to direct_exchange with a routing key that doesn’t match important_key, it will be routed to alternate_exchange and then to unroutable_messages_queue.

# Publish a message with a matching routing key (goes to my_queue)
curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"properties": {}, "routing_key": "important_key", "payload": "This is an important message", "payload_encoding": "string"}' \
  http://localhost:15672/api/exchanges/%2f/direct_exchange/publish

# Publish a message with a non-matching routing key (goes to alternate_exchange -> unroutable_messages_queue)
curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"properties": {}, "routing_key": "other_key", "payload": "This message is unroutable!", "payload_encoding": "string"}' \
  http://localhost:15672/api/exchanges/%2f/direct_exchange/publish

If you check the my_queue, you’ll find the first message. If you check unroutable_messages_queue, you’ll find the second. The alternate-exchange configuration itself is applied as a policy to the exchange. When a message arrives at direct_exchange, RabbitMQ first tries to find a binding for the given routing key. If no binding is found, instead of returning the message to the publisher (if mandatory flag is set) or dropping it, RabbitMQ publishes it to the exchange specified by the alternate-exchange argument. This alternate exchange can then route the message to a designated queue for logging, dead-lettering, or further processing.

The alternate-exchange argument is not inherited. If you have an exchange that is itself an alternate exchange for another, you must explicitly configure the alternate-exchange argument on the inner exchange as well if you want unroutable messages from that exchange to be handled. This means that if alternate_exchange itself has unroutable messages, they won’t automatically go to another fallback unless it also has its own alternate-exchange configured.

The most surprising true thing about alternate exchanges is that they don’t require the alternate exchange to be of the same type as the primary exchange. You can have a direct exchange use a topic exchange as its alternate, or vice-versa, allowing for flexible routing strategies for unroutable messages.

This mechanism doesn’t inherently guarantee message delivery to the alternate exchange if the alternate exchange itself is unavailable or misconfigured. If the alternate exchange is deleted or has no available queues bound to it, messages sent to it might still be dropped or returned, depending on the publisher’s mandatory flag and immediate flag settings.

The next concept you’ll likely encounter is how to manage the lifecycle and monitoring of these unroutable messages, which often leads to exploring dead-lettering strategies in conjunction with alternate exchanges.

Want structured learning?

Take the full Rabbitmq course →