October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PCOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
World desk10 min

Building a Sample Java WebSocket Client with Java 11+

A practical Java 11+ WebSocket client tutorial using the standard JDK API, with complete listener code, fragmented-message handling, authentication, subprotocols, TLS, reconnects, and alternatives.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For a dependency-free Java WebSocket client, use the standard java.net.http.WebSocket API introduced in Java 11. It connects to ws:// or wss:// endpoints asynchronously, handles text, binary, ping, pong, close, and error events, and supports handshake headers, subprotocols, timeouts, proxies, and custom TLS.

This tutorial builds a complete client and then explains the lifecycle details that short examples often miss: listener demand, fragmented messages, process lifetime, authentication, reconnects, and graceful shutdown.

What a Java WebSocket client does

A WebSocket starts as an HTTP handshake. With HTTP/1.1, a successful server response is 101 Switching Protocols; communication then continues as WebSocket frames over a persistent, bidirectional connection. Use ws:// for an unencrypted connection and wss:// for TLS-encrypted WebSocket traffic.

This is different from a browser JavaScript client, a raw TCP socket, long polling, or a REST client that repeatedly makes HTTP requests. The Java SE HTTP client and WebSocket APIs are available from Java 11 onward (Java 11 API documentation).

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Prerequisites and project setup

  • Java 11 or newer.
  • A reachable WebSocket server and its endpoint URI.
  • The server’s authentication method, expected message format, and required subprotocol, if any.
  • Maven is optional; the JDK client requires no third-party WebSocket dependency.

A minimal Maven project can target Java 11:

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                             https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>example</groupId>
  <artifactId>java-websocket-client</artifactId>
  <version>1.0-SNAPSHOT</version>
  <properties>
    <maven.compiler.release>11</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
</project>

The API is in the JDK’s java.net.http module, so Maven does not download a WebSocket library. A JPMS project should declare:

module example.websocket.client {
    requires java.net.http;
}

Smallest working client

The builder opens the connection with buildAsync, which returns a CompletableFuture<WebSocket>. The listener must request another event after handling one; otherwise delivery can stop.

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.WebSocket;
import java.util.concurrent.CompletionStage;

public final class SampleWebSocketClient {
    public static void main(String[] args) {
        URI endpoint = URI.create("ws://localhost:8080/chat");
        HttpClient client = HttpClient.newHttpClient();

        WebSocket.Listener listener = new WebSocket.Listener() {
            @Override
            public void onOpen(WebSocket socket) {
                System.out.println("Connected");
                socket.request(1);
            }

            @Override
            public CompletionStage<?> onText(
                    WebSocket socket, CharSequence data, boolean last) {
                System.out.println("Received: " + data);
                socket.request(1);
                return null;
            }

            @Override
            public CompletionStage<?> onClose(
                    WebSocket socket, int status, String reason) {
                System.out.printf("Closed: %d (%s)%n", status, reason);
                return null;
            }

            @Override
            public void onError(WebSocket socket, Throwable error) {
                error.printStackTrace();
            }
        };

        WebSocket socket = client.newWebSocketBuilder()
                .buildAsync(endpoint, listener)
                .join();

        socket.sendText("Hello from Java", true).join();
        socket.sendClose(WebSocket.NORMAL_CLOSURE, "Done").join();
    }
}

join() is convenient for a command-line demonstration, but it blocks the calling thread. In a service, compose the futures instead of blocking a request or event-loop thread.

A listener that handles real messages

A WebSocket message is not necessarily delivered in one callback. A text or binary message can be split across frames and callbacks. The last argument is true only for the callback that ends the message. Buffer text before parsing it, and stream or accumulate binary data according to your application.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import java.net.http.WebSocket;
import java.nio.ByteBuffer;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

public final class ClientListener implements WebSocket.Listener {
    private final StringBuilder text = new StringBuilder();
    private final CompletableFuture<Void> closed = new CompletableFuture<>();

    @Override
    public void onOpen(WebSocket socket) {
        System.out.println("Connected");
        socket.request(1);
    }

    @Override
    public CompletionStage<?> onText(
            WebSocket socket, CharSequence data, boolean last) {
        text.append(data);
        if (last) {
            String message = text.toString();
            text.setLength(0);
            System.out.println("Received text: " + message);
            // Parse JSON or dispatch the complete application message here.
        }
        socket.request(1);
        return null;
    }

