Architecture
Inside the STOMP connector — the embedded STOMP server, the frame codec, version negotiation, handler dispatch, and the destination → pattern → channel router.
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 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.
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/61614via 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\nlines + a blank line + the body + aNULterminator). It applies per-version header escaping and is binary-safe via a content-length-aware reader; the writer always stampscontent-lengthon 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
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
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.
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).
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) — there is no MQTT-style +/#. A wildcard on
SEND, or on queues / events-store / RPC, is rejected with invalid destination. See
Destination grammar.
The three-step RPC flow (Commands & Queries)
/command/ and /query/ are request/reply, and STOMP is requester-only — 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:
SUBSCRIBEto/reply/<name>first — connection-local: no array, no authz, no ack tracking.SENDto/command/<svc>or/query/<svc>with a requiredreply-to:(an active/reply/subscription on the same connection), an optionalcorrelation-id:(echoed only when set), and an optionaltimeout:in milliseconds (effectivemin(timeout, RpcTimeoutSeconds * 1000), default 30000).- The reply arrives as a
MESSAGEon the/reply/subscription.
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 and
Error frames.
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.
The same KubeMQ channel backs both transports, so a STOMP client and a gRPC/REST client interoperate transparently.
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.
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 and Events.
Related
Destination mapping
The full destination grammar, the slash→dot rule, aliases, wildcards, and the stomp.* header⇄tag convention.
Destination grammar
The formal grammar, the prefix→pattern table, and the header reference tables.
Protocol versions
Version negotiation, per-version escaping, and the egress representability guard.
Capabilities
Supported commands, frame limits, and the out-of-scope feature list.
Was this page helpful?