# Exchanges and Routing (/connectors/rabbitmq/concepts/exchanges-and-routing)



In the KubeMQ RabbitMQ connector, exchanges and bindings are **virtual connector-side routing
metadata, not data stores**. There is no exchange object holding messages — a publish to an
exchange is resolved into a *set of target queues* **at publish time**, and each resolved queue is
written to its KubeMQ Queue channel `amqp.{vhost}.{queue}`. This guide covers the exchange types,
topic wildcards, declare idempotency, and what happens to the routing result.

<Callout type="info">
  Every AMQP queue maps to a single KubeMQ **Queue** channel `amqp.{vhost}.{queue}`. Exchanges and
  bindings exist only to *select which queues a publish lands in* — they are evaluated by the
  connector at publish time, then discarded. See
  [Architecture](/connectors/rabbitmq/concepts/architecture).
</Callout>

## How a publish routes [#how-a-publish-routes]

<Mermaid
  chart="graph LR
  PUB[&#x22;Publisher<br/>basic.publish(exchange, routing-key)&#x22;]
  EX[&#x22;Exchange matcher<br/>(virtual: direct / fanout / topic / headers / default)&#x22;]
  Q1[&#x22;Queue amqp.vhost.orders&#x22;]
  Q2[&#x22;Queue amqp.vhost.audit&#x22;]
  BROKER[&#x22;Message Broker&#x22;]

  PUB --> EX
  EX -->|matched binding| Q1
  EX -->|matched binding| Q2
  Q1 --> BROKER
  Q2 --> BROKER

  classDef queues stroke-width:2px;
  class Q1,Q2 queues;"
/>

The matcher produces a set of target queues, deduplicates it, runs a per-queue authorization
check, and then writes the message to each surviving queue's KubeMQ channel.

## Exchange types [#exchange-types]

| Exchange type      | Routing semantics                                                                                                        | Pre-declared (per vhost)              |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------- |
| **default** (`""`) | Implicit per-queue binding; `routing-key` = queue name; a missing queue is unroutable                                    | implicit                              |
| **direct**         | Exact `routing-key` match; multiple bindings on the same key all match                                                   | `amq.direct` (durable)                |
| **fanout**         | All bound queues; routing key ignored                                                                                    | `amq.fanout` (durable)                |
| **topic**          | Trie matcher; `*` = exactly one word, `#` = zero or more words, `.` = word separator                                     | `amq.topic` (durable)                 |
| **headers**        | `x-match` ∈ {`all`, `any`, `all-with-x`, `any-with-x`} (default `all`); `x-`-prefixed headers are excluded from matching | `amq.headers` / `amq.match` (durable) |

## Pre-declared `amq.*` exchanges [#pre-declared-amq-exchanges]

The `amq.direct`, `amq.fanout`, `amq.topic`, `amq.headers`, and `amq.match` exchanges are
pre-declared durable per vhost. They:

* **cannot be deleted** by a client → `403 access-refused`;
* **cannot be redeclared with different args** → `406 precondition-failed`;
* a client declaring **any** exchange with an `amq.*` prefix → `403`.

## Topic wildcards [#topic-wildcards]

The topic matcher uses `.` as the word separator:

| Token | Matches                |
| ----- | ---------------------- |
| `*`   | exactly **one** word   |
| `#`   | **zero or more** words |

Worked examples:

| Binding pattern | Matches                          | Does NOT match                          |
| --------------- | -------------------------------- | --------------------------------------- |
| `stock.*.nyse`  | `stock.ibm.nyse`                 | `stock.ibm.us.nyse` (two words for `*`) |
| `stock.#`       | `stock`, `stock.a`, `stock.a.b`  | `stocks.a`                              |
| `#`             | any key, including the empty key | —                                       |
| `*.orange.*`    | `quick.orange.rabbit`            | `lazy.orange.elephant.x`                |

## Bindings and declare idempotency [#bindings-and-declare-idempotency]

* **Declare idempotency:** identical args → ok; **any** field mismatch (type / durable /
  auto-delete / internal / deep-equal arguments) → `406`.
* **Passive declare** (`passive=true`): exists → ok; missing → `404 not-found`.

## The routing result [#the-routing-result]

1. The exchange matcher produces a set of target queues.
2. The set is **deduplicated** — a message matching multiple bindings to the **same** queue is
   delivered exactly **once**.
3. A per-queue Casbin **Write** check runs; denied queues are silently removed +
   `amqp.publish.denied` audit — see [Authentication](/connectors/rabbitmq/how-to/authentication).
4. If the routed set is **empty**:
   * `mandatory=true` → `basic.return(312 NO_ROUTE)` with the full message content;
   * otherwise the message is **silently dropped**.

See [Reliability](/connectors/rabbitmq/how-to/reliability) for `mandatory` / `basic.return`.

## Unsupported / inert routing features [#unsupported--inert-routing-features]

| Feature                                                                 | Behavior                                                                 |
| ----------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| **Exchange-to-exchange bindings** (`exchange.bind` / `exchange.unbind`) | `540 not-implemented` (capability advertised `false`).                   |
| **`alternate-exchange` argument**                                       | Accepted and stored, but **inert** — WARN once; badged in the dashboard. |

<Callout type="warn">
  **Inert arguments.** Several exchange/queue arguments are accepted (and badged in the dashboard
  topology view) but **never alter behavior**: `alternate-exchange`, priority queues,
  `x-max-length` / overflow, `x-queue-type` / mode, single-active-consumer, `x-expires`, and consumer
  `x-priority`. Don't rely on them. See
  [Capabilities](/connectors/rabbitmq/reference/capabilities).
</Callout>

## Error quick reference [#error-quick-reference]

| Trigger                                                               | Code        |
| --------------------------------------------------------------------- | ----------- |
| Unmatched direct routing key, no `mandatory`                          | silent drop |
| `mandatory=true` and unroutable                                       | `312`       |
| Redeclare with mismatched args                                        | `406`       |
| Passive declare of a missing exchange                                 | `404`       |
| Client declares an `amq.*` exchange / deletes a pre-declared exchange | `403`       |
| `exchange.bind` / `exchange.unbind`                                   | `540`       |

## Related [#related]

<Cards>
  <Card title="Queues and consumers" href="/connectors/rabbitmq/how-to/queues-and-consumers" description="Declaring queues, consuming, ack/nack, and prefetch on the amqp.{vhost}.{queue} channels that routing resolves to." />

  <Card title="Reliability" href="/connectors/rabbitmq/how-to/reliability" description="Publisher confirms, mandatory/return, dead-letter exchanges, and the publish-then-close footgun." />

  <Card title="Channel mapping" href="/connectors/rabbitmq/reference/channel-mapping" description="The amqp.{vhost}.{queue} naming grammar and how vhosts, queues, and routing keys map to KubeMQ channels." />
</Cards>