    @Override
    public CompletionStage<?> onBinary(
            WebSocket socket, ByteBuffer data, boolean last) {
        System.out.println("Received binary bytes: " + data.remaining());
        // Accumulate data when a complete binary message is required.
        socket.request(1);
        return null;
    }

    @Override
    public CompletionStage<?> onPing(
            WebSocket socket, ByteBuffer message) {
        System.out.println("Received ping");
        socket.request(1);
        return null;
    }

    @Override
    public CompletionStage<?> onPong(
            WebSocket socket, ByteBuffer message) {
        System.out.println("Received pong");
        socket.request(1);
        return null;
    }

    @Override
    public CompletionStage<?> onClose(
            WebSocket socket, int status, String reason) {
        System.out.printf("Closed: %d (%s)%n", status, reason);
        closed.complete(null);
        return null;
    }

    @Override
    public void onError(WebSocket socket, Throwable error) {
        error.printStackTrace();
        closed.completeExceptionally(error);
    }

    public CompletableFuture<Void> closed() {
        return closed;
    }
}

Call request(1) in every callback that consumes an event. Keep CPU-heavy parsing and business work off the callback path, and apply a bounded queue if producers can outpace consumers.

Sending text, binary, and control messages

All send operations are asynchronous and return a CompletableFuture. The boolean argument marks whether the data completes the message; ordinary application messages normally pass true.

socket.sendText("hello", true)
      .thenRun(() -> System.out.println("Client send completed"));

socket.sendBinary(ByteBuffer.wrap(new byte[] {1, 2, 3}), true);
socket.sendPing(ByteBuffer.wrap(new byte[] {9}));
socket.sendPong(ByteBuffer.wrap(new byte[] {8}));
socket.sendClose(WebSocket.NORMAL_CLOSURE, "Application stopping");

Completion of a send future is not an acknowledgment that the remote application processed the message. If your protocol needs request/response behavior, include a correlation ID and define an application-level acknowledgment. WebSocket itself does not provide durable delivery, replay, or exactly-once processing.

Keep the process alive and close it cleanly

A command-line JVM can exit while asynchronous callbacks are pending. Wait for a close future, a service lifecycle, or a latch:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
URI endpoint = URI.create("ws://localhost:8080/chat");
HttpClient client = HttpClient.newHttpClient();
ClientListener listener = new ClientListener();

WebSocket socket = client.newWebSocketBuilder()
        .buildAsync(endpoint, listener)
        .join();

socket.sendText("Hello", true).join();
listener.closed().join();

For orderly shutdown, send a close frame and wait for the operation when appropriate. Handle remote closure in onClose, transport failures in onError, and process shutdown hooks without abruptly terminating messages still queued.

Configure timeouts, headers, and subprotocols

Connection timeout

Set the TCP/handshake connection timeout on the HTTP client or WebSocket builder:

HttpClient client = HttpClient.newBuilder()
        .connectTimeout(Duration.ofSeconds(10))
        .build();

WebSocket socket = client.newWebSocketBuilder()
        .connectTimeout(Duration.ofSeconds(10))
        .buildAsync(URI.create("wss://example.com/socket"), listener)
        .join();

The builder exposes connectTimeout, header, subprotocols, and buildAsync (WebSocket.Builder API). A connection timeout is not a read, idle, server-session, or message-response timeout. Add an application timeout with CompletableFuture.orTimeout where available, or schedule cancellation yourself.

Opening-handshake headers

WebSocket socket = client.newWebSocketBuilder()
        .header("Authorization", "Bearer " + token)
        .header("X-Client-Version", "1.0")
        .buildAsync(URI.create("wss://example.com/socket"), listener)
        .join();

Servers may instead require a cookie or an authentication message after opening. Avoid putting secrets in URI query strings because URLs can appear in logs. Never hard-code production credentials. Protocol-controlled headers cannot be arbitrarily replaced through the normal header method.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Subprotocol negotiation

WebSocket socket = client.newWebSocketBuilder()
        .subprotocols("chat", "json")
        .buildAsync(endpoint, listener)
        .join();

The first value is the preferred protocol and later values are alternatives. The server must select one of the offered protocols. Verify the negotiated protocol when your message format depends on it; a preference is not a guarantee.

