Backend Development 21 min read

WebSocket Integration in Spring Boot: Javax, WebMVC, and WebFlux Configurations

This article provides a comprehensive guide on implementing WebSocket clusters in Spring Boot, covering three main integration methods—Javax, WebMVC, and WebFlux—including server‑side and client‑side configurations, code examples, and practical tips for each approach.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
WebSocket Integration in Spring Boot: Javax, WebMVC, and WebFlux Configurations

Hello everyone, I am Chen.

Recently I created a Spring Cloud library that implements a configuration annotation for WebSocket clustering, which led me to research various WebSocket integration methods. Below are the approaches I have gathered (there are surprisingly many ways to integrate a simple WS).

Javax

WebMVC

WebFlux

Java‑WebSocket

SocketIO

Netty

In this article I will focus on the first three methods because Spring Boot is the mainstream framework today. The latter three are not tightly bound to Spring Boot and can be used with plain Java; I will give a brief introduction to them as well.

Now let’s dive into the first three methods (Javax, WebMVC, WebFlux) and discuss both server‑side and client‑side configurations in Spring Boot (client configuration is often missing in documentation, which can be frustrating).

Javax

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

Server Side

Step 1

@Component
@ServerEndpoint("/websocket/{type}")
public class JavaxWebSocketServerEndpoint {
    @OnOpen
    public void onOpen(Session session, EndpointConfig config, @PathParam(value = "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 (or byte[])
    }
    @OnError
    public void onError(Session session, Throwable e) {
        // error handling
    }
}

We add @ServerEndpoint on the class to declare a server endpoint and can configure a dynamic path using {} placeholders.

@OnOpen marks the method that is called when a client connects; the Session represents the connection and can be cached for sending messages. @PathParam retrieves the dynamic path value.

@OnClose marks the method called when a client disconnects; you can remove the cached Session and obtain the close reason.

@OnMessage marks the method called when a message is received; it can handle text, binary, or pong messages.

@OnError marks the method called when an exception occurs; both the Session and the exception are available.

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 ServerEndpointExporter enables the native javax.websocket usage.

Cold Knowledge

The javax.websocket library defines PongMessage but not PingMessage . In practice browsers automatically reply to a ping with a pong, so you usually do not need to handle ping yourself.

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

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

JavaScript’s built‑in WebSocket does not provide a ping API, which suggests that the server initiates ping and the client replies with pong.

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 also uses @ClientEndpoint and the same lifecycle annotations as the server.

Step 2

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

We obtain a WebSocketContainer via ContainerProvider and call connectToServer with the client class and target URI.

Cold Knowledge

The container is loaded via SPI. In a Spring environment you can also obtain it through 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

// send text
session.getAsyncRemote().sendText("Hello");
// send binary
session.getAsyncRemote().sendBinary(ByteBuffer.wrap(bytes));
// send object (uses Encoder)
session.getAsyncRemote().sendObject(obj);
// send ping
session.getAsyncRemote().sendPing(ByteBuffer.allocate(0));
// send pong
session.getAsyncRemote().sendPong(ByteBuffer.allocate(0));

WebMVC

Dependency:

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

Server Side

Step 1

import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;

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() {
        // does not support partial messages
        return false;
    }
}

Step 2

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

We enable WebSocket with @EnableWebSocket and register a handler for the /websocket path.

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 true to continue
            return false;
        }
        @Override
        public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
            // after handshake
        }
    }
}

Cold Knowledge

WebMVC’s handler mapping stores handlers in a Map<String, WebSocketHandler> , which does not support Ant‑style wildcard paths directly. 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
        @NonNull
        public 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();

The concrete WebSocketClient implementation can be chosen based on the container (e.g., StandardWebSocketClient , JettyWebSocketClient , etc.).

WebFlux

WebFlux does not require an extra dependency for WebSocket.

Server Side

Step 1

import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketSession;

public class ReactiveWebSocketServerHandler implements WebSocketHandler {
    @Override
    public Mono
handle(WebSocketSession session) {
        Mono
send = session.send(Flux.create(sink -> {
            // use sink.next(...) to send messages at any time
        })).doOnError(it -> {
            // error handling
        });
        Mono
receive = session.receive()
                .doOnNext(it -> {
                    // receive message
                })
                .doOnError(it -> {
                    // error handling
                })
                .then();
        Disposable close = 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 order, the mapping defaults to Ordered.LOWEST_PRECEDENCE , which may cause the handler to be ignored because other mappings take precedence.

Client

Step 1

public class ReactiveWebSocketClientHandler implements WebSocketHandler {
    @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();
        Disposable close = 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();

Cold Knowledge

Just like the WebMVC client, the reactive WebSocketClient has multiple implementations (e.g., ReactorNettyWebSocketClient , JettyWebSocketClient , UndertowWebSocketClient , TomcatWebSocketClient ), and you should pick the one that matches your runtime container.

Java‑WebSocket

This is a pure‑Java third‑party library dedicated to WebSocket implementation. It has over 9k stars on GitHub.

Repository: https://github.com/TooTallNate/Java-WebSocket

SocketIO

Socket.IO uses a custom protocol, supports many languages, and provides unified server and client APIs (e.g., socket.io-server-java and socket.io-client-java ). It is more suitable for real‑time chat scenarios; for plain WebSocket use it may be overkill.

Repository: https://github.com/socketio

Netty

Netty is a widely known asynchronous event‑driven network framework. Documentation and examples are abundant.

Repository: https://github.com/netty/netty

Final Note (Please Support)

If this article helped you, please like, view, share, or bookmark it. Your support motivates me to keep writing.

I also run a Knowledge Planet community. Joining costs 199 CNY and provides access to extensive resources such as Spring full‑stack projects, massive data sharding practice, DDD micro‑service series, RocketMQ deep dive, and more.

For joining, add my WeChat: special_coder .

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.