Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallFor 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.
| # | Preview | Product | Price | |
|---|---|---|---|---|
| 1 |
|
Masterkurs Client/Server-Programmierung mit Java: Anwendungen entwickeln mit Standard-Technologien... | $39.99 | Buy on Amazon |
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).
#1 Best Overall
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.
Recommended Free Tools
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:
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.
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.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →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.
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.
Quick Recap
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.