Proxy, executor, and TLS settings

Configure a proxy with an HttpClient.Builder proxy selector, and provide a custom executor when you need to isolate WebSocket work from other HTTP operations. For ordinary publicly trusted certificates, wss:// normally works with the default TLS configuration. Private certificate authorities and mutual TLS require an SSLContext on the HttpClient. Fix the trust chain, hostname, certificate validity, or client certificate rather than installing a trust-all TrustManager; disabling validation is unsafe.

Complete configurable example

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.WebSocket;
import java.time.Duration;

public final class SampleWebSocketClient implements WebSocket.Listener {
    private final ClientListener delegate = new ClientListener();

    @Override public void onOpen(WebSocket s) { delegate.onOpen(s); }
    @Override public java.util.concurrent.CompletionStage<?> onText(WebSocket s, CharSequence d, boolean l) { return delegate.onText(s, d, l); }
    @Override public java.util.concurrent.CompletionStage<?> onBinary(WebSocket s, java.nio.ByteBuffer d, boolean l) { return delegate.onBinary(s, d, l); }
    @Override public java.util.concurrent.CompletionStage<?> onPing(WebSocket s, java.nio.ByteBuffer m) { return delegate.onPing(s, m); }
    @Override public java.util.concurrent.CompletionStage<?> onPong(WebSocket s, java.nio.ByteBuffer m) { return delegate.onPong(s, m); }
    @Override public java.util.concurrent.CompletionStage<?> onClose(WebSocket s, int c, String r) { return delegate.onClose(s, c, r); }
    @Override public void onError(WebSocket s, Throwable t) { delegate.onError(s, t); }

    public static void main(String[] args) {
        URI endpoint = URI.create(System.getProperty(
                "websocket.uri", "ws://localhost:8080/chat"));
        HttpClient client = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(10))
                .build();
        SampleWebSocketClient listener = new SampleWebSocketClient();
        WebSocket socket = client.newWebSocketBuilder()
                .connectTimeout(Duration.ofSeconds(10))
                .buildAsync(endpoint, listener)
                .join();
        socket.sendText("{"type":"greeting","text":"Hello"}", true).join();
        listener.delegate.closed().join();
    }
}

Compile directly with:

javac -d out src/main/java/SampleWebSocketClient.java
java -cp out SampleWebSocketClient

Or use Maven with the exec-maven-plugin configured:

mvn compile exec:java 
  -Dexec.mainClass=SampleWebSocketClient 
  -Dwebsocket.uri=ws://localhost:8080/chat

A successful run depends on the endpoint. Typical output includes a connection line, a server-dependent message, and a normal close such as status 1000.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Connection failures and troubleshooting

Handle the connection future explicitly so handshake failures are visible:

client.newWebSocketBuilder()
      .buildAsync(endpoint, listener)
      .whenComplete((socket, error) -> {
          if (error != null) {
              System.err.println("WebSocket connection failed: " + error);
              error.printStackTrace();
          }
      });
Symptom Likely cause Inspect
Invalid URI Wrong scheme or malformed URI Use ws:// or wss:// and verify the path
404, 400, or 426 during connect Wrong route or ordinary HTTP endpoint Server route and handshake logs
401 or 403 Authentication or authorization rejection Token, cookie, permissions, and gateway policy
TLS exception Certificate, hostname, or trust-chain problem Certificate chain and client trust store
Connects but receives nothing Missing demand request or server sends no event Every callback’s request(1) and server behavior
JSON parse errors Fragmented text or wrong application format Buffer until last and check the protocol schema
Program exits immediately Main thread ended Wait on a future, latch, or application lifecycle
Reconnect storm Retries have no backoff Retry delay, jitter, and server limits

A handshake rejection is different from a WebSocket close, an application error message, or a transport interruption. Inspect the HTTP response and server logs before changing message-handling code. Gateways can also remove headers, reject an Origin, or require a particular subprotocol.

Reconnect without creating a retry storm

Use exponential backoff with jitter, a maximum delay, and a retry limit or externally controlled policy:

