Skip to content

Headers & Choosing an Exchange

A headers exchange ignores the routing key entirely and routes on the message’s headers — arbitrary key/value pairs in the message properties. A binding specifies the headers a message must carry to match, plus a special argument x-match:

  • x-match: all — the message must have every header in the binding (a logical AND).
  • x-match: any — the message must have at least one (a logical OR).

For example, a binding of { format: "pdf", type: "report", x-match: "all" } matches only messages whose headers include both format=pdf and type=report.

flowchart LR
  p["publish
headers: {format: pdf, type: report}"] --> x["headers exchange"]
  x -->|"x-match: all
format=pdf, type=report"| q1["queue: pdf-reports"]
  x -. "x-match: all
format=png" .-> q2["queue: images"]
A headers exchange matching on header values

Headers routing is powerful when your criteria are genuinely multi-dimensional and don’t compress neatly into one dotted string. In practice it’s the least used type: a well-designed topic routing key usually expresses the same intent more simply and is faster to match. Reach for headers only when routing truly depends on several independent attributes.

Most routing decisions come down to a short table. Match what you want to the simplest type that expresses it:

What you wantExchange typeHow
One clear destination category per messagedirectBind by exact routing key
Broadcast every message to all subscribersfanoutEach subscriber binds its own queue
Subscribers filter by flexible patternstopicBind with * / # patterns on a dotted key
Route on several independent attributesheadersBind with header conditions + x-match

A few rules of thumb:

  • Start with the simplest type that works. If a single exact key is enough, use direct — don’t reach for topic speculatively.
  • Default an event bus to topic. It subsumes direct and fanout, so you keep room to add finer subscribers without changing producers.
  • Avoid headers unless you must. A good dotted routing key almost always beats it on clarity and speed.
  • Design the routing-key scheme early. domain.detail.action (like order.eu.created) is a convention worth agreeing on before the first producer ships.
What does a headers exchange route on?
A headers binding uses "x-match: any". A message matches when it has:
Which exchange type is the best default for a flexible event bus?
Why is the headers exchange rarely used in practice?