apache/cassandra · error · ProtocolException

Invalid message version. Got %s but previous messages on thi

Error message

Invalid message version. Got %s but previous messages on this connection had version %s

What it means

ProtocolException thrown by the pre-v5 Envelope.Decoder when a frame's protocol version differs from the version already established on that connection. Native protocol versions are negotiated once (via STARTUP) and must remain constant for the connection's lifetime; a mid-connection version change means a desynchronized or malicious stream, so the frame is rejected.

Source

Thrown at src/java/org/apache/cassandra/transport/PreV5Handlers.java:295

    /**
     * Simple adaptor to allow {@link org.apache.cassandra.transport.Message.Decoder#decodeMessage(Channel, Envelope)}
     * to be used as a handler in pre-V5 pipelines
     */
    @ChannelHandler.Sharable
    public static class ProtocolDecoder extends MessageToMessageDecoder<Envelope>
    {
        public static final ProtocolDecoder instance = new ProtocolDecoder();
        private ProtocolDecoder(){}

        public void decode(ChannelHandlerContext ctx, Envelope source, List<Object> results)
        {
            try
            {
                ProtocolVersion version = getConnectionVersion(ctx);
                if (source.header.version != version)
                {
                    throw new ProtocolException(
                        String.format("Invalid message version. Got %s but previous " +
                                      "messages on this connection had version %s",
                                      source.header.version, version));
                }
                results.add(Message.Decoder.decodeMessage(ctx.channel(), source));
            }
            catch (Throwable ex)
            {
                source.release();
                // Remember the streamId
                throw ErrorMessage.wrap(ex, source.header.streamId);
            }
        }
    }

    /**
     * Simple adaptor to plug CQL message encoding into pre-V5 pipelines
     */

View on GitHub (pinned to 88fd0f6a0e)

Solutions

  1. Reconnect so the version is renegotiated via STARTUP and kept consistent.
  2. Fix the client to use the negotiated ProtocolVersion object for every frame instead of a hardcoded version byte.
  3. Check intermediaries (proxies, TLS offloaders) that may rewrite or corrupt frame headers.
  4. Update the driver to a version that pins frames to the negotiated version.

Example fix

// before: hardcoded version byte in client frame writer
out.writeByte(3); // v3 regardless of negotiation
// after: write the negotiated version
out.writeByte(connection.getProtocolVersion().asInt());
Defensive patterns

Strategy: validation

Validate before calling

// client: assert every frame uses the negotiated version
checkState(negotiatedVersion == frameVersion, "frame version %s != negotiated %s", frameVersion, negotiatedVersion);

Try / catch

try { decode(buf); } catch (ProtocolException e) { if (e.getMessage().contains("Invalid message version")) { reconnectWithRenegotiation(); } }

Prevention

When it happens

Trigger: On a pre-v5 connection, decode obtains the connection's negotiated version (getConnectionVersion) and finds source.header.version != that version — e.g. after byte-stream desync, or a client suddenly sending frames with a different version byte than STARTUP declared.

Common situations: Stream corruption from proxies/NAT devices, clients that hardcode a version byte differing from their negotiated version, fuzzers, or mixing driver instances over a shared connection.

Related errors


AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10). Data as JSON: /api/errors/bb41b457588ac42d. Report an issue: GitHub.