Duration delay = Duration.ofSeconds(1);
for (int attempt = 1; attempt <= 5; attempt++) {
    try {
        WebSocket socket = client.newWebSocketBuilder()
                .buildAsync(endpoint, listener)
                .join();
        break;
    } catch (RuntimeException failure) {
        Thread.sleep(delay.toMillis());
        delay = Duration.ofSeconds(Math.min(delay.getSeconds() * 2, 30));
    }
}

This sketch omits jitter and classification. Do not retry permanent failures such as an invalid URI or credentials. On reconnect, refresh expired tokens, restore subscriptions and state, and protect against replaying a non-idempotent message whose delivery status is unknown.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Jakarta WebSocket and Jetty alternatives

Jakarta WebSocket

Choose Jakarta WebSocket when your application already uses Jakarta EE or needs its endpoint/container model. Annotated endpoints use @ClientEndpoint, @OnOpen, @OnMessage, and @OnClose:

import jakarta.websocket.ClientEndpoint;
import jakarta.websocket.OnClose;
import jakarta.websocket.OnMessage;
import jakarta.websocket.OnOpen;
import jakarta.websocket.Session;

@ClientEndpoint
public class JakartaClientEndpoint {
    @OnOpen
    public void open(Session session) {
        session.getAsyncRemote().sendText("Hello");
    }

    @OnMessage
    public void message(String value) {
        System.out.println(value);
    }

    @OnClose
    public void close(jakarta.websocket.CloseReason reason) {
        System.out.println(reason);
    }
}

The Jakarta API is a specification and API, not a complete standalone runtime. Add an implementation compatible with your deployment. The jakarta.* namespace is not interchangeable with old javax.websocket.* examples. See the Jakarta tutorial, Jakarta WebSocket project, and the API artifact listing at Maven Central.

Jetty WebSocket Client

Jetty is a sensible choice when the application already uses Jetty, needs its lifecycle and HTTP integration, or requires Jetty-specific HTTP/2 options. Jetty’s client connection model uses WebSocketClient.connect(...) and returns a future containing a Jetty session. Start and stop the client with the application lifecycle. Keep Jetty major and minor versions aligned with the project’s dependency management; consult the Jetty client guide and Jetty server/API guide. The client artifact is listed at Maven Central.

Approach Best fit Advantages Trade-offs
JDK WebSocket General Java 11+ applications No extra dependency; standard asynchronous API Application protocols, reconnects, and lifecycle remain your responsibility
Jakarta WebSocket Jakarta EE applications Endpoint annotations and container integration API alone is not a runtime; namespace compatibility matters
Jetty client Jetty-based systems Jetty lifecycle and HTTP integration More dependencies and version alignment
OkHttp WebSocket Applications already using OkHttp Convenient within an existing OkHttp stack Verify current coordinates and versions; avoid a duplicate HTTP stack

Security and operational checklist

  • Use wss:// in production and validate certificates and hostnames.
  • Do not log bearer tokens, cookies, or sensitive message payloads.
  • Limit message sizes and parse untrusted input defensively.
  • Move expensive processing away from callbacks and bound internal queues.
  • Set reconnect limits, backoff, and jitter.
  • Restore authentication, subscriptions, and state after reconnecting.
  • Test against a local or controlled integration server rather than depending on an unverified public echo service.

The Bottom Line

Start with Java 11’s standard java.net.http.WebSocket API unless your project already depends on Jakarta EE or Jetty. Correct demand handling, message fragmentation, lifecycle waits, and explicit authentication/TLS configuration are what turn a connecting example into a reliable client.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. Shenzhen desk3 min
    HONOR Expands Beyond Smartphones With Humanoid Robot RevealHONOR said it unveiled its first humanoid robot at MWC 2026 and named shopping assistance, workplace inspections, and supportive companionship as intended uses. Later Robotics D1 claims and a reported…
  2. Cupertino desk5 min
    Apple Unveils AirPods Max 2: The Upgrade That Should Have Happened Years AgoAirPods Max 2 adds H2-powered audio features and Apple claims up to 1.5× more effective ANC, but its design, Smart Case, and 20-hour battery rating are unchanged. Wired lossless audio…
  3. Cupertino desk4 min
    Apple’s OLED Touch MacBooks Are Coming—but the Dynamic Island Is the Real GambleApple has not announced an OLED touchscreen MacBook, but reports point to high-end models arriving in late 2026 or early 2027. The reported Mac Dynamic Island could be useful, but…
Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.