Backend Development 20 min read

Integrating WebSocket in Spring Boot: Javax, WebMVC, and WebFlux Implementations

This article reviews several ways to integrate WebSocket with Spring Boot, focusing on the three mainstream approaches—Javax, WebMVC, and WebFlux—by providing server and client configurations, code examples, and practical tips such as handling ping/pong, handshake interceptors, and reactive streams.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Integrating WebSocket in Spring Boot: Javax, WebMVC, and WebFlux Implementations

After recently implementing a Spring Cloud library that uses a configuration annotation for a WebSocket clustering solution, I investigated the many ways WebSocket can be integrated with Spring Boot. The approaches I found are Javax, WebMVC, WebFlux, Java‑WebSocket, SocketIO, and Netty. This article concentrates on the first three, which are the most common in Spring Boot projects.

Javax

The javax.websocket package defines a standard set of WebSocket interfaces.

Server

Step 1

@Component
@ServerEndpoint("/websocket/{type}")
public class JavaxWebSocketServerEndpoint {
    @OnOpen
    public void onOpen(Session session, EndpointConfig config, @PathParam("type") String type) {
        // connection established
    }
    @OnClose
    public void onClose(Session session, CloseReason reason) {
        // connection closed
    }
    @OnMessage
    public void onMessage(Session session, String message) {
        // receive text message
    }
    @OnMessage
    public void onMessage(Session session, PongMessage message) {
        // receive pong message
    }
    @OnMessage
    public void onMessage(Session session, ByteBuffer message) {
        // receive binary message
    }
    @OnError
    public void onError(Session session, Throwable e) {
        // error handling
    }
}

The @ServerEndpoint annotation marks the class as a WebSocket endpoint and allows a dynamic path using {} . The lifecycle annotations ( @OnOpen , @OnClose , @OnMessage , @OnError ) work exactly as their names suggest.

Step 2

implementation 'org.springframework.boot:spring-boot-starter-websocket'
@Configuration(proxyBeanMethods = false)
public class JavaxWebSocketConfiguration {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

Adding the Spring WebSocket starter and manually exposing a ServerEndpointExporter enables the native javax.websocket usage within Spring.

Cold Knowledge

The library defines PongMessage but not PingMessage .

Most browsers automatically reply to a ping with a pong, so you rarely need to handle ping yourself.

Custom heartbeat data sent via Message will not be auto‑handled; you must use the API directly:

// send ping
session.getAsyncRemote().sendPing(ByteBuffer buffer);
// send pong
session.getAsyncRemote().sendPong(ByteBuffer buffer);

Client

Step 1

@ClientEndpoint
public class JavaxWebSocketClientEndpoint {
    @OnOpen
    public void onOpen(Session session) {
        // connection established
    }
    @OnClose
    public void onClose(Session session, CloseReason reason) {
        // connection closed
    }
    @OnMessage
    public void onMessage(Session session, String message) {
        // receive text message
    }
    @OnMessage
    public void onMessage(Session session, PongMessage message) {
        // receive pong message
    }
    @OnMessage
    public void onMessage(Session session, ByteBuffer message) {
        // receive binary message
    }
    @OnError
    public void onError(Session session, Throwable e) {
        // error handling
    }
}

The client uses @ClientEndpoint and the same lifecycle annotations as the server.

Step 2

WebSocketContainer container = ContainerProvider.getWebSocketContainer();
Session session = container.connectToServer(JavaxWebSocketClientEndpoint.class, uri);

In a Spring environment you can obtain the container via ServletContextAware :

@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(@NonNull ServletContext servletContext) {
        if (container == null) {
            container = (WebSocketContainer) servletContext.getAttribute("javax.websocket.server.ServerContainer");
        }
    }
}

Sending Messages

Session session = ...
// text
session.getAsyncRemote().sendText("message");
// binary
session.getAsyncRemote().sendBinary(ByteBuffer buffer);
// object (uses Encoder)
session.getAsyncRemote().sendObject(obj);
// ping
session.getAsyncRemote().sendPing(ByteBuffer buffer);
// pong
session.getAsyncRemote().sendPong(ByteBuffer buffer);

WebMVC

Dependency:

implementation 'org.springframework.boot:spring-boot-starter-websocket'

Server

Step 1

public class ServletWebSocketServerHandler implements WebSocketHandler {
    @Override
    public void afterConnectionEstablished(@NonNull WebSocketSession session) throws Exception {
        // connection established
    }
    @Override
    public void handleMessage(@NonNull WebSocketSession session, @NonNull WebSocketMessage
message) throws Exception {
        // receive message
    }
    @Override
    public void handleTransportError(@NonNull WebSocketSession session, @NonNull Throwable exception) throws Exception {
        // error handling
    }
    @Override
    public void afterConnectionClosed(@NonNull WebSocketSession session, @NonNull CloseStatus closeStatus) throws Exception {
        // connection closed
    }
    @Override
    public boolean supportsPartialMessages() {
        return false;
    }
}

Step 2

@Configuration
@EnableWebSocket
public class ServletWebSocketServerConfigurer implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(@NonNull WebSocketHandlerRegistry registry) {
        registry.addHandler(new ServletWebSocketServerHandler(), "/websocket")
                .setAllowedOrigins("*");
    }
}

Enabling @EnableWebSocket and registering a handler maps the endpoint to /websocket .

Handshake Interceptor

