Skip to content

Headers Exchange — Complete Guide

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Headers Exchange. We cover key concepts, practical examples, and best practices to help you master this topic.

A headers exchange routes messages based on header attributes instead of routing keys, enabling flexible attribute-based routing with multiple matching conditions.

What You'll Learn

By the end of this lesson, you will understand how headers exchanges work, the x-match argument for any/all matching, when to use headers over topic exchanges, and how to implement attribute-based routing.

Why It Matters

Sometimes routing decisions depend on multiple attributes, not just a single routing key. A message might need to match by content type, priority, and source simultaneously. Headers exchanges let you route based on arbitrary key-value pairs in the message headers.

Real-World Use

A data pipeline processes files with different formats (JSON, CSV, XML), sources (internal, partner), and priorities. A headers exchange routes files to different processing queues based on these attributes. A JSON file from a partner with high priority goes to a fast processing queue.

Headers Exchange Flow

flowchart LR
    P[Publisher] --> H[Headers Exchange]
    H -->|"x-match: all
format=json, source=partner"| Q1[Partner JSON Queue] H -->|"x-match: any
priority=high, source=partner"| Q2[Priority Queue] H -->|"x-match: all
format=csv"| Q3[CSV Queue] Q1 --> C1[Consumer] Q2 --> C2[Consumer] Q3 --> C3[Consumer] style H fill:#f90,color:#fff

Implementing Headers Exchange

import pika
import json

connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

channel.exchange_declare(exchange='data_router', exchange_type='headers', durable=True)

messages = [
    ({'format': 'json', 'source': 'partner', 'priority': 'high'}, {'file': 'data.json'}),
    ({'format': 'csv', 'source': 'internal', 'priority': 'low'}, {'file': 'report.csv'}),
    ({'format': 'xml', 'source': 'partner', 'priority': 'normal'}, {'file': 'feed.xml'}),
]

for headers, data in messages:
    channel.basic_publish(
        exchange='data_router',
        routing_key='',
        body=json.dumps(data),
        properties=pika.BasicProperties(headers=headers, delivery_mode=2)
    )
    print(f"Published with headers: {headers}")

connection.close()

Expected output:

Published with headers: {'format': 'json', 'source': 'partner', 'priority': 'high'}
Published with headers: {'format': 'csv', 'source': 'internal', 'priority': 'low'}
Published with headers: {'format': 'xml', 'source': 'partner', 'priority': 'normal'}
# Consumer 1: JSON files from partners (all must match)
import pika, json

conn = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
ch = conn.channel()
ch.exchange_declare(exchange='data_router', exchange_type='headers', durable=True)
q = ch.queue_declare(queue='partner_json', durable=True)

args = {
    'x-match': 'all',
    'format': 'json',
    'source': 'partner',
}
ch.queue_bind(exchange='data_router', queue=q.method.queue, arguments=args)
print("Partner JSON consumer bound")

def cb(c, m, p, body):
    print(f"[Partner JSON] {body.decode()} (headers: {p.headers})")

ch.basic_consume(queue=q.method.queue, on_message_callback=cb, auto_ack=True)
ch.start_consuming()

Expected output:

Partner JSON consumer bound
[Partner JSON] {"file": "data.json"} (headers: {'format': 'json', ...})

Only the message with format=json AND source=partner reaches this queue.

x-match: all vs any

x-match: all — All specified headers must match. Use this when every condition is required.

x-match: any — Any one of the specified headers must match. Use this for OR conditions.

# Consumer 2: High priority OR partner source (any match)
args = {
    'x-match': 'any',
    'priority': 'high',
    'source': 'partner',
}
ch.queue_bind(exchange='data_router', queue='priority_queue', arguments=args)

This queue receives messages where priority=high OR source=partner.

Headers vs Topic Exchange

Feature Headers Exchange Topic Exchange
Matching Header attributes Routing key pattern
Conditions AND/OR (x-match) Pattern match only
Flexibility Multiple attributes Single key hierarchy
Complexity Higher setup Simpler
Use case Attribute-based routing Hierarchical routing

Common Mistakes

1. Using Headers for Simple Routing

If you only need to match by one attribute, use a direct or topic exchange. Headers exchanges are overkill for single-attribute routing.

2. Forgetting x-match Argument

