# Topic mapping (/connectors/mqtt/concepts/topic-mapping)



The KubeMQ MQTT connector maps every MQTT topic to a KubeMQ messaging pattern and channel through a
well-defined grammar. The &#x2A;*first topic segment (the prefix)** selects the KubeMQ pattern; the
**remaining segments** become the channel name with `/` replaced by `.`. Understanding this grammar
is essential before writing any producer or consumer code.

## Prefix-to-pattern table [#prefix-to-pattern-table]

| MQTT topic                   | KubeMQ pattern | Direction                          | Example topic                 | KubeMQ channel       |
| ---------------------------- | -------------- | ---------------------------------- | ----------------------------- | -------------------- |
| `events/<ch>`                | Events         | publish + subscribe                | `events/site1/temp`           | `site1.temp`         |
| `store/<ch>`                 | Events-Store   | publish + subscribe (StartNewOnly) | `store/site1/temp`            | `site1.temp`         |
| `queues/<ch>`                | Queues         | publish = produce only             | `queues/jobs/email`           | `jobs.email`         |
| `$share/<group>/queues/<ch>` | Queues         | subscribe = consume (QoS ≥ 1)      | `$share/g1/queues/jobs/email` | `jobs.email`         |
| `commands/<ch>`              | Commands (RPC) | publish = send (MQTT 5.0 only)     | `commands/svc/reboot`         | `svc.reboot`         |
| `queries/<ch>`               | Queries (RPC)  | publish = send (MQTT 5.0 only)     | `queries/svc/status`          | `svc.status`         |
| `$reply/<clientID>/<suffix>` | (broker-local) | subscribe + RPC ResponseTopic      | `$reply/c1/inbox`             | not routed to KubeMQ |

## Separator conversion: `/` → `.` [#separator-conversion---]

MQTT uses `/` as the path separator; KubeMQ uses `.`. The connector converts every `/` in the
**channel portion** of the topic to `.`:

```text
events/site1/sensors/temp
       ↓ strip the prefix
       site1/sensors/temp
       ↓ replace / with .
       site1.sensors.temp        ← KubeMQ channel
```

This conversion is one-way on delivery: when the connector delivers a KubeMQ message to an MQTT
subscriber, the channel's `.` separators are **not** converted back. The MQTT subscribe filter must
match the dotted form.

<Callout type="warn">
  **A literal `.` in a topic segment is lossy.** A `.` inside a segment maps to `.` in the channel —
  **indistinguishable** from a `/`-converted `.`. So `events/a/b/c`, `events/a.b/c`, and
  `events/a/b.c` all resolve to the same channel `a.b.c`. Use `/` exclusively for hierarchy in MQTT
  topics; reserve `.` for the channel names used on gRPC/REST clients.
</Callout>

## Prefixless topics and `DefaultPattern` [#prefixless-topics-and-defaultpattern]

A topic with no recognized prefix (e.g. `sensor/data`) is routed to the configured
`DefaultPattern`:

| `DefaultPattern`   | Behaviour                                                            |
| ------------------ | -------------------------------------------------------------------- |
| `events` (default) | Routed to **Events** on channel `sensor.data`                        |
| `store`            | Routed to **Events-Store**                                           |
| `none`             | Publish rejected — PUBACK `0x90`; subscribe rejected — SUBACK `0x8F` |

Configure with `CONNECTORSMQTT_DEFAULT_PATTERN` (default `events`).

## Wildcard subscriptions — Events only [#wildcard-subscriptions--events-only]

MQTT wildcards are supported on **Events subscriptions only**. A wildcard subscribe on any other
pattern is rejected with SUBACK `0xA2` (wildcard-subscriptions-not-supported).

| MQTT wildcard      | KubeMQ equivalent | Example filter   | Matches                                   |
| ------------------ | ----------------- | ---------------- | ----------------------------------------- |
| `+` (single level) | `*`               | `events/site1/+` | `events/site1/temp`, `events/site1/hum`   |
| `#` (multi level)  | `>`               | `events/site1/#` | `events/site1/temp`, `events/site1/a/b/c` |

Rules:

* `#` must be the **final segment** of the topic filter.
* Both wildcards may appear in one filter: `events/+/sensors/#`.
* A non-Events wildcard subscribe returns SUBACK `0xA2`.

<Callout type="warn">
  **Overlapping wildcard filters deliver multiple copies.** Each distinct Events subscribe filter
  creates an independent bridge entry, and there is **no cross-entry deduplication**. If a publish
  matches N of a client's overlapping filters (e.g. `#`, `events/#`, and the exact topic), the
  client receives **N copies**. Use non-overlapping filters when duplicate delivery is unacceptable.
</Callout>

## Queue consume: `$share` shared subscriptions [#queue-consume-share-shared-subscriptions]

