# Protocol versions (/connectors/stomp/how-to/protocol-versions)



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.

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

## Version negotiation [#version-negotiation]

* `accept-version` is **comma-separated, order-independent, and whitespace-tolerant** (`1.2, 1.1`
  and `1.1,1.2` both negotiate 1.2).
* **Unknown tokens are ignored** (`1.0,9.9` → 1.0).
* **No common version** → `ERROR` carrying `version: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-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`,`\\&#x60;,&#x2A;*`\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; &#x2A;*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-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**. &#x2A;*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).

<Callout type="warn">
  **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](/connectors/stomp/how-to/destination-mapping) for the full per-version
  representability table.
</Callout>

## SUBSCRIBE id rules [#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 [#per-version-ack-token-source]

The 1.2 `ack` token is an **opaque UUID** emitted on the MESSAGE frame, &#x2A;*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](/connectors/stomp/how-to/ack-modes-and-receipts) for the full ACK
flow.

## 1.0 leniency (compatibility) [#10-leniency-compatibility]

The connector applies ActiveMQ-style leniency on 1.0, beyond the strict 1.0 spec:

* **Auto subscription ids** (`auto-N`) when SUBSCRIBE omits `id`.
* **`client-individual` and `client` ack modes** accepted on 1.0 (not in the 1.0 spec).
* **NACK** accepted and honored on 1.0.
* **UNSUBSCRIBE by `destination`** (without `id`) — 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 [#related]

<Cards>
  <Card title="Destination mapping" href="/connectors/stomp/how-to/destination-mapping" description="Header escaping, the egress representability guard, and the stomp.* header to tag convention." />

  <Card title="Ack modes and receipts" href="/connectors/stomp/how-to/ack-modes-and-receipts" description="The ack modes and the per-version ACK flow in depth." />

  <Card title="Connectivity and security" href="/connectors/stomp/how-to/connectivity-and-security" description="The full CONNECT handshake and heartbeat negotiation." />
</Cards>
