Protocol versions
STOMP 1.0/1.1/1.2 on the KubeMQ connector — version negotiation from accept-version, the per-version feature matrix, header escaping, and ack token rules.
The KubeMQ STOMP connector speaks STOMP 1.0, 1.1, and 1.2. The version is chosen during the
CONNECT handshake from your accept-version header — the connector picks the highest common of
{1.0, 1.1, 1.2}. A missing or empty accept-version defaults to 1.0; if there is no common
version the connector returns an ERROR and closes.
Use accept-version:1.2. Every example sends accept-version:1.2. 1.2 is the only version that
can escape \r (CR) in header values, uses the explicit ack token, and stamps the subscription
header on MESSAGE frames. 1.0 and 1.1 are supported for compatibility with older clients.
Version negotiation
accept-versionis comma-separated, order-independent, and whitespace-tolerant (1.2, 1.1and1.1,1.2both negotiate 1.2).- Unknown tokens are ignored (
1.0,9.9→ 1.0). - No common version →
ERRORcarryingversion:1.0,1.1,1.2+ close.
The negotiated version is echoed back on the CONNECTED frame's version header and surfaces as
version in the management API connection record.
Feature matrix
| Feature | 1.0 | 1.1 | 1.2 | Notes |
|---|---|---|---|---|
| Highest-common negotiation | Yes | Yes | Yes | missing/empty accept-version → 1.0 |
| Header escaping | none | \n,\c,\\ | \n,\c,\\,\r | applied only to non-CONNECT/STOMP frames once negotiated |
\r (CR) representable in a header value | No | No | Yes | 1.0/1.1 drop CR on egress |
| Repeated header → first value wins | Yes | Yes | Yes | all occurrences preserved in arrival order |
SUBSCRIBE id required | No — auto auto-N | Yes | Yes | missing id on 1.1/1.2 → invalid subscription |
MESSAGE subscription header | omitted | Yes | Yes | identifies the matching subscription |
MESSAGE ack token (opaque UUID) | No | No | Yes | 1.2-only; distinct from message-id |
| ACK/NACK correlation | message-id → oldest pending | message-id + subscription | id:<ack-token> | per-version, connection-scoped |
client / client-individual ack | Yes (lenient) | Yes | Yes | accepted on 1.0 too (ActiveMQ-style) |
| NACK | Yes (lenient) | Yes | Yes | honored on 1.0 too |
UNSUBSCRIBE by destination (no id) | Yes (fallback) | No | No | 1.0-only leniency |
| CRLF line endings tolerated | Yes | Yes | Yes | a trailing \r is stripped on read (all versions) |
Header escaping per version
Header escaping applies to both the header name and value, but only on the negotiated 1.1/1.2 version and only for non-CONNECT/STOMP frames. The handshake is exempt: CONNECT and STOMP frames are never unescaped, and CONNECTED is never escaped. 1.0 does no escaping at all.
| Escape sequence | Decodes to | 1.0 | 1.1 | 1.2 |
|---|---|---|---|---|
\n | LF | literal | Yes | Yes |
\c | : | literal | Yes | Yes |
\\ | \ | literal | Yes | Yes |
\r | CR | literal | fatal malformed frame | Yes |
any other (\t, …) | — | literal | fatal | fatal |
Columns are the STOMP version negotiated via the CONNECT accept-version header.
On 1.1, an \r escape (or any undefined escape) is a fatal malformed frame. A trailing lone
backslash is fatal. A repeated header resolves to its first value (all occurrences are still
preserved in arrival order).
CR/LF in a header value is silently dropped to 1.0/1.1 subscribers. STOMP 1.1 has no escape for
CR, and 1.0 has no escaping at all, so the egress representability guard drops any header it cannot
serialize (rather than corrupting the frame) and the connection stays alive. Use
accept-version:1.2 for any binary or special-character scenario, and keep CR/LF out of header
values — structured or multiline metadata belongs in the body. See
Destination mapping for the full per-version
representability table.
SUBSCRIBE id rules
| Version | id on SUBSCRIBE | Behavior |
|---|---|---|
| 1.0 | optional | auto-generated auto-N when absent |
| 1.1 / 1.2 | required | missing/empty id → ERROR "invalid subscription" / "id header required" + close |
Per-version ACK token source
The 1.2 ack token is an opaque UUID emitted on the MESSAGE frame, distinct from
message-id, and the only correct way to ACK on 1.2. On 1.1 you ACK by message-id +
subscription; on 1.0 by message-id alone, which resolves to the oldest pending delivery on the
connection.
| Version | MESSAGE carries | ACK/NACK by |
|---|---|---|
| 1.2 | subscription, ack=UUID token, message-id | id:<ack-token> |
| 1.1 | subscription, message-id (no ack) | message-id + subscription |
| 1.0 | message-id only (no subscription, no ack) | message-id → oldest pending |
See Ack modes and receipts for the full ACK flow.
1.0 leniency (compatibility)
The connector applies ActiveMQ-style leniency on 1.0, beyond the strict 1.0 spec:
- Auto subscription ids (
auto-N) when SUBSCRIBE omitsid. client-individualandclientack modes accepted on 1.0 (not in the 1.0 spec).- NACK accepted and honored on 1.0.
- UNSUBSCRIBE by
destination(withoutid) — the connector removes the first matching subscription.
These are conveniences for legacy clients. Do not depend on them for portability — prefer
accept-version:1.2.
Related
Was this page helpful?
Events Store
Persistent STOMP pub/sub over KubeMQ Events Store — the /topic-store/ prefix, the replay headers, and replaying history from first, last, a sequence, or a time.
Queries
Requester-only RPC queries over STOMP — the /query/ destination prefix and the 3-step reply-to flow that returns a response body and tags on KubeMQ Queries.