WebSockets in Java: when HTTP is no longer enough
\n\nImagine an online exchange. You're watching a Bitcoin price chart. The numbers change every second. Or a multiplayer game where you move a character and a hundred other players see it instantly. Or just a corporate chat where messages arrive without pressing F5.
\n\nHow does this work under the hood? Previously, developers had to improvise: polling (the client asks the server every second "hey, any news?"), long polling (the server holds the connection until news arrives), Server-Sent Events (one-way stream). All of these are workarounds. The HTTP protocol was originally designed as request-response. It doesn't know how to "push" data on its own.
\n\nThis is where WebSockets come to the rescue. This is a protocol that establishes a connection once and keeps it open. The server can send data to the client at any time, and vice versa. Bidirectional real-time communication, without the overhead of HTTP headers.
\n\nIn this article, we will cover WebSockets in Java from start to finish. We'll write a full-fledged chat, examine pitfalls, and compare popular libraries. If you are a Java developer and want to build real-time applications in Java, you've come to the right place.
\n\nHow does WebSocket work? A quick primer
\n\nWebSocket is not magic, it's just a protocol over TCP. It starts as a regular HTTP request (handshake), and then "upgrades" to a persistent bidirectional channel.
\n\nStage 1: Handshake
\nThe client sends a regular HTTP GET request with the header Upgrade: websocket. The server responds with status code 101 Switching Protocols. That's it. From this point on, data is sent not as HTTP packets, but as WebSocket frames.
Stage 2: Data frames
\nData is transmitted in small packets (frames). A frame can be text (a regular string) or binary (image, audio). Java libraries hide this complexity — you simply work with strings or bytes.
\n\nStage 3: Closing
\nEither side can initiate closing the connection by sending a special Close frame.
\n\nSounds simple? It is. But the devil is in the details: timeouts, reconnects, scaling. More on that later.
\n\nLibraries for WebSockets in Java
\n\nIn the Java ecosystem, there are three main approaches:
\n\n- \n
- Jakarta WebSocket (JSR 356) — the Java EE / Jakarta EE standard. Works on any compatible server (Tomcat, Jetty, GlassFish). Lots of annotations, little code. \n
- Spring WebSocket — an extension of the standard within the Spring ecosystem. Provides integration with Security, STOMP (a protocol over WebSocket for subscribing to topics), and convenient templates. \n
- Netty / Vert.x / Undertow — low-level asynchronous frameworks. Maximum performance, but more code. \n
For 90% of tasks, Jakarta or Spring will suffice. We'll look at both options with real examples.
\n\nExample 1: A simple chat on Jakarta WebSocket (without Spring)
\n\nLet's say we have a good old Tomcat 10+ (Jakarta EE). We need a server endpoint and a client (HTML+JS).
\n\nServer: endpoint class
\nimport jakarta.websocket.*;\nimport jakarta.websocket.server.ServerEndpoint;\nimport java.io.IOException;\nimport java.util.Set;\nimport java.util.concurrent.CopyOnWriteArraySet;\n\n@ServerEndpoint("/chat")\npublic class ChatEndpoint {\n\n // Store all active sessions (thread-safe set)\n private static final Set<Session> sessions = new CopyOnWriteArraySet<>();\n\n @OnOpen\n public void onOpen(Session session) {\n sessions.add(session);\n broadcast("User connected. Total in chat: " + sessions.size());\n }\n\n @OnMessage\n public void onMessage(String message, Session session) {\n // Can add logic: send only to