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
- Log the raw :status bytes and fix the peer/proxy that emits the malformed header
- Ensure the server always sends a valid 3-digit :status (e.g. 200, 404) before closing the stream
- Check for intermediaries that convert to HTTP/1.x and back, mangling the status
- 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
- Test servers/proxies emit only RFC-valid 3-digit :status values
- Fuzz HTTP/2 intermediaries before production
- Upgrade proxies known to corrupt HTTP/2 pseudo-headers
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- TLS ALPN negotiation failed with protocols: ${protocols}
- EOF trying to read ${length} bytes
- Invalid initial window size: ${newWindowSize}
- Expected NPN/ALPN ${expectedProtocol}: ${negotiatedProtocol}
- UNAVAILABLE
AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08).
Data as JSON: /api/errors/082060c178bba3d6.
Report an issue: GitHub.