@Configuration
@EnableWebSocket
public class ServletWebSocketServerConfigurer implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(@NonNull WebSocketHandlerRegistry registry) {
        registry.addHandler(new ServletWebSocketServerHandler(), "/websocket")
                .addInterceptors(new ServletWebSocketHandshakeInterceptor())
                .setAllowedOrigins("*");
    }
    public static class ServletWebSocketHandshakeInterceptor implements HandshakeInterceptor {
        @Override
        public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map
attributes) throws Exception {
            // before handshake
            return false; // return true to continue
        }
        @Override
        public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
            // after handshake
        }
    }
}

Cold Knowledge

The default handler registry stores handlers in a Map<String, WebSocketHandler> , which does not support Ant‑style path patterns. You can replace the internal UrlPathHelper to achieve /websocket/** matching:

@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 String prefix;
        @Override
        public @NonNull String resolveAndCacheLookupPath(@NonNull HttpServletRequest request) {
            String path = super.resolveAndCacheLookupPath(request);
            if (path.startsWith(prefix)) {
                return prefix + "/**";
            }
            return path;
        }
    }
}

Client

Step 1

public class ServletWebSocketClientHandler implements WebSocketHandler {
    @Override
    public void afterConnectionEstablished(@NonNull WebSocketSession session) throws Exception {
        // connection established
    }
    @Override
    public void handleMessage(@NonNull WebSocketSession session, @NonNull WebSocketMessage
message) throws Exception {
        // receive message
    }
    @Override
    public void handleTransportError(@NonNull WebSocketSession session, @NonNull Throwable exception) throws Exception {
        // error handling
    }
    @Override
    public void afterConnectionClosed(@NonNull WebSocketSession session, @NonNull CloseStatus closeStatus) throws Exception {
        // connection closed
    }
    @Override
    public boolean supportsPartialMessages() {
        return false;
    }
}

Step 2

WebSocketClient client = new StandardWebSocketClient();
WebSocketHandler handler = new ServletWebSocketClientHandler();
WebSocketConnectionManager manager = new WebSocketConnectionManager(client, handler, uri);
manager.start();

Choose the appropriate WebSocketClient implementation (e.g., JettyWebSocketClient , TomcatWebSocketClient ) based on your container.

WebFlux

No extra dependency is required for WebFlux WebSocket support.

Server

Step 1

public class ReactiveWebSocketServerHandler implements WebSocketHandler {
    @NonNull
    @Override
    public Mono
handle(WebSocketSession session) {
        Mono
send = session.send(Flux.create(sink -> {
            // use sink.next(...) to push messages
        })).doOnError(it -> {
            // error handling
        });
        Mono
receive = session.receive()
                .doOnNext(it -> {
                    // receive message
                })
                .doOnError(it -> {
                    // error handling
                })
                .then();
        @SuppressWarnings("all")
        Disposable disposable = session.closeStatus()
                .doOnError(it -> {
                    // error handling
                })
                .subscribe(it -> {
                    // connection closed
                });
        return Mono.zip(send, receive).then();
    }
}

Step 2

@Component
public class ReactiveWebSocketServerHandlerMapping extends SimpleUrlHandlerMapping {
    public ReactiveWebSocketServerHandlerMapping() {
        Map
map = new HashMap<>();
        map.put("/websocket/**", new ReactiveWebSocketServerHandler());
        setUrlMap(map);
        setOrder(100);
    }
}

Step 3

@Configuration(proxyBeanMethods = false)
public class ReactiveWebSocketConfiguration {
    @Bean
    public WebSocketHandlerAdapter webSocketHandlerAdapter() {
        return new WebSocketHandlerAdapter();
    }
}

Cold Knowledge

If you do not set an explicit order, the mapping defaults to Ordered.LOWEST_PRECEDENCE and may be bypassed by other mappings, causing connection failures.

Client

Step 1

public class ReactiveWebSocketClientHandler implements WebSocketHandler {
    @NonNull
    @Override
    public Mono
handle(WebSocketSession session) {
        Mono
send = session.send(Flux.create(sink -> {
            // sink.next(...) to send messages
        })).doOnError(it -> {
            // error handling
        });
        Mono
receive = session.receive()
                .doOnNext(it -> {
                    // receive message
                })
                .doOnError(it -> {
                    // error handling
                })
                .then();
        @SuppressWarnings("all")
        Disposable disposable = session.closeStatus()
                .doOnError(it -> {
                    // error handling
                })
                .subscribe(it -> {
                    // connection closed
                });
        return Mono.zip(send, receive).then();
    }
}

Step 2

WebSocketClient client = new ReactorNettyWebSocketClient();
WebSocketHandler handler = new ReactiveWebSocketClientHandler();
client.execute(uri, handler).subscribe();

Other implementations such as JettyWebSocketClient , UndertowWebSocketClient , or TomcatWebSocketClient can be used depending on the runtime container.

Other Libraries

Java‑WebSocket

A pure‑Java third‑party library for WebSocket. It has over 9k stars on GitHub. See https://github.com/TooTallNate/Java-WebSocket for documentation.

SocketIO

Provides a custom protocol with multi‑language support. Use socket.io-server-java and socket.io-client-java . It is more suitable for real‑time chat scenarios than plain WebSocket.

Netty

Well‑known asynchronous networking framework. The source code and examples are abundant on GitHub: https://github.com/netty/netty .

Final Note

The author has compiled three Spring‑related columns into PDFs. To obtain them, follow the QR‑code or public‑account instructions (search for "Spring Cloud 进阶", "Spring Boot进阶", or "Mybatis 进阶" on the "码猿技术专栏" WeChat public account). If this article helped you, please like, comment, share, and bookmark—it fuels further content creation.

Backend DevelopmentSpring BootWebSocketWebFluxJavaxwebmvc
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.