Skip to content

jakarta Websocket served with h2 and timeouts disabled always times out after 30s #13475

@niloc132

Description

@niloc132

Jetty version(s)
Reproduced in Jetty 12.0.25, 11.0.26

Jetty Environment
For Jetty 12, tested ee9, ee10

Java version/vendor (use: java -version)

java -version
openjdk version "21.0.8" 2025-07-15
OpenJDK Runtime Environment (build 21.0.8+9)
OpenJDK 64-Bit Server VM (build 21.0.8+9, mixed mode, sharing)

OS type/version

uname -a
Linux runes 6.16.0-arch2-1 #1 SMP PREEMPT_DYNAMIC Wed, 13 Aug 2025 23:38:48 +0000 x86_64 GNU/Linux

Description

How to reproduce?
Create a simple jakarta websocket endpoint that disables its timeout, in keeping with the jakarta.websocket.Session#setMaxIdleTimeout javadoc:

@ServerEndpoint("/websocket/")
public class JakartaWebsocketImpl {
    @OnOpen
    public void onOpen(Session session) throws IOException {
        // Disable timeout, as the javadoc indicates:
        // "A value that is zero or negative indicates that this timeout will not be used."
        session.setMaxIdleTimeout(0);// fails
//        session.setMaxIdleTimeout(-1);// also fails
    }
    @OnMessage
    public void onMessage(String message, Session session) throws IOException {
        // Handle incoming messages
        System.out.println("Received message: " + message);
        session.getBasicRemote().sendText("Echo: " + message);
    }
}

The purpose of this simplified websocket is to have fairly continuous traffic to demonstrate that there is no substantial idle time, with the idle explicitly disabled.

Error observed 30s after client connects:

[Scheduler-335176384-1] WARN com.example.servlet.JakartaWebsocketImpl - Unhandled Error: com.example.servlet.JakartaWebsocketImpl@40c2f37b
org.eclipse.jetty.websocket.core.exception.WebSocketTimeoutException: Connection Idle Timeout
        at org.eclipse.jetty.websocket.core.WebSocketConnection.onIdleExpired(WebSocketConnection.java:287)
        at org.eclipse.jetty.http2.server.internal.ServerHTTP2StreamEndPoint.onTimeout(ServerHTTP2StreamEndPoint.java:59)
        at org.eclipse.jetty.http2.server.internal.HTTP2ServerConnection.onStreamTimeout(HTTP2ServerConnection.java:189)
        at org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory$HTTPServerSessionListener.onIdleTimeout(HTTP2ServerConnectionFactory.java:186)
        at org.eclipse.jetty.http2.HTTP2Stream.notifyIdleTimeout(HTTP2Stream.java:916)
        at org.eclipse.jetty.http2.HTTP2Stream.onIdleTimeout(HTTP2Stream.java:330)
        at org.eclipse.jetty.http2.HTTP2Session$StreamTimeouts.onExpired(HTTP2Session.java:2784)
        at org.eclipse.jetty.http2.HTTP2Session$StreamTimeouts.onExpired(HTTP2Session.java:2768)
        at org.eclipse.jetty.io.CyclicTimeouts.iterate(CyclicTimeouts.java:113)
        at org.eclipse.jetty.io.CyclicTimeouts$Timeouts.onTimeoutExpired(CyclicTimeouts.java:206)
        at org.eclipse.jetty.io.CyclicTimeout$Wakeup.run(CyclicTimeout.java:294)
        at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
        at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
        at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: java.util.concurrent.TimeoutException: Idle timeout 0 ms elapsed
        ... 11 more

