apache/cassandra · critical · ProtocolException

Attempted to encode a response with an unset stream id:

Error message

Attempted to encode a response with an unset stream id: 

What it means

Fatal ProtocolException thrown when the native-protocol layer tries to encode a server Response whose streamId is still UNSET_STREAM_ID. A response must carry the stream id of the request it answers; an unset id means a server code path produced a response without routing information, and sending it could deliver it to an unrelated in-flight request (CASSANDRA-21508). The connection is torn down intentionally to avoid mis-routing.

Solutions

  1. Find the server code path that created the Response and ensure streamId is set from the originating request before encoding (response.setStreamId(request.streamId)).
  2. Check server logs for the stack trace preceding the connection teardown to identify the producing handler.
  3. Upgrade to a version containing the fix for the specific handler bug (search JIRA for CASSANDRA-21508 follow-ups).
  4. As a client-side mitigation, expect the connection to close and rely on driver reconnection/retry; the fatal exception is intentional protection.
  5. If seen without any local patches, report with full server logs — it indicates an internal invariant violation.

Example fix

// before
Message.Response resp = Message.response(type, message);
channel.writeAndFlush(resp); // stream id never set
// after
resp.setStreamId(message.getStreamId());
channel.writeAndFlush(resp);
Defensive patterns

Strategy: type-guard

Validate before calling

// server-side guard before writing a response
if (response.getStreamId() == Message.UNSET_STREAM_ID)
    throw new IllegalStateException("Response written without stream id: " + response);

Type guard

boolean hasRoutedStreamId(Message.Response r) { return r.getStreamId() != Message.UNSET_STREAM_ID; }

Prevention

When it happens

Trigger: A server handler path creates a Response (e.g. an ERROR response or event) and writes it without calling setStreamId with the originating request's stream id — typically in custom/modified request handlers, early-failure paths that build responses before pairing with a request, or internal code paths responding outside the normal request dispatch.

Common situations: Running patched/unreleased Cassandra builds where a new error path forgets to stamp the stream id; a bug in driver-facing request handling causing connection resets; after CASSANDRA-21508, responses that previously would have been silently mis-routed now kill the connection with this exception in server logs.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at src/java/org/apache/cassandra/transport/Message.java:347

            this.warnings = warnings;
            return this;
        }

        public List<String> getWarnings()
        {
            return warnings;
        }
    }

    public Envelope encode(ProtocolVersion version, int streamId)
    {
        // A Response's stream id must be stamped before it is serialized to the wire. UNSET_STREAM_ID here
        // means a server code path produced a response without routing information; sending it would risk
        // delivering it to an unrelated in-flight request (CASSANDRA-21508). Fail fatally so the connection
        // is torn down rather than mis-route a response. Checked before the try below so it is not caught and
        // re-wrapped (which would carry the unset id forward).
        if (streamId == UNSET_STREAM_ID)
            throw ProtocolException.toFatalException(new ProtocolException("Attempted to encode a response with an unset stream id: " + this));

        int flags = Flag.none();
        @SuppressWarnings("unchecked")
        Codec<Message> codec = (Codec<Message>)this.type.codec;
        try
        {
            int messageSize = codec.encodedSize(this, version);
            ByteBuf body;
            if (this instanceof Response)
            {
                Response message = (Response)this;
                TimeUUID tracingId = message.getTracingId();
                Map<String, ByteBuffer> customPayload = message.getCustomPayload();
                if (tracingId != null)
                    messageSize += TimeUUID.sizeInBytes();
                List<String> warnings = message.getWarnings();
                if (warnings != null)
                {

View on GitHub (pinned to 88fd0f6a0e)