# Destination Grammar (/connectors/stomp/reference/destination-grammar)



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](/connectors/stomp/how-to/destination-mapping).

## The primary destination prefixes (lead with these) [#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](/connectors/stomp/how-to/commands)).

### ActiveMQ / MQTT-style aliases (accepted, but do not lead with them) [#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) [#the-parse-algorithm-formal-grammar]

```text
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-empty
```

The connector resolves a destination as follows:

1. **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.
2. **Strip exactly one leading `/`.** Both `/queue/x` and `queue/x` resolve identically; only the
   first slash is removed.
3. **Split on `/`.** Any empty segment (a `//` or a trailing `/`) is rejected.
4. **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).
5. **Remaining segments are `.`-joined** into the KubeMQ channel: `/topic/a/b/c` → channel
   `a.b.c`.

A known prefix with **no** trailing channel segment is rejected: `/queue` → empty channel;
`/queue/` → empty segment.

### Slash → dot is the channel join [#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` |

<Callout type="warn">
  **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**. &#x2A;*Prefer slashes;
  avoid literal dots in destination segments.**
</Callout>

## Wildcard subscriptions [#wildcard-subscriptions]

Wildcards use **the message broker's native wildcard syntax** and are passed through
untranslated — &#x2A;*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.>`.

<Callout type="warn">
  **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.
</Callout>

## Pattern routing (where each SEND goes) [#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 [#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.

<Callout type="info">
  **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.
</Callout>

## Header ⇄ tag reference tables [#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](/connectors/stomp/how-to/destination-mapping) for the prose
explanation).

### Standard headers ↔ reserved `stomp.*` tags (round-trip both directions) [#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*&#x2A; pass through name-as-is as tags (first-wins on duplicate). Limits: **≤32*&#x2A;
  custom tags, **≤4096 bytes** per value — a SEND that exceeds either is a **fatal** `frame too
  large` + close.

<Callout type="warn">
  **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.
</Callout>

### Machinery headers — never become tags [#machinery-headers--never-become-tags]

These frame-machinery headers are stripped on ingress and never forwarded as KubeMQ tags:

```text
destination   receipt   transaction   content-length
message-id    subscription   ack   id   timeout
```

### Protected-on-ingress headers (spoofing guard) [#protected-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 [#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 [#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                             |

<Callout type="warn">
  **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**.
</Callout>

## Destination errors (sanitized wire vocabulary) [#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](/connectors/stomp/reference/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 [#related]

<Cards>
  <Card title="Destination Mapping" href="/connectors/stomp/how-to/destination-mapping" description="The prose walkthrough of the grammar and the header ⇄ tag convention." />

  <Card title="Capabilities" href="/connectors/stomp/reference/capabilities" description="The supported-command and limits reference." />

  <Card title="Error Frames" href="/connectors/stomp/reference/error-frames" description="The full ERROR-frame vocabulary." />

  <Card title="Events" href="/connectors/stomp/how-to/events" description="Fan-out pub/sub and the events-only wildcard subscriptions." />
</Cards>
