RabbitMQ’s headers exchange doesn’t actually look at the routing key; it routes messages based on header values instead.

Let’s see this in action. We’ll set up a scenario where we want to route messages to different queues based on a x-match header and some arbitrary key-value pairs in the headers.

First, let’s declare our exchange and queues. We’ll use a headers exchange named header_exchange.

rabbitmqadmin declare exchange name=header_exchange type=headers
rabbitmqadmin declare queue name=queue_x_match_all
rabbitmqadmin declare queue name=queue_x_match_any

Now, we bind the queues to the exchange with specific header conditions.

For queue_x_match_all, we want messages where all specified headers match.

rabbitmqadmin declare binding source=header_exchange destination=queue_x_match_all binding_key="" arguments='{"x-match": "all", "version": "1.0", "user_type": "premium"}'

For queue_x_match_any, we want messages where any of the specified headers match.

rabbitmqadmin declare binding source=header_exchange destination=queue_x_match_any binding_key="" arguments='{"x-match": "any", "region": "us-east", "service": "payment"}'

Notice the binding_key="". For headers exchanges, the routing key is ignored during routing. The magic happens in the arguments section of the binding.

Now, let’s publish some messages and observe where they go.

Publish a message that should go to queue_x_match_all:

rabbitmqadmin publish exchange=header_exchange routing_key="" payload="This is a premium user message." properties='{"headers": {"version": "1.0", "user_type": "premium"}}'

This message has both "version": "1.0" and "user_type": "premium" in its headers. Since the binding for queue_x_match_all has "x-match": "all", both conditions are met, and the message will be routed to queue_x_match_all.

Publish a message that should go to queue_x_match_any:

rabbitmqadmin publish exchange=header_exchange routing_key="" payload="This message is for the payment service." properties='{"headers": {"service": "payment", "timestamp": "1678886400"}}'

This message has "service": "payment" in its headers. The binding for queue_x_match_any has "x-match": "any", and it requires either "region": "us-east" OR "service": "payment". Since "service": "payment" matches, this message will be routed to queue_x_match_any.

Now, let’s publish a message that matches both bindings:

rabbitmqadmin publish exchange=header_exchange routing_key="" payload="This is a premium user message for the payment service." properties='{"headers": {"version": "1.0", "user_type": "premium", "service": "payment"}}'

This message will be routed to both queue_x_match_all (because it has version: 1.0 and user_type: premium) and queue_x_match_any (because it has service: payment).

The x-match header is crucial. When it’s set to "all", all header key-value pairs specified in the binding’s arguments must be present and match in the message’s headers for the message to be routed to that queue. When it’s set to "any", at least one of the header key-value pairs in the binding’s arguments must match a header in the message. If x-match is omitted from the binding arguments, it defaults to "all".

The routing key in a headers exchange binding is effectively ignored. The routing is entirely driven by the header conditions. This makes headers exchanges very flexible for scenarios where you need to route based on arbitrary attributes of a message, not just a predefined path.

What most people don’t realize is that the header values themselves can be more complex than simple strings. RabbitMQ supports comparisons for numbers and booleans, and you can even specify ranges. For instance, a binding argument like {"price_cents": {"$gt": 1000}} would route messages with a price_cents header greater than 1000. However, these advanced comparisons are only supported when publishing messages using AMQP 0-9-1 clients that can serialize these types correctly, and the header values must be of the appropriate numerical or boolean type.

The next step is to explore how to handle message prioritization alongside header-based routing.

Want structured learning?

Take the full Rabbitmq course →