A plain queue subscribe (`queues/<ch>`) is **not allowed** — it returns SUBACK `0x83`. Queue
consumption requires an MQTT 5.0 shared subscription:

```text
$share/<group>/queues/<channel>
```

| Component          | Meaning                                                             |
| ------------------ | ------------------------------------------------------------------- |
| `$share`           | MQTT 5.0 shared-subscription prefix                                 |
| `<group>`          | Group name — an **audit/metrics label only** (see the caveat below) |
| `queues/<channel>` | Must resolve to the **Queues** pattern                              |

QoS must be **≥ 1** — a QoS 0 shared-queue subscribe returns SUBACK `0x83`.

<Callout type="warn">
  **The `$share` group name is audit/metrics-only.** All groups compete in **one** shared KubeMQ
  queue pool — this is **not** MQTT per-group-copy semantics. `$share/A/queues/x` and
  `$share/B/queues/x` consume from the **same** pool (one copy total, any consumer wins), not one
  copy per group. For true fan-out to independent consumer groups, use **different KubeMQ channels**.
</Callout>

## RPC reply topics: `$reply/<clientID>/<suffix>` [#rpc-reply-topics-replyclientidsuffix]

`$reply` is a **broker-local reserved namespace** for RPC response routing. It is **never** routed
to KubeMQ — it stays inside the broker.

| Rule              | Details                                                                                          |
| ----------------- | ------------------------------------------------------------------------------------------------ |
| Format            | `$reply/<clientID>/<suffix>` — **3 segments minimum**                                            |
| Own namespace     | A client may only subscribe to / use as ResponseTopic its **own** `$reply/<own-clientID>/...`    |
| Foreign namespace | Using another client's `$reply` namespace → PUBACK `0x83` (publish with a foreign ResponseTopic) |
| Routing           | Never forwarded to KubeMQ                                                                        |

An RPC client subscribes to its own reply topic **before** publishing the request, then sets it as
`Properties.ResponseTopic` on the publish. See [Commands](/connectors/mqtt/how-to/commands)
and [Queries](/connectors/mqtt/how-to/queries) for the full flow.

## MQTT 5.0 User Properties ↔ KubeMQ Tags [#mqtt-50-user-properties--kubemq-tags]

On **MQTT 5.0** connections, User Properties on a PUBLISH are carried into the KubeMQ message
`Tags`, and vice versa on delivery. This mapping is **5.0 only** — MQTT 3.1.1 has no User
Properties field.

* **Inbound** — every `Properties.User` entry on a 5.0 PUBLISH is copied 1:1 into the KubeMQ
  message `Tags` (duplicate keys are last-wins). Applies to Events, Events-Store, Queues publishes,
  and the RPC bridge.
* **Outbound** — when a KubeMQ message is delivered to a 5.0 MQTT subscriber, its `Tags` are
  written back as `Properties.User`. A 3.1.1 subscriber receives no user properties.

Caps (per message):

| Cap                                 | Limit | Exceeded on 5.0                 | Exceeded on 3.1.1             |
| ----------------------------------- | ----- | ------------------------------- | ----------------------------- |
| Max user-property count             | 32    | PUBACK `0x97` + `publish.error` | Silent drop + `publish.error` |
| Max total bytes (all keys + values) | 4096  | PUBACK `0x97` + `publish.error` | Silent drop + `publish.error` |

When either cap is exceeded the message is **not** routed.

## Reason codes for invalid topics [#reason-codes-for-invalid-topics]

| Trigger                                     | Packet | Code                                        |
| ------------------------------------------- | ------ | ------------------------------------------- |
| `DefaultPattern=none`, prefixless publish   | PUBACK | `0x90` topic-name-invalid                   |
| `DefaultPattern=none`, prefixless subscribe | SUBACK | `0x8F` topic-filter-invalid                 |
| Empty channel segment                       | SUBACK | `0x8F` topic-filter-invalid                 |
| Non-Events wildcard subscribe               | SUBACK | `0xA2` wildcard-subscriptions-not-supported |
| Plain `queues/<ch>` subscribe               | SUBACK | `0x83` implementation-specific              |
| QoS 0 `$share/*/queues/<ch>` subscribe      | SUBACK | `0x83` implementation-specific              |
| Foreign `$reply` namespace (publish)        | PUBACK | `0x83` implementation-specific              |

## Related [#related]

<Cards>
  <Card title="Topic grammar" href="/connectors/mqtt/reference/topic-grammar" description="The formal grammar reference — prefix tokens, segment rules, and the full reason-code mapping." />

  <Card title="Protocol versions" href="/connectors/mqtt/concepts/protocol-versions" description="Why $share consume, RPC, and User Properties are MQTT 5.0 only." />

  <Card title="QoS and sessions" href="/connectors/mqtt/concepts/qos-and-sessions" description="The QoS each pattern requires — including QoS ≥ 1 for $share Queue consume." />
</Cards>