Simple HTML/JS client, fails in both Firefox and Chrome:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>websocket sample</title>
</head>
<body>
<div id="messages"></div>
<script>
    var ws = new WebSocket('wss://localhost:8443/websocket/');
    ws.onopen = event => write('open');
    ws.onmessage = event => write('message:' + event.data);
    ws.onclose = event => write('close: ' + event.code + ' ' + event.reason);
    ws.onerror = event => write('error');
    function write(msg) {
        var div = document.createElement('div');
        div.append(new Date(), ' ', msg);
        document.getElementById('messages').append(div);
    }

    var id = 1;
    var cancel = setInterval(() => {
        if (ws.readyState === WebSocket.OPEN) {
            ws.send('hello ' + (id++));
            write('message sent')
        } else {
            write('WebSocket is not open. Current state: ' + ws.readyState);
            clearInterval(cancel);
        }
    }, 5000);
</script>
</body>
</html>

Jetty 12 ee10/ee9 server - note that browsers do not support h2 without tls, so a keystore is required to reproduce:

public class Server {

    public static void main(String[] args) throws Exception {
        org.eclipse.jetty.server.Server s = new org.eclipse.jetty.server.Server();
        final HttpConfiguration httpConfig = new HttpConfiguration();

        final HTTP2ServerConnectionFactory h2 = new HTTP2ServerConnectionFactory(httpConfig);
        h2.setRateControlFactory(new RateControl.Factory() {});
        final ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory();
        alpn.setDefaultProtocol(h2.getProtocol());
        final SslConnectionFactory tls = new SslConnectionFactory(alpn.getProtocol());
        final SslContextFactory sslContextFactory = tls.getSslContextFactory();
        sslContextFactory.setKeyStorePath("./keystore.p12");
        sslContextFactory.setKeyStorePassword("password");
        ServerConnector sc = new ServerConnector(s, tls, alpn, h2);
        sc.setPort(8443);

        s.setConnectors(new Connector[] { sc });

        WebAppContext context = new WebAppContext();
        context.setBaseResource(ResourceFactory.of(s).newClassLoaderResource("web/"));
        context.setContextPath("/");
        JakartaWebSocketServletContainerInitializer.configure(context, (ctx, container) -> {
            container.addEndpoint(JakartaWebsocketImpl.class);
        });
        s.setHandler(context);

        s.start();
        s.join();
    }
}

Jetty 11 example:

public class Server {

    public static void main(String[] args) throws Exception {
        org.eclipse.jetty.server.Server s = new org.eclipse.jetty.server.Server();
        final HttpConfiguration httpConfig = new HttpConfiguration();

        final HTTP2ServerConnectionFactory h2 = new HTTP2ServerConnectionFactory(httpConfig);
        h2.setRateControlFactory(new RateControl.Factory() {});
        final ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory();
        alpn.setDefaultProtocol(h2.getProtocol());
        final SslConnectionFactory tls = new SslConnectionFactory(alpn.getProtocol());
        final SslContextFactory sslContextFactory = tls.getSslContextFactory();
        sslContextFactory.setKeyStorePath("./keystore.p12");
        sslContextFactory.setKeyStorePassword("password");
        ServerConnector sc = new ServerConnector(s, tls, alpn, h2);
        sc.setPort(8443);

        s.setConnectors(new Connector[] { sc });

        WebAppContext context = new WebAppContext();
        context.setBaseResource(Resource.newClassPathResource("web/"));
        context.setContextPath("/");
        JakartaWebSocketServletContainerInitializer.configure(context, (ctx, container) -> {
            container.addEndpoint(JakartaWebsocketImpl.class);
        });
        s.setHandler(context);

        s.start();
        s.join();
    }
}

org.eclipse.jetty.io.IdleTimeout#setIdleTimeout and #activate() have explicit checks for timeout greater than zero, but the org.eclipse.jetty.http2.HTTP2StreamEndPoint implementations seem to be missing this. Additionally, the actual timeout seems based on some 30s timer, rather than the explicitly set instant timeout. Plus, it isn't actually used as an "idle" timeout, just a "kill the connection after X seconds" timer.

Let me know if sharing the full working project would help here.

Workaround:
Disabling the session idle timeout will prevent the bug, but will make the websocket experience timeouts with http/1.1 in the normal fashion

Metadata

Metadata

Assignees

Labels

BugFor general bugs on Jetty side

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions