Destination Grammar
How a STOMP destination maps to a KubeMQ (pattern, channel) pair — primary prefixes and aliases, slash-to-dot joining, wildcards, and header-to-tag tables.
The single most important mental model for the KubeMQ STOMP connector: a STOMP destination is
parsed into a (pattern, channel) pair, where the first path segment selects the KubeMQ
messaging pattern and the remaining segments are .-joined into the KubeMQ channel name.
Everything else on this page follows from that one rule.
This is the formal reference. For a task-oriented walkthrough see Destination Mapping.
The primary destination prefixes (lead with these)
A STOMP destination begins with one of six primary prefixes. These are the names you should write in application code:
| STOMP destination | Pattern | KubeMQ channel | Delivery model |
|---|---|---|---|
/queue/orders/new | Queues | orders.new | at-least-once, competing consumers |
/topic/a/b/c | Events | a.b.c | at-most-once, fan-out |
/topic-store/audit | Events-Store | audit | persistent, replayable |
/command/exec | Commands (RPC) | exec | request / reply |
/query/lookup | Queries (RPC) | lookup | request / reply |
/reply/r1 | reply (connection-local) | r1 | RPC reply sink |
The prefix map is case-sensitive. The /reply/ prefix is special — it is a connection-local
RPC reply sink, never routed to the KubeMQ array (see
Commands).
ActiveMQ / MQTT-style aliases (accepted, but do not lead with them)
Each pattern also accepts a single MQTT-style alias prefix. The aliases are accepted on
ingress for compatibility, but egress always canonicalizes to the primary name — a subscriber
that subscribed via /events/x receives MESSAGE frames stamped destination:/topic/x. Prefer
the primary names everywhere.
| Primary | Alias |
|---|---|
/queue/ | /queues/ |
/topic/ | /events/ |
/topic-store/ | /store/ |
/command/ | /commands/ |
/query/ | /queries/ |
There is no /topic_store/, /eventstore/, /exchange/, or /amq/queue/ prefix.
The parse algorithm (formal grammar)
destination = [ "/" ] prefix-or-segment *( "/" segment )
prefix = "queue" / "queues" / "topic" / "events"
/ "topic-store" / "store" / "command" / "commands"
/ "query" / "queries" / "reply"
segment = 1*( %x00-2E / %x30-FF ) ; any non-"/" bytes; non-emptyThe connector resolves a destination as follows:
- Length gate first. The raw destination string is rejected if it exceeds 512 bytes — checked on the raw string, before the leading slash is stripped.
- Strip exactly one leading
/. Both/queue/xandqueue/xresolve identically; only the first slash is removed. - Split on
/. Any empty segment (a//or a trailing/) is rejected. - First segment selects the pattern via the case-sensitive prefix map. A known prefix
consumes the first segment; an unknown first segment falls through to the configured
DefaultPattern(see below). - Remaining segments are
.-joined into the KubeMQ channel:/topic/a/b/c→ channela.b.c.
A known prefix with no trailing channel segment is rejected: /queue → empty channel;
/queue/ → empty segment.
Slash → dot is the channel join
This is the core transformation. Every / after the prefix becomes a . in the KubeMQ channel
name. The same channel is reachable from gRPC, MQTT, AMQP, and REST under its dotted name.
| STOMP destination | KubeMQ channel |
|---|---|
/queue/jobs | jobs |
/queue/jobs/email | jobs.email |
/topic/orders/eu/west | orders.eu.west |
A literal . inside a destination segment is lossy. /topic/a.b and /topic/a/b both
map to the same KubeMQ channel a.b, and egress always emits the slash form /topic/a/b. A
destination that contains literal dots is therefore not round-trip safe. Prefer slashes;
avoid literal dots in destination segments.
Wildcard subscriptions
Wildcards use the message broker's native wildcard syntax and are passed through
untranslated — there is no MQTT-style + / #. They are subject to three hard constraints:
- SUBSCRIBE-only — a wildcard in a SEND destination (any pattern) is rejected as
invalid destination. - Events-only — wildcards are allowed only on the Events pattern (
/topic/,/events/). Queues, Events-Store, Commands, Queries, and reply destinations reject them. - Two tokens, with the broker's native semantics — not the MQTT
+/#:
| Token | Meaning | Position rule |
|---|---|---|
* | matches exactly one segment | any position |
> | matches one or more trailing segments | must be the final segment |
Slash → dot applies to the matched filter too: /topic/a/*/c → channel a.*.c; /topic/orders/>
→ channel orders.>.
Egress delivers the CONCRETE matched channel, not the filter. A subscriber on
/topic/orders/* that receives a message published to orders.eu gets a MESSAGE frame stamped
destination:/topic/orders/eu — the concrete channel, never /topic/orders/*. Always read the
destination header to learn what actually matched.
Pattern routing (where each SEND goes)
Once a destination resolves to a (pattern, channel) pair, the SEND routes to the matching KubeMQ
array call:
| Pattern | Array call | Notes |
|---|---|---|
| Queues | array.SendQueueMessage | at-least-once |
| Events | array.SendEvents | at-most-once |
| Events-Store | array.SendEventsStore (Store=true) | persisted |
| Commands | array.SendCommand | RPC; reply on /reply/ |
| Queries | array.SendQuery | RPC; reply on /reply/ |
| reply | (none) | connection-local; never routed |
A SEND to a /reply/ destination is rejected: invalid destination / cannot SEND to reply destinations.
DefaultPattern — bare (prefixless) destinations
A destination whose first segment is not a known prefix falls through to the connector-wide
DefaultPattern:
CONNECTORS_STOMP_DEFAULT_PATTERN | Bare destination resolves to |
|---|---|
events (default) | Events; sensor/temp → channel sensor.temp |
queues | Queues |
store | Events-Store |
none | rejected — bare destinations require an explicit prefix (invalid destination) |
There is no commands or queries default — a bare destination can never resolve to an RPC
pattern.
Examples and applications should always use explicit prefixes. Relying on DefaultPattern
couples your code to the connector's configuration; an operator who sets DefaultPattern=none
would break every bare destination.
Header ⇄ tag reference tables
STOMP headers map onto KubeMQ message Tags. There are three classes of header; the tables below are the authoritative reference (see Destination Mapping for the prose explanation).
Standard headers ↔ reserved stomp.* tags (round-trip both directions)
Five standard STOMP headers round-trip through reserved stomp.* tags:
| STOMP header (ingress & egress) | KubeMQ tag key |
|---|---|
content-type | stomp.content-type |
correlation-id | stomp.correlation-id |
reply-to | stomp.reply-to |
priority | stomp.priority |
type | stomp.type |
- Collision rule:
stomp.*WINS over a same-named bare custom tag on egress; exactly one header results. - Custom headers pass through name-as-is as tags (first-wins on duplicate). Limits: ≤32
custom tags, ≤4096 bytes per value — a SEND that exceeds either is a fatal
frame too large+ close.
No content-type frame default. The frame codec never defaults content-type. A native
(gRPC / REST / MQTT / AMQP) producer that sets no stomp.content-type (or plain
content-type) tag yields a MESSAGE with no content-type header — the STOMP subscriber must
assume binary / octet-stream. content-length is always present, so the body is still framed
correctly.
Machinery headers — never become tags
These frame-machinery headers are stripped on ingress and never forwarded as KubeMQ tags:
destination receipt transaction content-length
message-id subscription ack id timeoutProtected-on-ingress headers (spoofing guard)
Inbound header names starting with stomp. or equal to x-kubemq-metadata are silently
stripped on ingress. A client cannot inject stomp.* tags directly — only through the canonical
standard headers above.
| Inbound header name | Action |
|---|---|
stomp.* (any) | stripped (logged at debug) |
x-kubemq-metadata | stripped (logged at debug) |
x-kubemq-metadata is egress-only
STOMP ingress never sets the KubeMQ Metadata field — STOMP has no canonical envelope (unlike
AMQP). On egress only, a non-empty native Metadata value surfaces as the x-kubemq-metadata
header, prepended at the highest emit priority.
Egress representability guard
On delivery, a header whose name / value cannot be serialized on the negotiated protocol
version is dropped (the connection stays alive; metric deliver_headers / dropped):
| Version | Drops a header when… |
|---|---|
| 1.0 | name contains :, CR, or LF; or value contains CR or LF |
| 1.1 | name or value contains CR (1.1 has no CR escape) |
| 1.2 | never — always representable |
CR/LF in a header value is silently dropped to 1.0 / 1.1 subscribers. Use accept-version:1.2
and keep CR/LF out of header values; put structured / multi-line metadata in the body.
Destination errors (sanitized wire vocabulary)
All destination-class errors are sanitized before crossing the wire — internal error text never reaches the client. See Error Frames for the full ERROR vocabulary.
| Trigger | Wire message |
|---|---|
| raw destination > 512 bytes | invalid destination |
//, trailing /, or /queue/ | invalid destination |
known prefix, no channel (/queue) | invalid destination |
bare destination with DefaultPattern=none | invalid destination |
| wildcard on SEND, or on queues / store / RPC | invalid destination |
| >64 headers / >8 KiB block / >32 tags / >4 KiB value | frame too large |
Related
Was this page helpful?
Connections & Observability
The STOMP connector's observability surface — the management API, the three Prometheus series, the web dashboard, and the audit taxonomy for verifying STOMP.
Error Frames
The complete ERROR-frame vocabulary the KubeMQ STOMP connector emits — handshake, codec, dispatch, SEND, SUBSCRIBE, and RPC errors, plus receipt rules.