eclipse-vertx/vert.x · error · java.lang.IllegalStateException
Cannot write an HTTP/2 frame over an HTTP/1.x connection
Error message
Cannot write an HTTP/2 frame over an HTTP/1.x connection
What it means
The stream object embedded in Http1ClientConnection implements writeFrame(type, flags, payload) from the HTTP/2 stream API, but on an HTTP/1.x connection there is no HTTP/2 framing layer, so it unconditionally throws IllegalStateException. This surfaces when code written against the generic Vert.x HTTP API calls writeFrame without checking the protocol.
Source
Thrown at vertx-core/src/main/java/io/vertx/core/http/impl/http1/Http1ClientConnection.java:685
PromiseInternal<Void> promise = context.promise();
conn.writeHead(this, request, chunked, buf != null ? ((BufferInternal)buf).getByteBuf() : null, end, connect, promise);
return promise.future();
}
@Override
public Future<Void> writeChunk(Buffer buff, boolean end) {
if (buff != null || end) {
Promise<Void> listener = context.promise();
conn.writeBuffer(this, buff != null ? ((BufferInternal)buff).getByteBuf() : null, end, listener);
return listener.future();
} else {
throw new IllegalStateException("???");
}
}
@Override
public Future<Void> writeFrame(int type, int flags, Buffer payload) {
throw new IllegalStateException("Cannot write an HTTP/2 frame over an HTTP/1.x connection");
}
@Override
public Future<Boolean> cancel() {
return writeReset(0x8).map(true);
}
@Override
public Future<Void> writeReset(long code) {
Promise<Void> promise = context.promise();
EventLoop eventLoop = conn.context.nettyEventLoop();
if (eventLoop.inEventLoop()) {
reset(code, promise);
} else {
eventLoop.execute(() -> reset(code, promise));
}
return promise.future();
}View on GitHub (pinned to fb308bd8c3)
Solutions
- Check the connection protocol before calling writeFrame: only invoke it when using HTTP/2 (request.version() / HttpVersion.HTTP_2, or connection.isSSL with alpn h2)
- Configure the client/server for HTTP/2 (setUseAlpn(true), setHttp2ClearTextEnabled, setProtocolVersion(HTTP_2)) if frames are actually needed
- Replace raw frame usage with protocol-neutral APIs (e.g. ping via connection ping if available on HTTP/2 only; otherwise drop the frame logic)
Example fix
// before
request.writeFrame(0x6, 0, pingPayload); // throws on HTTP/1.x
// after
if (request.version() == HttpVersion.HTTP_2) {
request.writeFrame(0x6, 0, pingPayload);
} else {
// HTTP/1.x has no frame layer; use app-level keepalive instead
} Defensive patterns
Strategy: type-guard
Validate before calling
if (request.version() != HttpVersion.HTTP_2) {
throw new IllegalStateException("writeFrame requires HTTP/2, got " + request.version());
} Type guard
boolean supportsFrames(HttpClientRequest req) {
return req.version() == HttpVersion.HTTP_2;
} Try / catch
try {
request.writeFrame(type, flags, payload);
} catch (IllegalStateException e) {
// connection is HTTP/1.x; use protocol-neutral alternative or skip
} Prevention
- Verify the negotiated protocol before using HTTP/2-only APIs
- Explicitly configure HTTP/2 (useAlpn, h2c) when frame-level features are required
- Keep HTTP/1 and HTTP/2 code paths separate in shared helpers
When it happens
Trigger: Calling HttpClientRequest/HttpClientStream.writeFrame(int type, int flags, Buffer payload) (e.g. sending a PING, RST_STREAM, or other raw HTTP/2 frame) on a request/connection negotiated as HTTP/1.1 — for example with the default protocol or after ALPN falls back to HTTP/1.x.
Common situations: Using Http2Client-style frame APIs on a client configured with setProtocolVersion(HTTP_1_1) or without HTTP/2 (no alpn/h2c); shared helper code written for HTTP/2 being reused for HTTP/1 connections; downgrade after h2c upgrade negotiation failed.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- HTTP/1.x connections don't support SETTINGS
- HTTP/1.x connections don't support PING
- http2MaxPoolSize must be > 0
- HTTP/1.x connections don't support GOAWAY
- Request must have a content-type header to decode a multipar
AI-assisted analysis of eclipse-vertx/vert.x@fb308bd8c3 (2026-09-06).
Data as JSON: /api/errors/475439290bfb82df.
Report an issue: GitHub.