Mastering WebSocket Integration in Spring Boot: Javax, WebMVC, WebFlux, and More
This article walks through the author's practical research on integrating WebSocket in Spring Boot, detailing step‑by‑step configurations for Javax, WebMVC, and WebFlux, comparing their APIs, highlighting pitfalls, and providing concrete code snippets and cold‑knowledge tips for both server and client implementations.
Javax WebSocket (JSR‑356)
Define a server endpoint with @ServerEndpoint. The path can contain variables, e.g. "/websocket/{type}". Lifecycle callbacks are mapped with annotations: @OnOpen – receives Session and optional @PathParam values. @OnClose – receives CloseReason. @OnMessage – overloads for String, ByteBuffer (or byte[]) and PongMessage. @OnError – receives the exception.
@Component
@ServerEndpoint("/websocket/{type}")
public class JavaxWebSocketServerEndpoint {
@OnOpen
public void onOpen(Session session, EndpointConfig config, @PathParam("type") String type) {}
@OnClose
public void onClose(Session session, CloseReason reason) {}
@OnMessage
public void onMessage(Session session, String message) {}
@OnMessage
public void onMessage(Session session, PongMessage message) {}
@OnMessage
public void onMessage(Session session, ByteBuffer message) {}
@OnError
public void onError(Session session, Throwable e) {}
}Add the Spring starter and expose a ServerEndpointExporter bean to enable the native API:
implementation 'org.springframework.boot:spring-boot-starter-websocket'
@Configuration(proxyBeanMethods = false)
public class JavaxWebSocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}Ping/Pong note : javax.websocket defines PongMessage but not PingMessage. Most implementations automatically reply to a ping with a pong, so explicit handling of ping is rarely needed. The API to send them is:
// send ping
session.getAsyncRemote().sendPing(ByteBuffer.allocate(0));
// send pong
session.getAsyncRemote().sendPong(ByteBuffer.allocate(0));Javax client
@ClientEndpoint
public class JavaxWebSocketClientEndpoint {
@OnOpen
public void onOpen(Session session) {}
@OnClose
public void onClose(Session session, CloseReason reason) {}
@OnMessage
public void onMessage(Session session, String message) {}
@OnMessage
public void onMessage(Session session, PongMessage message) {}
@OnMessage
public void onMessage(Session session, ByteBuffer message) {}
@OnError
public void onError(Session session, Throwable e) {}
}Connect using the SPI‑based container:
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
Session session = container.connectToServer(JavaxWebSocketClientEndpoint.class, uri);In a Spring environment the container can be obtained via ServletContextAware to reuse the server‑provided container:
@Component
public class JavaxWebSocketContainer implements ServletContextAware {
private volatile WebSocketContainer container;
public WebSocketContainer getContainer() {
if (container == null) {
synchronized (this) {
if (container == null) {
container = ContainerProvider.getWebSocketContainer();
}
}
}
return container;
}
@Override
public void setServletContext(ServletContext servletContext) {
if (container == null) {
container = (WebSocketContainer) servletContext.getAttribute(
"javax.websocket.server.ServerContainer");
}
}
}Sending messages (Javax)
session.getAsyncRemote().sendText("text message");
session.getAsyncRemote().sendBinary(ByteBuffer.wrap(...));
session.getAsyncRemote().sendObject(someObject);
session.getAsyncRemote().sendPing(ByteBuffer.allocate(0));
session.getAsyncRemote().sendPong(ByteBuffer.allocate(0));WebMVC (Spring’s servlet‑based WebSocket)
Implement WebSocketHandler to process the connection lifecycle:
public class ServletWebSocketServerHandler implements WebSocketHandler {
@Override
public void afterConnectionEstablished(@NonNull WebSocketSession session) throws Exception {}
@Override
public void handleMessage(@NonNull WebSocketSession session, @NonNull WebSocketMessage<?> message) throws Exception {}
@Override
public void handleTransportError(@NonNull WebSocketSession session, @NonNull Throwable exception) throws Exception {}
@Override
public void afterConnectionClosed(@NonNull WebSocketSession session, @NonNull CloseStatus closeStatus) throws Exception {}
@Override
public boolean supportsPartialMessages() { return false; }
}Register the handler with @EnableWebSocket:
@Configuration
@EnableWebSocket
public class ServletWebSocketServerConfigurer implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(@NonNull WebSocketHandlerRegistry registry) {
registry.addHandler(new ServletWebSocketServerHandler(), "/websocket")
.setAllowedOrigins("*");
}
}Handshake interceptor
@Configuration
@EnableWebSocket
public class ServletWebSocketServerConfigurer implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(@NonNull WebSocketHandlerRegistry registry) {
registry.addHandler(new ServletWebSocketServerHandler(), "/websocket")
.addInterceptors(new HandshakeInterceptor() {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Map<String, Object> attributes) {
// return true to continue, false to abort
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Exception exception) {}
})
.setAllowedOrigins("*");
}
}Path‑pattern limitation : The internal map is Map<String, WebSocketHandler>, which does not support Ant‑style patterns such as /websocket/**. A workaround replaces the UrlPathHelper:
@Configuration
@EnableWebSocket
public class ServletWebSocketServerConfigurer implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(@NonNull WebSocketHandlerRegistry registry) {
if (registry instanceof ServletWebSocketHandlerRegistry) {
((ServletWebSocketHandlerRegistry) registry)
.setUrlPathHelper(new PrefixUrlPathHelper("/websocket"));
}
registry.addHandler(new ServletWebSocketServerHandler(), "/websocket/**")
.setAllowedOrigins("*");
}
public class PrefixUrlPathHelper extends UrlPathHelper {
private final String prefix = "/websocket";
@Override
public String resolveAndCacheLookupPath(HttpServletRequest request) {
String path = super.resolveAndCacheLookupPath(request);
return path.startsWith(prefix) ? prefix + "/**" : path;
}
}
}WebMVC client
WebSocketClient client = new StandardWebSocketClient();
WebSocketHandler handler = new ServletWebSocketClientHandler();
WebSocketConnectionManager manager = new WebSocketConnectionManager(client, handler, uri);
manager.start();Other client implementations ( JettyWebSocketClient, UndertowWebSocketClient, etc.) can be chosen based on the runtime container.
WebFlux (Reactive WebSocket)
Reactive handler works with Mono and Flux streams.
public class ReactiveWebSocketServerHandler implements WebSocketHandler {
@Override
public Mono<Void> handle(WebSocketSession session) {
Mono<Void> send = session.send(Flux.create(sink -> {
// keep sink to emit later
sink.next(session.textMessage("welcome"));
})).doOnError(e -> {/* error handling */});
Mono<Void> receive = session.receive()
.doOnNext(msg -> {/* process msg */})
.doOnError(e -> {/* error handling */})
.then();
session.closeStatus()
.doOnError(e -> {/* error handling */})
.subscribe(status -> {/* connection closed */});
return Mono.zip(send, receive).then();
}
}Handler mapping
@Component
public class ReactiveWebSocketServerHandlerMapping extends SimpleUrlHandlerMapping {
public ReactiveWebSocketServerHandlerMapping() {
Map<String, WebSocketHandler> map = new HashMap<>();
map.put("/websocket/**", new ReactiveWebSocketServerHandler());
setUrlMap(map);
setOrder(100); // must be higher than LOWEST_PRECEDENCE
}
}Configuration bean
@Configuration(proxyBeanMethods = false)
public class ReactiveWebSocketConfiguration {
@Bean
public WebSocketHandlerAdapter webSocketHandlerAdapter() {
return new WebSocketHandlerAdapter();
}
}Reactive client
public class ReactiveWebSocketClientHandler implements WebSocketHandler {
@Override
public Mono<Void> handle(WebSocketSession session) {
Mono<Void> send = session.send(Flux.create(sink -> {
sink.next(session.textMessage("hello"));
})).doOnError(e -> {/* error handling */});
Mono<Void> receive = session.receive()
.doOnNext(msg -> {/* process */})
.doOnError(e -> {/* error handling */})
.then();
return Mono.zip(send, receive).then();
}
}
WebSocketClient client = new ReactorNettyWebSocketClient();
client.execute(uri, new ReactiveWebSocketClientHandler()).subscribe();Sending messages (Reactive)
public class ReactiveWebSocket {
private final WebSocketSession session;
private final FluxSink<WebSocketMessage> sender;
public void send(Object message) {
if (message instanceof WebSocketMessage) {
sender.next((WebSocketMessage) message);
} else if (message instanceof String) {
sender.next(session.textMessage((String) message));
} else if (message instanceof DataBuffer) {
sender.next(session.binaryMessage(factory -> (DataBuffer) message));
} else if (message instanceof ByteBuffer) {
sender.next(session.binaryMessage(factory -> factory.wrap((ByteBuffer) message)));
} else if (message instanceof byte[]) {
sender.next(session.binaryMessage(factory -> factory.wrap((byte[]) message)));
} else {
throw new IllegalArgumentException("Message type not match");
}
}
public void ping() {
sender.next(session.pingMessage(factory -> factory.wrap(ByteBuffer.allocate(0))));
}
public void pong() {
sender.next(session.pongMessage(factory -> factory.wrap(ByteBuffer.allocate(0))));
}
public void close(CloseStatus reason) {
sender.complete();
session.close(reason).subscribe();
}
}Other WebSocket libraries
Java‑WebSocket – pure‑Java implementation with >9 k stars.
https://github.com/TooTallNate/Java-WebSocket
Socket.IO – protocol wrapper offering multi‑language clients/servers for real‑time chat.
https://github.com/socketio
Netty – low‑level networking framework; extensive documentation and examples exist. No specific Spring Boot integration shown here.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect
Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
