Topic Grammar
How the KubeMQ MQTT connector maps every topic and filter to a pattern and channel — prefixes, slash-to-dot, wildcards, shared subscriptions, and RPC replies.
The KubeMQ MQTT connector maps every MQTT topic (on publish) and filter (on subscribe) to one of five KubeMQ messaging patterns. The first segment of the topic string is the pattern prefix; the remaining segments become the KubeMQ channel.
The Ruby client ships the MQTT v3.1.1 subset. The mqtt gem speaks MQTT 3.1.1 only, so
it cannot use RPC (commands/, queries/) or $share queue consumption — both require
MQTT 5.0. Use Ruby for Events, Events-Store, and queue produce only.
Pattern prefix table
| MQTT topic / filter | KubeMQ pattern | Direction | Example topic | KubeMQ channel |
|---|---|---|---|---|
events/<ch> | Events | publish + subscribe | events/site1/temp | site1.temp |
store/<ch> | Events-Store | publish + subscribe (start-new-only) | store/site1/temp | site1.temp |
queues/<ch> | Queues — produce only | publish | queues/jobs/email | jobs.email |
$share/<group>/queues/<ch> | Queues — consume | subscribe (QoS ≥ 1) | $share/g1/queues/jobs/email | jobs.email |
commands/<ch> | Commands (RPC send) | publish — MQTT 5.0 only | commands/svc/reboot | svc.reboot |
queries/<ch> | Queries (RPC send) | publish — MQTT 5.0 only | queries/svc/status | svc.status |
$reply/<clientID>/<suffix> | local (not routed) | subscribe + RPC ResponseTopic | $reply/c1/inbox | not routed |
| (no prefix) | DefaultPattern | publish + subscribe | site1/temp | site1.temp |
A prefixless topic is routed to DefaultPattern (events by default). When
DefaultPattern=none the connector rejects the message: PUBACK 0x90 on publish, SUBACK
0x8F on subscribe.
Path-separator mapping: / becomes .
Topic segments are joined with . to form the KubeMQ channel:
events/site1/factory/temp → channel: site1.factory.temp
store/orders/europe → channel: orders.europe
queues/jobs/email → channel: jobs.emailThe connector splits the non-prefix segments on / and joins them with ..
Gotcha — literal . in a segment conflates with /
A literal . inside a segment passes through unchanged, making the channel
indistinguishable from one produced by an extra /:
events/a.b/c → channel: a.b.c
events/a/b/c → channel: a.b.c ← same resultThese two topics map to identical KubeMQ channels. This is a lossy round-trip: the
reverse mapping converts . back to /, so the injected delivery topic is events/a/b/c
regardless of which form was published. Avoid dots inside individual path segments to
prevent ambiguity.
Wildcard subscriptions
MQTT wildcards are translated to KubeMQ wildcard syntax only on Events subscriptions.
| MQTT wildcard | KubeMQ equivalent | Position rule |
|---|---|---|
+ | * (single-level) | any segment |
# | > (multi-level) | final segment only |
Examples:
| MQTT subscribe filter | KubeMQ channel filter | Matches |
|---|---|---|
events/+/temp | *.temp | events/site1/temp, events/site2/temp |
events/site1/# | site1.> | events/site1/temp, events/site1/a/b |
events/# | > | all events channels |
Non-Events wildcards are rejected: subscribing to queues/+/foo or store/# returns
SUBACK 0xA2 (wildcard-subscriptions-not-supported).
Gotcha — overlapping wildcard filters deliver multiple copies
Each distinct subscribe filter creates an independent bridge registry entry (one
subscription per (pattern, filter) pair). A publish that matches N overlapping filters
fires N deliveries — one per matching entry. There is no cross-entry deduplication
in the connector.
Example: a client subscribes to both events/# and events/site1/#; a publish to
events/site1/temp matches both entries and the client receives two copies.
Shared subscriptions — queue consume
Queue consumption uses MQTT 5.0 shared subscriptions:
$share/<group>/queues/<channel>Rules:
| Condition | Result |
|---|---|
$share/<group>/queues/<ch> at QoS ≥ 1 | SUBACK 0x00 / 0x01 (granted) |
$share/<group>/queues/<ch> at QoS 0 | SUBACK 0x83 (impl-specific error) |
Plain queues/<ch> subscribe (no $share) | SUBACK 0x83 |
$share/<group> on any non-queues prefix | SUBACK 0x83 |
Empty group: $share//queues/<ch> | SUBACK 0x8F (invalid) |
QoS 2 is downgraded to QoS 1: the queue bridge operates at QoS 1 (PUBACK = acknowledge; no QoS 2 two-phase commit in the bridge).
Gotcha — the $share group name is audit-only; all groups compete in one pool
The KubeMQ queue is a single shared pool. Unlike standard MQTT brokers — which deliver a
separate copy per $share group — the KubeMQ MQTT connector routes all $share group
subscribers into one competing-consumer pool. Every message is consumed exactly once across
all subscribers, regardless of group name. The group name is recorded in audit and metrics
only.
RPC topics — MQTT 5.0 only
Commands and Queries are publish-only patterns over MQTT:
commands/<channel> # send a command RPC
queries/<channel> # send a query RPCGotcha — RPC requires MQTT 5.0; a v3.1.1 publish is silently dropped
A v3.1.1 (or earlier) client publishing to commands/ or queries/ receives a success
PUBACK (0x00), but the message is silently dropped and a publish.error audit event
is emitted. No RPC is issued.
MQTT 5.0 is required because:
- The
ResponseTopicproperty carries the reply address. - The
CorrelationDataproperty associates the response. - User Properties carry RPC metadata.
RPC subscribe is not allowed for MQTT clients
MQTT clients cannot subscribe to commands/<ch> or queries/<ch> as responders. These
subscriptions return SUBACK 0x83. RPC responders must be implemented on the gRPC side
using the KubeMQ Go SDK or another KubeMQ client.
Reply namespace
$reply/<own-clientID>/<suffix>- A client must subscribe to its own
$reply/<clientID>/<suffix>before issuing an RPC publish. - The
ResponseTopicproperty of the RPC PUBLISH must point to the client's own$reply/<clientID>/...namespace. Using another client's$replynamespace returns PUBACK0x83. $replytopics are local — they are never routed to the broker.
Gotcha — PUBACK is sent immediately; the client must implement its own timeout
The PUBACK for an RPC PUBLISH is sent immediately on receipt, before the response
arrives. The response is later injected as a separate PUBLISH on the $reply topic. The
client must implement its own timeout to detect missing responses. RpcTimeoutSeconds
(default 30 s) is the server-side bound; after that the server audits rpc.timeout and
discards the pending entry, but the client receives no notification.
Empty segments and structural errors
The connector rejects topics with empty segments (leading slash, trailing slash, or double slash):
| Offending topic | Error | Wire code |
|---|---|---|
/events/foo | empty segment (leading /) | PUBACK 0x90 / SUBACK 0x8F |
events//foo | empty segment (double /) | PUBACK 0x90 / SUBACK 0x8F |
events/foo/ | empty segment (trailing /) | PUBACK 0x90 / SUBACK 0x8F |
events/ (prefix only, no channel) | empty channel | PUBACK 0x90 / SUBACK 0x8F |
Reverse mapping: delivery topics
When the connector injects a delivery back to subscribing MQTT clients it replaces every
. with / and prepends the pattern prefix:
channel: site1.factory.temp + pattern: events → events/site1/factory/temp
channel: jobs.email + pattern: queues → queues/jobs/emailFor wildcard subscriptions the injected topic is the concrete per-message channel (e.g.
events/site1/temp), not the wildcard filter (e.g. events/+/temp). For exact
subscriptions the injected topic is the original filter string.
Summary
MQTT publish/subscribe topic
│
├── starts with "events/" → Events
├── starts with "store/" → Events-Store
├── starts with "queues/" → Queues (produce)
├── starts with "commands/" → Commands (MQTT 5.0 only)
├── starts with "queries/" → Queries (MQTT 5.0 only)
├── starts with "$share/<g>/queues/"
│ → Queues (consume, QoS ≥ 1)
├── starts with "$reply/<own-clientID>/"
│ → local (no broker routing)
└── no prefix → DefaultPattern (default: events)
DefaultPattern=none → rejectedRelated
Was this page helpful?
Reason Codes
Every MQTT 5.0 reason code the KubeMQ MQTT connector produces — the packet it appears on, what it means, the exact triggers, and how MQTT 3.1.1 behaves instead.
Migrating from MQTT
Move an MQTT 3.1.1 / 5.0 app to KubeMQ by swapping the broker host — what carries over, topic-prefix mapping onto KubeMQ patterns, and what does not migrate.