Without x-match, the binding never matches. x-match is required and must be all or any. Other arguments are the matching headers.

3. Confusing Header Types

Headers values are strings by default. RabbitMQ converts them to specific types based on the value. True becomes a boolean, [1,2,3] becomes a table. Be consistent with types between publishers and bindings.

4. Not Using Headers for Complex Routing

When you need AND/OR logic across multiple attributes, headers exchanges are the right tool. Trying to encode multiple attributes in a routing key (high.partner.json) is fragile.

5. Ignoring Performance

Headers exchanges are slower than direct or topic because the broker must evaluate multiple header conditions. For high-throughput systems, benchmark before committing.

Practice Questions

1. How does a headers exchange route messages?

It matches message header attributes against binding arguments. All matching headers (or any, depending on x-match) must be present for the message to be routed to the queue.

2. What is the x-match argument?

x-match determines whether all (all) or any (any) of the specified headers must match. x-match=all requires every header to match. x-match=any requires at least one header to match.

3. When would you use headers over topic exchange?

When routing depends on multiple independent attributes rather than a hierarchical key. For example, routing by format, source, and priority simultaneously.

4. Can a headers exchange have bindings without x-match?

No. x-match is required for all headers exchange bindings. Without it, the binding never matches and no messages are routed to that queue.

Challenge

Design a headers exchange routing Strategy for a document processing pipeline. Documents have type (invoice, report, contract), source (email, api, upload), sensitivity (public, internal, confidential), and language (en, es, fr). Define bindings for different processing steps.

FAQ

What header types does RabbitMQ support?

RabbitMQ headers support strings, integers, decimals, booleans, arrays, and tables (nested dictionaries). The type is inferred from the value.

Can I use negative matching in headers?

No. Headers exchanges only match on header existence and value equality. For negative matching, filter in the consumer.

Do headers exchanges work with Kafka?

Kafka does not have header-based routing. Kafka filters by topic partition key. Header-based filtering must be done in the consumer.

What happens if a header value is missing?

For x-match=all, the message is not routed to that queue. For x-match=any, other headers may still match.

Are headers exchanges slower than topic?

Yes, by 10-30% depending on the number of headers being compared. The overhead is negligible for most use cases.

Mini Project: Document Router

import pika
import json

conn = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
ch = conn.channel()
ch.exchange_declare(exchange='doc_router', exchange_type='headers', durable=True)

bindings = [
    ('invoice_queue', {'x-match': 'all', 'type': 'invoice', 'sensitivity': 'public'}),
    ('confidential_queue', {'x-match': 'all', 'sensitivity': 'confidential'}),
    ('partner_queue', {'x-match': 'any', 'source': 'partner', 'priority': 'high'}),
]

for queue_name, args in bindings:
    ch.queue_declare(queue=queue_name, durable=True)
    ch.queue_bind(exchange='doc_router', queue=queue_name, arguments=args)
    print(f"Bound {queue_name}: {args}")

docs = [
    ({'type': 'invoice', 'sensitivity': 'public', 'source': 'email'}, 'Invoice #123'),
    ({'type': 'contract', 'sensitivity': 'confidential', 'source': 'api'}, 'Contract ABC'),
    ({'type': 'report', 'sensitivity': 'public', 'source': 'partner'}, 'Partner Report'),
]

for headers, body in docs:
    ch.basic_publish(
        exchange='doc_router', routing_key='', body=body,
        properties=pika.BasicProperties(headers=headers, delivery_mode=2)
    )
    print(f"Published: {body} ({headers})")

conn.close()

Expected output:

Bound invoice_queue: {'x-match': 'all', 'type': 'invoice', 'sensitivity': 'public'}
Bound confidential_queue: {'x-match': 'all', 'sensitivity': 'confidential'}
Bound partner_queue: {'x-match': 'any', 'source': 'partner', 'priority': 'high'}
Published: Invoice #123 ({'type': 'invoice', 'sensitivity': 'public', 'source': 'email'})
Published: Contract ABC ({'type': 'contract', 'sensitivity': 'confidential', 'source': 'api'})
Published: Partner Report ({'type': 'report', 'sensitivity': 'public', 'source': 'partner'})

What's Next

Now that you understand headers exchanges, explore message tracing to learn how to trace messages across Distributed Systems, then apply everything in the mini project: multi-service event bus.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro