grpc/grpc-java · error · NumberFormatException

Malformed status code

Error message

Malformed status code 

What it means

Http2ClientStreamTransportState defines a marshaller for the HTTP/2 ':status' pseudo-header that parses a 3-digit ASCII status code. parseAsciiString throws NumberFormatException('Malformed status code ...') when the byte array is shorter than 3 characters, i.e. the peer sent a status line that cannot be a valid HTTP status code.

Source

Thrown at core/src/main/java/io/grpc/internal/Http2ClientStreamTransportState.java:54

   */
  private static final InternalMetadata.TrustedAsciiMarshaller<Integer> HTTP_STATUS_MARSHALLER =
      new InternalMetadata.TrustedAsciiMarshaller<Integer>() {
        @Override
        public byte[] toAsciiString(Integer value) {
          throw new UnsupportedOperationException();
        }

        /**
         * RFC 7231 says status codes are 3 digits long.
         *
         * @see <a href="https://tools.ietf.org/html/rfc7231#section-6">RFC 7231</a>
         */
        @Override
        public Integer parseAsciiString(byte[] serialized) {
          if (serialized.length >= 3) {
            return (serialized[0] - '0') * 100 + (serialized[1] - '0') * 10 + (serialized[2] - '0');
          }
          throw new NumberFormatException(
              "Malformed status code " + new String(serialized, InternalMetadata.US_ASCII));
        }
      };

  private static final Metadata.Key<Integer> HTTP2_STATUS = InternalMetadata.keyOf(":status",
      HTTP_STATUS_MARSHALLER);

  /** When non-{@code null}, {@link #transportErrorMetadata} must also be non-{@code null}. */
  private Status transportError;
  private Metadata transportErrorMetadata;
  private Charset errorCharset = StandardCharsets.UTF_8;
  private boolean headersReceived;

  protected Http2ClientStreamTransportState(
      int maxMessageSize,
      StatsTraceContext statsTraceCtx,
      TransportTracer transportTracer,
      CallOptions options) {

View on GitHub (pinned to 64daddc1f3)

Solutions

  1. Log the raw :status bytes and fix the peer/proxy that emits the malformed header
  2. Ensure the server always sends a valid 3-digit :status (e.g. 200, 404) before closing the stream
  3. Check for intermediaries that convert to HTTP/1.x and back, mangling the status
  4. Upgrade the proxy/server software — this is a peer protocol violation
Defensive patterns

Strategy: try-catch

Validate before calling

// before accepting a peer's :status value
if (statusBytes == null || statusBytes.length < 3) throw new IllegalArgumentException(":status must be 3 ASCII digits");

Try / catch

try { transport.handleStatus(...); } catch (NumberFormatException e) {
  if (e.getMessage().startsWith("Malformed status code")) {
    log.error("peer sent malformed :status: {}", e.getMessage()); // fail the stream cleanly
  } else throw e;
}

Prevention

When it happens

Trigger: Receiving an HTTP/2 :status header whose serialized value has fewer than 3 bytes — a malformed frame from the peer, a broken proxy injecting a truncated status, or a test/bidi endpoint writing an invalid :status value.

Common situations: Misbehaving HTTP/2 proxies or load balancers; custom servers emitting non-conforming :status headers; fuzzing or corrupted transport frames; connecting gRPC to a non-gRPC HTTP/2 endpoint.

Understand the failure class

Related errors


AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08). Data as JSON: /api/errors/082060c178bba3d6. Report an issue: GitHub.