KubeMQ
ConnectorsMQTTReference

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 / filterKubeMQ patternDirectionExample topicKubeMQ channel
events/<ch>Eventspublish + subscribeevents/site1/tempsite1.temp
store/<ch>Events-Storepublish + subscribe (start-new-only)store/site1/tempsite1.temp
queues/<ch>Queues — produce onlypublishqueues/jobs/emailjobs.email
$share/<group>/queues/<ch>Queues — consumesubscribe (QoS ≥ 1)$share/g1/queues/jobs/emailjobs.email
commands/<ch>Commands (RPC send)publish — MQTT 5.0 onlycommands/svc/rebootsvc.reboot
queries/<ch>Queries (RPC send)publish — MQTT 5.0 onlyqueries/svc/statussvc.status
$reply/<clientID>/<suffix>local (not routed)subscribe + RPC ResponseTopic$reply/c1/inboxnot routed
(no prefix)DefaultPatternpublish + subscribesite1/tempsite1.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.email

The 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 result

These 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 wildcardKubeMQ equivalentPosition rule
+* (single-level)any segment
#> (multi-level)final segment only

Examples:

MQTT subscribe filterKubeMQ channel filterMatches
events/+/temp*.tempevents/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:

ConditionResult
$share/<group>/queues/<ch> at QoS ≥ 1SUBACK 0x00 / 0x01 (granted)
$share/<group>/queues/<ch> at QoS 0SUBACK 0x83 (impl-specific error)
Plain queues/<ch> subscribe (no $share)SUBACK 0x83
$share/<group> on any non-queues prefixSUBACK 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 RPC

Gotcha — 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 ResponseTopic property carries the reply address.
  • The CorrelationData property 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 ResponseTopic property of the RPC PUBLISH must point to the client's own $reply/<clientID>/... namespace. Using another client's $reply namespace returns PUBACK 0x83.
  • $reply topics 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 topicErrorWire code
/events/fooempty segment (leading /)PUBACK 0x90 / SUBACK 0x8F
events//fooempty segment (double /)PUBACK 0x90 / SUBACK 0x8F
events/foo/empty segment (trailing /)PUBACK 0x90 / SUBACK 0x8F
events/ (prefix only, no channel)empty channelPUBACK 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/email

For 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 → rejected

Was this page helpful?

On this page