# Architecture (/connectors/stomp/concepts/architecture)



The KubeMQ **STOMP connector** is an embedded STOMP server hosted inside kubemq-server. It has
its own dedicated TCP/TLS listeners (default plain TCP **61613**, TLS **61614**), a hand-rolled
frame codec (no third-party STOMP server library), per-version negotiation (1.0/1.1/1.2), and a
destination router that bridges the STOMP wire protocol onto KubeMQ's five patterns by
**destination prefix**. It is built and started by default (`CONNECTORS_STOMP_ENABLE=true`).

A STOMP frame travels from a native client library, through the embedded STOMP server, onto one
of KubeMQ's five native messaging patterns, and out to the message broker — and back. A STOMP
client never touches proto, never touches a KubeMQ SDK, and never knows what the internal
broker is; it speaks plain STOMP over TCP and the connector does the bridging.

## The protocol stack [#the-protocol-stack]

The connector is a thin, faithful STOMP endpoint in front of the KubeMQ **array**. Everything
below the array — the Events/Queues/Commands/Queries machinery and the message broker — is the
same engine every other KubeMQ transport (gRPC, REST, MQTT, AMQP) sits on top of.

<Mermaid
  chart="`
graph TB
CLIENT[&#x22;STOMP client library<br/>go-stomp · stomp.py · stompit · Spring · Stomp.Net · stomp gem · async-stomp&#x22;]
LISTENER[&#x22;TCP / TLS listener<br/>:61613 / :61614 (binds all interfaces)&#x22;]
CODEC[&#x22;Frame codec<br/>COMMAND + headers + body + NUL<br/>per-version escaping; binary-safe content-length&#x22;]
HANDSHAKE[&#x22;CONNECT / auth / heartbeat<br/>version negotiate → MaxConnections → auth → client id&#x22;]
HANDLERS[&#x22;Handlers<br/>SEND · SUBSCRIBE · UNSUBSCRIBE · ACK · NACK · RPC dispatch&#x22;]
ROUTER[&#x22;Destination router<br/>prefix selects pattern; remainder slash→dot = channel&#x22;]
BRIDGE[&#x22;Queue bridge / RPC bridge / subscription registry&#x22;]
ARRAY[&#x22;KubeMQ array&#x22;]
BROKER[&#x22;Message Broker&#x22;]

CLIENT -- &#x22;raw TCP / TLS&#x22; --> LISTENER
LISTENER --> CODEC
CODEC --> HANDSHAKE
HANDSHAKE --> HANDLERS
HANDLERS --> ROUTER
ROUTER --> BRIDGE
BRIDGE --> ARRAY
ARRAY --> BROKER

class CLIENT client
class LISTENER,CODEC,HANDSHAKE,HANDLERS,ROUTER,BRIDGE connector
class ARRAY,BROKER broker
`"
/>

*The same array path the gRPC connector uses; the destination prefix selects the pattern and the remaining segments (slash→dot) become the KubeMQ channel.*

The pieces, top to bottom:

* **TCP / TLS listener** — binds all interfaces on `61613` / `61614` via an availability-first
  loader: a bind failure logs an error and the server continues **without** STOMP rather than
  crashing.
* **Frame codec** — a hand-rolled codec for the STOMP frame shape (`COMMAND\n` + `name:value\n`
  lines + a blank line + the body + a `NUL` terminator). It applies per-version header escaping
  and is binary-safe via a content-length-aware reader; the writer always stamps
  `content-length` on egress.
* **CONNECT / auth / heartbeat** — the handshake: negotiate the protocol version, check the
  connection limit, authenticate, derive the client id, and set up the heartbeat watchdog,
  then reply with `CONNECTED`.
* **Handlers** — dispatch each client frame: `SEND`, `SUBSCRIBE`, `UNSUBSCRIBE`, `ACK`, `NACK`,
  and RPC dispatch.
* **Destination router** — the heart of the bridge: the first destination segment selects the
  pattern and the remaining segments are `.`-joined into the channel.
* **Bridges & registry** — the queue bridge, RPC bridge, and subscription registry sit between
  the handlers and the KubeMQ array.

## Version negotiation [#version-negotiation]

The connector supports STOMP **1.0, 1.1, and 1.2** and picks the **highest common** version
from the client's `CONNECT` `accept-version` header (comma-separated, order-independent,
whitespace-tolerant). A missing or empty header resolves to `1.0`; no common version returns an
`ERROR` frame and closes. The examples request `accept-version:1.2`, which is the recommended
default — only 1.2 can always represent every header on egress (1.0 and 1.1 silently drop
unrepresentable CR/LF in header values). The `CONNECTED` reply carries the negotiated `version`,
the server's advertised `heart-beat` (`sx,sy`), a derived `session` id, and `server`
\= `KubeMQ/<version>`.

The heartbeat is governed by `HeartbeatMs` (advertised `sx=sy`, default 10000 ms). The dead-peer
cutoff is **2× the negotiated client→server interval** — the client must send a frame (or a bare
newline) before that window elapses, or the watchdog force-closes the connection. Any inbound
byte refreshes liveness.

## Destination → pattern → channel mapping [#destination--pattern--channel-mapping]

This is the single most important mental model. The **first destination segment** selects a
KubeMQ pattern (case-sensitive); the **remaining segments are `.`-joined** into the KubeMQ
channel. ActiveMQ-style **primary** names are the canonical form; MQTT-style **aliases** map to
the same patterns. On egress the connector **always canonicalizes back to the primary name**.

<Mermaid
  chart="`
graph LR
DEST[&#x22;Destination<br/>/&lt;prefix&gt;/&lt;segments&gt;&#x22;]
Q[&#x22;/queue/&#x22;]
T[&#x22;/topic/&#x22;]
TS[&#x22;/topic-store/&#x22;]
C[&#x22;/command/&#x22;]
QY[&#x22;/query/&#x22;]
BROKER[&#x22;Message Broker&#x22;]

DEST --> Q
DEST --> T
DEST --> TS
DEST --> C
DEST --> QY

Q -- &#x22;Queues&#x22; --> BROKER
T -- &#x22;Events&#x22; --> BROKER
TS -- &#x22;Events-Store&#x22; --> BROKER
C -- &#x22;Commands (RPC)&#x22; --> BROKER
QY -- &#x22;Queries (RPC)&#x22; --> BROKER

class DEST client
class Q,T,TS,C,QY connector
class BROKER broker
`"
/>

*The destination prefix selects the KubeMQ pattern; the remaining segments (slash→dot) become the channel.*

| STOMP destination (ingress) | Alias        | Pattern                       | KubeMQ channel | Canonical egress (always primary) |
| --------------------------- | ------------ | ----------------------------- | -------------- | --------------------------------- |
| `/queue/orders/new`         | `/queues/`   | Queues                        | `orders.new`   | `/queue/orders/new`               |
| `/topic/a/b/c`              | `/events/`   | Events                        | `a.b.c`        | `/topic/a/b/c`                    |
| `/topic-store/audit`        | `/store/`    | Events-Store                  | `audit`        | `/topic-store/audit`              |
| `/command/exec`             | `/commands/` | Commands (RPC)                | `exec`         | `/command/exec`                   |
| `/query/lookup`             | `/queries/`  | Queries (RPC)                 | `lookup`       | `/query/lookup`                   |
| `/reply/r1`                 | *(none)*     | reply (connection-local)      | `r1`           | `/reply/r1`                       |
| `sensor/temp` (bare)        | —            | events (via `DefaultPattern`) | `sensor.temp`  | `/topic/sensor/temp`              |

The parse algorithm: the 512-byte length limit is checked on the **raw** destination string
first; strip **exactly one** leading `/`; split on `/` (any empty segment is rejected); the first
segment selects the pattern via a **case-sensitive** map; the remaining segments are `.`-joined
into the channel; an unknown first segment falls back to `DefaultPattern` (default `events`).
There is no `/topic_store/` or `/eventstore/` — the Events-Store prefix is `/topic-store/`
(primary) / `/store/` (alias).

<Callout type="warn">
  **A literal `.` in a destination segment is lossy.** `/topic/a.b` and `/topic/a/b` **both** map
  to channel `a.b`, and egress always emits the **slash** form `/topic/a/b`, so `/topic/a.b` is
  **not** round-trip safe. Prefer slashes; avoid literal dots in segments. Wildcard subscriptions
  are **Events-only**, **subscribe-only**, and use the message broker's native wildcard syntax
  (`*` = one segment, `>` = the final tail) — &#x2A;*there is no MQTT-style `+`/`#`**. A wildcard on
  `SEND`, or on queues / events-store / RPC, is rejected with `invalid destination`. See
  [Destination grammar](/connectors/stomp/reference/destination-grammar).
</Callout>

## The three-step RPC flow (Commands & Queries) [#the-three-step-rpc-flow-commands--queries]

`/command/` and `/query/` are request/reply, and STOMP is **requester-only*&#x2A; — the responder
lives on the KubeMQ (gRPC) side. A STOMP **`SUBSCRIBE` to `/command` or `/query` is rejected**
(`cannot subscribe to RPC destinations` + close). The flow is:

1. **`SUBSCRIBE` to `/reply/<name>` first** — connection-local: no array, no authz, no
   ack tracking.
2. **`SEND` to `/command/<svc>` or `/query/<svc>`** with a **required** `reply-to:` (an active
   `/reply/` subscription on the **same** connection), an optional `correlation-id:` (echoed
   only when set), and an optional `timeout:` in **milliseconds** (effective
   `min(timeout, RpcTimeoutSeconds * 1000)`, default 30000).
3. The reply arrives as a **`MESSAGE` on the `/reply/` subscription**.

<Callout type="info">
  A timeout, logical error, or dropped reply arrives as a **`MESSAGE` carrying a `stomp-error`
  header** — **not** an `ERROR` frame — and the connection stays open. Only a `reply-to` violation
  or pending-cap overflow closes the connection. The body shape differs by failure kind: a
  *logical* error carries the responder's body and tags alongside `stomp-error`; only a transport
  error/timeout or a nil response is empty-bodied. See
  [Commands](/connectors/stomp/how-to/commands) and
  [Error frames](/connectors/stomp/reference/error-frames).
</Callout>

## Cross-protocol interop [#cross-protocol-interop]

Because the connector bridges onto the shared KubeMQ array, a message published from STOMP is
readable by any other KubeMQ transport on the **same channel**, and vice-versa — via the shared
KubeMQ array, the same path gRPC uses. A STOMP `SEND` to `/topic/it/cross` (channel `it.cross`)
is received by a native gRPC `SubscribeEvents` consumer on channel `it.cross`, and a native gRPC
publish to `it.cross` is delivered to a STOMP subscriber on `/topic/it/cross`.

<Mermaid
  chart="`
graph LR
STOMP[&#x22;STOMP client<br/>/topic/it/cross&#x22;]
GRPCCLIENT[&#x22;gRPC / REST client<br/>channel: it.cross&#x22;]
BROKER[&#x22;Message Broker&#x22;]

STOMP -- &#x22;produce&#x22; --> BROKER
BROKER -- &#x22;consume&#x22; --> GRPCCLIENT
GRPCCLIENT -- &#x22;produce&#x22; --> BROKER
BROKER -- &#x22;consume&#x22; --> STOMP

class STOMP,GRPCCLIENT client
class BROKER broker
`"
/>

*The same KubeMQ channel backs both transports, so a STOMP client and a gRPC/REST client interoperate transparently.*

<Callout type="info">
  Cross-protocol interop is **array-proven** in both directions for STOMP ↔ gRPC. Always phrase it
  as "via the shared KubeMQ array, the same path gRPC uses" — there are no separately-proven
  STOMP ↔ MQTT or STOMP ↔ AMQP flows, though all transports share the same channels.
</Callout>

## Reliability at a glance [#reliability-at-a-glance]

| Pattern               | Delivery guarantee | What happens on a full output buffer                                         |
| --------------------- | ------------------ | ---------------------------------------------------------------------------- |
| Queues                | **at-least-once**  | NAck → requeue (never lost; duplicates tolerated)                            |
| Events / Events-Store | **at-most-once**   | that delivery is **dropped** for that subscriber; the connection stays alive |

Never promise exactly-once. See [Queues](/connectors/stomp/how-to/queues) and
[Events](/connectors/stomp/how-to/events).

## Related [#related]

<Cards>
  <Card title="Destination mapping" href="/connectors/stomp/how-to/destination-mapping" description="The full destination grammar, the slash→dot rule, aliases, wildcards, and the stomp.* header⇄tag convention." />

  <Card title="Destination grammar" href="/connectors/stomp/reference/destination-grammar" description="The formal grammar, the prefix→pattern table, and the header reference tables." />

  <Card title="Protocol versions" href="/connectors/stomp/how-to/protocol-versions" description="Version negotiation, per-version escaping, and the egress representability guard." />

  <Card title="Capabilities" href="/connectors/stomp/reference/capabilities" description="Supported commands, frame limits, and the out-of-scope feature list." />
</Cards>
