Capabilities
What the KubeMQ STOMP connector supports and rejects — protocol versions, client commands, ack modes, hard-rejected features, and reliability guarantees.
This reference defines exactly what the embedded KubeMQ STOMP connector supports, what
it hard-rejects, and the hard limits it enforces. The connector is an embedded STOMP
1.0 / 1.1 / 1.2 server inside kubemq-server with its own raw-TCP / TLS listeners and a
hand-rolled frame codec. It bridges STOMP onto KubeMQ's five native patterns by destination
prefix — see Destination Grammar.
Protocol versions
| Version | Supported | Notes |
|---|---|---|
| 1.2 | Yes (recommended) | full escaping including CR; id-based ACK token; id required on SUBSCRIBE |
| 1.1 | Yes | escapes : / LF / \ but not CR; message-id + subscription ACK; id required on SUBSCRIBE |
| 1.0 | Yes | no header escaping; auto sub-id; ACK by message-id; ActiveMQ-style leniency |
accept-version picks the highest common version. The list is comma-separated,
order-independent, and whitespace-tolerant; unknown tokens are ignored. An absent or empty
accept-version negotiates 1.0. When there is no common version the connector emits an
ERROR carrying version:1.0,1.1,1.2 and closes the connection.
Recommended default: accept-version:1.2. It is the only version that can represent CR
in a header value — and the only one that never silently drops a CR/LF-bearing header to the
subscriber. See Protocol Versions for the
full per-version feature matrix.
Supported client commands
All 11 client → server STOMP commands are recognized:
| Command | Supported | Behavior |
|---|---|---|
CONNECT / STOMP | Yes | handshake; must be the first frame within 30 s |
SEND | Yes | routes by destination prefix to the matching pattern |
SUBSCRIBE | Yes | events / store / queues / /reply/; not /command or /query |
UNSUBSCRIBE | Yes | by id (1.1 / 1.2); by destination on 1.0 |
ACK | Yes | queue acknowledgement; no-op on events subscriptions |
NACK | Yes | queue negative-ack / requeue; honored on 1.0 too (lenient) |
DISCONNECT | Yes | graceful close; RECEIPT flushed before socket close |
BEGIN / COMMIT / ABORT | Hard-rejected | ERROR "transactions not supported" + close |
Server → client frames are CONNECTED, MESSAGE, RECEIPT, and ERROR. A body is allowed
only on SEND (client) and on MESSAGE / ERROR (server) — a body on any other command
is a malformed frame.
Acknowledgement modes (queues)
Three ack modes apply to a SUBSCRIBE on a /queue/ destination:
ack mode | Client action | Semantics |
|---|---|---|
auto (default) | none | fire-and-forget; reserve → enqueue → immediate ack |
client-individual | ACK / NACK one message | resolves exactly that one delivery — the recommended reliable default |
client (cumulative) | ACK a message + all earlier on the subscription | grouped per downstream transaction |
- Ack timeout = 30 s. A 1 s sweeper NAcks / requeues the delivery; the client is not disconnected, and a late ACK after expiry is silently ignored.
- No client-side DLQ. Redelivery surfaces only via the
redelivered:trueMESSAGE header;maxReceiveCount/ DLQ is queue-channel config on the broker side, not a STOMP feature.
See ACK Modes & Receipts for per-version ACK / NACK token correlation and receipt rules.
Events-Store replay (/topic-store/ only)
A SUBSCRIBE to a /topic-store/ destination accepts start-from + start-value headers.
Replay headers are ignored for plain /topic/ Events.
start-from | start-value | Replays from |
|---|---|---|
absent / new | must NOT be present | new messages only (default) |
first | must NOT be present | the earliest stored message |
last | must NOT be present | the most recent stored message |
sequence | required, numeric ≥ 0 | the given sequence number |
time | required, RFC3339 or unix-seconds ≥ 0 | the given timestamp |
time-delta | required, numeric > 0 (seconds) | now minus N seconds |
Bad combinations — a start-value present for new / first / last, or a missing / invalid
value for sequence / time / time-delta — produce ERROR "invalid subscription" + close.
See Events-Store.
Hard-rejected features
These are documented exclusions. Each is refused deterministically with an ERROR frame
followed by an immediate socket close — no example ever uses them.
| Feature | Behavior |
|---|---|
| STOMP transactions | BEGIN / COMMIT / ABORT, or any frame carrying a transaction header, → ERROR "transactions not supported" + close |
| Selectors | a selector header on SUBSCRIBE → ERROR "selectors not supported" + close (even without a transaction) |
SUBSCRIBE to /command / /query | → ERROR "cannot subscribe to RPC destinations" + close — STOMP is RPC-requester-only |
SEND to /reply/ | → ERROR "invalid destination" + close — /reply/ is a connection-local sink |
V1 non-goals (not implemented)
These are not refused at the frame layer (where applicable) but are simply not implemented in V1. They are listed so nothing is silently omitted:
| Feature | Status |
|---|---|
| STOMP-over-WebSocket | not in V1 — the connector is raw TCP / TLS only. This is why JS examples use stompit (raw TCP), not @stomp/stompjs (WebSocket-only). |
| Durable subscriptions | not supported — use Events-Store /topic-store/ + start-from replay for persistence |
| Temp queues / temp topics | not supported |
| vhost semantics | the host header is accepted and ignored (no vhost isolation) |
| Per-operation token re-validation | auth is connect-time only; token expiry does not drop a live connection |
| Client-side DLQ / redelivery-limit knobs | redelivery surfaces only as redelivered:true; DLQ is broker config |
Hard limits
Frame-codec constants (not tunable — no env var)
These five are package constants in the frame codec:
| Limit | Value | Error on violation |
|---|---|---|
| Max headers / frame | 64 | frame too large |
| Max header-block bytes | 8192 (8 KiB; command + headers share the budget) | frame too large |
| Max destination length | 512 bytes | invalid destination |
| Max custom tags (SEND) | 32 | frame too large + close |
| Max tag-value bytes (SEND) | 4096 | frame too large + close |
The one tunable codec limit
| Limit | Default | Env var |
|---|---|---|
| Max body size | 104857600 (~100 MiB) | CONNECTORS_STOMP_MAX_BODY_SIZE |
A body larger than MaxBodySize produces frame too large. This is the only config-tunable
codec limit — the other five are constants. See
Configuration.
Connection / flow-control limits
| Limit | Value | Notes |
|---|---|---|
MaxConnections | 1000 (0 = unlimited) | counted at accept — a raw socket that never CONNECTs still consumes a slot; an over-limit ERROR "connection limit reached" is deferred to the handshake |
| Per-channel queue inflight | 64 | not tunable |
| Out-queue depth | 256 | not tunable |
| Per-subscription events deliver buffer | 100 | tunable via CONNECTORS_STOMP_SUB_BUFF_SIZE (1..10000) |
content-length and binary safety
content-length, when present, is authoritative and binary-safe — the reader reads exactly
N bytes then requires a NUL terminator (the only way to send a body with embedded NULs). The
writer auto-stamps content-length on every bodied MESSAGE / ERROR. There is no
content-type default at the frame layer, so a producer that sets no content-type tag yields a
MESSAGE with no content-type header — the subscriber must assume binary. See
Destination Grammar for the
header ⇄ tag mapping.
Reliability
| Pattern | Guarantee |
|---|---|
| Queues | at-least-once — duplicates tolerated, never lost; NAck / requeue on full output, sweeper requeue on ack-timeout, disconnect NAcks pending |
| Events / Events-Store | at-most-once — a full subscriber output buffer drops THAT delivery for THAT subscriber; the connection stays alive |
Never exactly-once. No example or guide should promise it.
The ten STOMP gotchas
The behaviors most likely to surprise a STOMP migrant. Each is surfaced as a callout in the page it applies to:
| # | Gotcha |
|---|---|
| 1 | RPC failures are a MESSAGE + stomp-error header, NOT an ERROR frame — the connection stays OPEN. A logical error carries the responder's body + tags alongside stomp-error; only a transport error / timeout or nil response is empty-bodied — never assume an empty body. |
| 2 | Ack timeout = 30 s → requeue (not disconnect); NO client-side DLQ — redelivery surfaces only as redelivered:true. See Acknowledgement modes (queues). |
| 3 | Queues at-least-once; events / store at-most-once — never exactly-once. |
| 4 | No content-type frame default — no content-type tag means no content-type header (the subscriber assumes binary). |
| 5 | CR/LF in a header value is silently dropped to 1.0 / 1.1 subscribers — use accept-version:1.2; structured metadata belongs in the body. |
| 6 | Wildcards are events-only + subscribe-only, using the message broker's native syntax (* = one segment, > = final tail); egress delivers the CONCRETE channel — no MQTT + / #. |
| 7 | A literal . in a destination segment is lossy — /topic/a.b and /topic/a/b collide; egress emits the slash form. |
| 8 | @stomp/stompjs is WebSocket-only and cannot drive the raw-TCP V1 connector — JS examples use stompit (raw TCP). |
| 9 | Cross-protocol interop is gRPC / array-proven only — phrase it as "via the shared KubeMQ array, the same path gRPC uses". |
| 10 | SUBSCRIBE to /command / /query is rejected; transactions & selectors are hard-rejected (ERROR + close) — STOMP is RPC-requester-only. |
Related
Destination Grammar
The destination → (pattern, channel) grammar and the header ⇄ tag tables.
Error Frames
The complete ERROR-frame vocabulary behind each rejection above.
Connections & Observability
The management API, Prometheus metrics, dashboard, and audit taxonomy.
Migrating from STOMP
What to drop when migrating an existing STOMP application to KubeMQ.
Was this page helpful?