Client-side WebSocket connection management and implementations. Supports initiating WebSocket connections, managing connection lifecycle, and provides JSR-356 standard client implementations.
Contract for initiating WebSocket connections from the client side.
/**
* Contract for establishing WebSocket connections to a server.
* Implementations handle the client-side handshake and connection setup.
*/
public interface WebSocketClient {
/**
* Execute a WebSocket connection using a URI template.
*
* @param webSocketHandler the handler for the connection
* @param uriTemplate the URI template (e.g., "ws://localhost:8080/ws")
* @param uriVariables variables to expand in the URI template
* @return a CompletableFuture that completes when connection is established
*/
CompletableFuture<WebSocketSession> execute(
WebSocketHandler webSocketHandler,
String uriTemplate,
Object... uriVariables);
/**
* Execute a WebSocket connection with headers.
*
* @param webSocketHandler the handler for the connection
* @param headers HTTP headers for the handshake
* @param uri the WebSocket URI
* @return a CompletableFuture that completes when connection is established
*/
CompletableFuture<WebSocketSession> execute(
WebSocketHandler webSocketHandler,
WebSocketHttpHeaders headers,
URI uri);
}Manages WebSocket connection lifecycle with automatic start/stop support.
/**
* WebSocket connection manager that connects on start and disconnects on stop.
* Integrates with Spring's SmartLifecycle for automatic connection management.
* Useful for long-lived connections that should be established when the
* application starts.
*/
public class WebSocketConnectionManager extends ConnectionManagerSupport {
/**
* Create a connection manager with URI template.
*
* @param client the WebSocket client
* @param webSocketHandler the WebSocket handler
* @param uriTemplate the URI template
* @param uriVariables variables for the URI template
*/
public WebSocketConnectionManager(
WebSocketClient client,
WebSocketHandler webSocketHandler,
String uriTemplate,
Object... uriVariables);
/**
* Create a connection manager with URI.
*
* @param client the WebSocket client
* @param webSocketHandler the WebSocket handler
* @param uri the WebSocket URI
*/
public WebSocketConnectionManager(
WebSocketClient client,
WebSocketHandler webSocketHandler,
URI uri);
/**
* Set the sub-protocols to request during handshake.
*
* @param protocols the sub-protocols to request
*/
public void setSubProtocols(List<String> protocols);
/**
* Get the configured sub-protocols.
*
* @return the sub-protocols
*/
public List<String> getSubProtocols();
/**
* Set the Origin header value for the handshake.
*
* @param origin the origin value
*/
public void setOrigin(String origin);
/**
* Get the configured Origin header value.
*
* @return the origin
*/
public String getOrigin();
/**
* Set additional HTTP headers for the handshake.
*
* @param headers the HTTP headers
*/
public void setHeaders(HttpHeaders headers);
/**
* Get the configured HTTP headers.
*
* @return the headers
*/
public HttpHeaders getHeaders();
/**
* Open a new connection. Called automatically on start if autoStartup is true.
*/
@Override
protected void openConnection();
/**
* Close the connection. Called automatically on stop.
*/
@Override
protected void closeConnection() throws Exception;
/**
* Whether the connection is currently open.
*
* @return true if connected
*/
@Override
public boolean isConnected();
}Base class for connection managers with lifecycle support.
/**
* Abstract base class for connection managers. Implements SmartLifecycle
* for integration with Spring's container lifecycle.
*/
public abstract class ConnectionManagerSupport implements SmartLifecycle {
/**
* Set whether to automatically connect on startup.
* Default is true.
*
* @param autoStartup whether to auto-connect
*/
public void setAutoStartup(boolean autoStartup);
/**
* Whether auto-startup is enabled.
*
* @return true if auto-startup is enabled
*/
@Override
public boolean isAutoStartup();
/**
* Set the lifecycle phase. Lower values start earlier.
* Default is Integer.MAX_VALUE.
*
* @param phase the phase value
*/
public void setPhase(int phase);
/**
* Get the lifecycle phase.
*
* @return the phase value
*/
@Override
public int getPhase();
/**
* Start the connection manager (establish connection).
*/
@Override
public void start();
/**
* Stop the connection manager (close connection).
*/
@Override
public void stop();
/**
* Stop with a callback.
*/
@Override
public void stop(Runnable callback);
/**
* Whether the connection manager is running.
*
* @return true if running
*/
@Override
public boolean isRunning();
/**
* Open a new connection. Subclasses implement the actual connection logic.
*/
protected abstract void openConnection();
/**
* Close the connection. Subclasses implement the actual close logic.
*/
protected abstract void closeConnection() throws Exception;
/**
* Whether the connection is currently established.
*
* @return true if connected
*/
public abstract boolean isConnected();
}Base implementation of WebSocketClient with common client logic.
/**
* Abstract base class for WebSocketClient implementations.
* Provides common connection logic and task executor support.
*/
public abstract class AbstractWebSocketClient implements WebSocketClient {
/**
* Create a client with default configuration.
*/
public AbstractWebSocketClient();
/**
* Set the task executor for handling connection callbacks.
* If not set, callbacks are executed on the I/O thread.
*
* @param taskExecutor the task executor
*/
public void setTaskExecutor(AsyncTaskExecutor taskExecutor);
/**
* Get the configured task executor.
*
* @return the task executor, or null if not set
*/
public AsyncTaskExecutor getTaskExecutor();
@Override
public CompletableFuture<WebSocketSession> execute(
WebSocketHandler webSocketHandler,
String uriTemplate,
Object... uriVariables);
@Override
public CompletableFuture<WebSocketSession> execute(
WebSocketHandler webSocketHandler,
WebSocketHttpHeaders headers,
URI uri);
/**
* Perform the actual connection. Subclasses implement this method
* with client-specific connection logic.
*
* @param webSocketHandler the WebSocket handler
* @param headers the handshake headers
* @param uri the WebSocket URI
* @return a CompletableFuture for the connection
*/
protected abstract CompletableFuture<WebSocketSession> doHandshakeInternal(
WebSocketHandler webSocketHandler,
WebSocketHttpHeaders headers,
URI uri);
}JSR-356 (Jakarta WebSocket) client implementation.
/**
* WebSocketClient implementation using the standard JSR-356 Java WebSocket API.
* Works with any JSR-356 compliant WebSocket client implementation.
*/
public class StandardWebSocketClient extends AbstractWebSocketClient {
/**
* Create a client using the default WebSocketContainer.
*/
public StandardWebSocketClient();
/**
* Create a client using a specific WebSocketContainer.
*
* @param webSocketContainer the container to use
*/
public StandardWebSocketClient(WebSocketContainer webSocketContainer);
/**
* Set user properties to pass to the WebSocketContainer.
*
* @param userProperties the user properties
*/
public void setUserProperties(Map<String, Object> userProperties);
/**
* Get the configured user properties.
*
* @return the user properties
*/
public Map<String, Object> getUserProperties();
@Override
protected CompletableFuture<WebSocketSession> doHandshakeInternal(
WebSocketHandler webSocketHandler,
WebSocketHttpHeaders headers,
URI uri);
}FactoryBean for creating and configuring a WebSocketContainer.
/**
* FactoryBean that creates a JSR-356 WebSocketContainer with configurable
* properties. Useful for customizing the WebSocket client container.
*/
public class WebSocketContainerFactoryBean
implements FactoryBean<WebSocketContainer>, InitializingBean {
/**
* Create a factory bean with default configuration.
*/
public WebSocketContainerFactoryBean();
/**
* Set the maximum text message buffer size in bytes.
* Default is typically 8192 (8KB).
*
* @param bufferSize the buffer size in bytes
*/
public void setMaxTextMessageBufferSize(int bufferSize);
/**
* Set the maximum binary message buffer size in bytes.
* Default is typically 8192 (8KB).
*
* @param bufferSize the buffer size in bytes
*/
public void setMaxBinaryMessageBufferSize(int bufferSize);
/**
* Set the maximum idle timeout in milliseconds.
* A timeout of zero or negative means no timeout.
*
* @param timeout the idle timeout in milliseconds
*/
public void setMaxSessionIdleTimeout(long timeout);
/**
* Set the timeout in milliseconds for asynchronous send operations.
*
* @param timeout the send timeout in milliseconds
*/
public void setAsyncSendTimeout(long timeout);
@Override
public WebSocketContainer getObject() throws Exception;
@Override
public Class<?> getObjectType();
@Override
public boolean isSingleton();
}Connection manager for JSR-356 Endpoint connections.
/**
* WebSocket connection manager that connects using a JSR-356 Endpoint.
* Provides programmatic endpoint connection with Spring lifecycle support.
*/
public class EndpointConnectionManager extends ConnectionManagerSupport {
/**
* Create a connection manager for an Endpoint.
*
* @param endpointClass the Endpoint class
* @param uriTemplate the URI template
* @param uriVars variables for the URI template
*/
public EndpointConnectionManager(
Class<? extends Endpoint> endpointClass,
String uriTemplate,
Object... uriVars);
/**
* Create a connection manager with a WebSocketContainer.
*
* @param container the WebSocket container
* @param endpointClass the Endpoint class
* @param uriTemplate the URI template
* @param uriVars variables for the URI template
*/
public EndpointConnectionManager(
WebSocketContainer container,
Class<? extends Endpoint> endpointClass,
String uriTemplate,
Object... uriVars);
/**
* Create a connection manager with an Endpoint supplier.
*
* @param container the WebSocket container
* @param endpointSupplier supplier for Endpoint instances
* @param uriTemplate the URI template
* @param uriVars variables for the URI template
*/
public EndpointConnectionManager(
WebSocketContainer container,
Supplier<Endpoint> endpointSupplier,
String uriTemplate,
Object... uriVars);
@Override
protected void openConnection();
@Override
protected void closeConnection() throws Exception;
@Override
public boolean isConnected();
}Connection manager for @ClientEndpoint annotated classes.
/**
* Connection manager for JSR-356 @ClientEndpoint annotated classes.
* Enables using Spring-managed beans as WebSocket client endpoints.
*/
public class AnnotatedEndpointConnectionManager extends ConnectionManagerSupport {
/**
* Create a connection manager for an annotated endpoint.
*
* @param endpoint the @ClientEndpoint annotated object
* @param uriTemplate the URI template
* @param uriVars variables for the URI template
*/
public AnnotatedEndpointConnectionManager(
Object endpoint,
String uriTemplate,
Object... uriVars);
/**
* Create a connection manager with a WebSocketContainer.
*
* @param container the WebSocket container
* @param endpoint the @ClientEndpoint annotated object
* @param uriTemplate the URI template
* @param uriVars variables for the URI template
*/
public AnnotatedEndpointConnectionManager(
WebSocketContainer container,
Object endpoint,
String uriTemplate,
Object... uriVars);
@Override
protected void openConnection();
@Override
protected void closeConnection() throws Exception;
@Override
public boolean isConnected();
}import org.springframework.web.socket.client.standard.StandardWebSocketClient;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
public class WebSocketClientExample {
public static void main(String[] args) throws Exception {
WebSocketClient client = new StandardWebSocketClient();
WebSocketHandler handler = new TextWebSocketHandler() {
@Override
public void afterConnectionEstablished(WebSocketSession session)
throws Exception {
System.out.println("Connected to server");
session.sendMessage(new TextMessage("Hello Server!"));
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message)
throws Exception {
System.out.println("Received: " + message.getPayload());
}
};
CompletableFuture<WebSocketSession> future = client.execute(
handler,
"ws://localhost:8080/ws"
);
// Wait for connection
WebSocketSession session = future.get();
// Keep connection open
Thread.sleep(10000);
// Close connection
session.close();
}
}import org.springframework.web.socket.client.standard.StandardWebSocketClient;
import org.springframework.web.socket.WebSocketHttpHeaders;
WebSocketClient client = new StandardWebSocketClient();
WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
headers.add("Authorization", "Bearer token123");
headers.add("X-Client-Version", "1.0");
URI uri = new URI("ws://localhost:8080/ws");
CompletableFuture<WebSocketSession> future = client.execute(
myHandler,
headers,
uri
);
future.thenAccept(session -> {
System.out.println("Connected with custom headers");
}).exceptionally(ex -> {
System.err.println("Connection failed: " + ex.getMessage());
return null;
});import org.springframework.web.socket.client.WebSocketConnectionManager;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
@Configuration
public class WebSocketClientConfig {
@Bean
public WebSocketConnectionManager connectionManager() {
WebSocketClient client = new StandardWebSocketClient();
WebSocketHandler handler = new MyClientHandler();
WebSocketConnectionManager manager = new WebSocketConnectionManager(
client,
handler,
"ws://localhost:8080/ws"
);
// Configure connection manager
manager.setAutoStartup(true); // Connect on startup
manager.setHeaders(customHeaders());
return manager;
}
private HttpHeaders customHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.add("X-Client-ID", "client-123");
return headers;
}
}
// Handler implementation
class MyClientHandler extends TextWebSocketHandler {
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
System.out.println("Connected to server");
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message)
throws Exception {
System.out.println("Received: " + message.getPayload());
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status)
throws Exception {
System.out.println("Disconnected: " + status);
}
}import org.springframework.web.socket.client.standard.WebSocketContainerFactoryBean;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
import jakarta.websocket.WebSocketContainer;
@Configuration
public class WebSocketClientConfig {
@Bean
public WebSocketContainerFactoryBean webSocketContainer() {
WebSocketContainerFactoryBean factory = new WebSocketContainerFactoryBean();
// Configure buffer sizes
factory.setMaxTextMessageBufferSize(16 * 1024); // 16KB
factory.setMaxBinaryMessageBufferSize(16 * 1024); // 16KB
// Configure timeouts
factory.setMaxSessionIdleTimeout(60000); // 60 seconds
factory.setAsyncSendTimeout(10000); // 10 seconds
return factory;
}
@Bean
public StandardWebSocketClient webSocketClient(WebSocketContainer container) {
return new StandardWebSocketClient(container);
}
}import org.springframework.web.socket.client.WebSocketConnectionManager;
@Bean
public WebSocketConnectionManager stompConnectionManager() {
WebSocketClient client = new StandardWebSocketClient();
WebSocketHandler handler = new StompClientHandler();
WebSocketConnectionManager manager = new WebSocketConnectionManager(
client,
handler,
"ws://localhost:8080/stomp"
);
// Request STOMP sub-protocol
manager.setSubProtocols(Arrays.asList("v12.stomp", "v11.stomp"));
return manager;
}import org.springframework.web.socket.client.standard.StandardWebSocketClient;
public class RobustWebSocketClient {
public void connect() {
StandardWebSocketClient client = new StandardWebSocketClient();
WebSocketHandler handler = new TextWebSocketHandler() {
@Override
public void afterConnectionEstablished(WebSocketSession session)
throws Exception {
System.out.println("Connected successfully");
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception)
throws Exception {
System.err.println("Transport error: " + exception.getMessage());
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status)
throws Exception {
System.out.println("Connection closed: " + status);
// Implement reconnection logic here
scheduleReconnect();
}
};
client.execute(handler, "ws://localhost:8080/ws")
.thenAccept(session -> {
System.out.println("Connection established");
})
.exceptionally(ex -> {
System.err.println("Failed to connect: " + ex.getMessage());
scheduleReconnect();
return null;
});
}
private void scheduleReconnect() {
// Schedule reconnection attempt
new Timer().schedule(new TimerTask() {
@Override
public void run() {
connect();
}
}, 5000); // Retry after 5 seconds
}
}import jakarta.websocket.*;
import org.springframework.web.socket.client.standard.AnnotatedEndpointConnectionManager;
@ClientEndpoint
public class MyClientEndpoint {
@OnOpen
public void onOpen(Session session) {
System.out.println("Connected: " + session.getId());
try {
session.getBasicRemote().sendText("Hello from client");
} catch (IOException e) {
e.printStackTrace();
}
}
@OnMessage
public void onMessage(String message) {
System.out.println("Received: " + message);
}
@OnClose
public void onClose(Session session, CloseReason closeReason) {
System.out.println("Closed: " + closeReason);
}
@OnError
public void onError(Throwable error) {
System.err.println("Error: " + error.getMessage());
}
}
@Configuration
public class ClientConfig {
@Bean
public AnnotatedEndpointConnectionManager endpointManager() {
MyClientEndpoint endpoint = new MyClientEndpoint();
return new AnnotatedEndpointConnectionManager(
endpoint,
"ws://localhost:8080/ws"
);
}
}import java.util.concurrent.CompletableFuture;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
public class AsyncWebSocketClient {
public CompletableFuture<String> sendAndReceive(String message) {
CompletableFuture<String> responseFuture = new CompletableFuture<>();
StandardWebSocketClient client = new StandardWebSocketClient();
WebSocketHandler handler = new TextWebSocketHandler() {
private WebSocketSession currentSession;
@Override
public void afterConnectionEstablished(WebSocketSession session)
throws Exception {
this.currentSession = session;
session.sendMessage(new TextMessage(message));
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage msg)
throws Exception {
// Complete future with response
responseFuture.complete(msg.getPayload());
session.close();
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception)
throws Exception {
responseFuture.completeExceptionally(exception);
}
};
client.execute(handler, "ws://localhost:8080/ws")
.exceptionally(ex -> {
responseFuture.completeExceptionally(ex);
return null;
});
return responseFuture;
}
public static void main(String[] args) throws Exception {
AsyncWebSocketClient client = new AsyncWebSocketClient();
client.sendAndReceive("Hello")
.thenAccept(response -> System.out.println("Got response: " + response))
.exceptionally(ex -> {
System.err.println("Error: " + ex.getMessage());
return null;
});
}
}import java.util.concurrent.ConcurrentHashMap;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
public class WebSocketConnectionPool {
private final StandardWebSocketClient client = new StandardWebSocketClient();
private final Map<String, WebSocketSession> connections = new ConcurrentHashMap<>();
public CompletableFuture<WebSocketSession> getConnection(String uri) {
WebSocketSession existing = connections.get(uri);
if (existing != null && existing.isOpen()) {
return CompletableFuture.completedFuture(existing);
}
// Create new connection
return client.execute(createHandler(uri), uri)
.thenApply(session -> {
connections.put(uri, session);
return session;
});
}
private WebSocketHandler createHandler(String uri) {
return new TextWebSocketHandler() {
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status)
throws Exception {
connections.remove(uri);
}
};
}
public void closeAll() {
connections.values().forEach(session -> {
try {
if (session.isOpen()) {
session.close();
}
} catch (IOException e) {
e.printStackTrace();
}
});
connections.clear();
}
}