Reactive WebSockets with Spring 5

1. Overview

In this article, we’re going to create a quick example using the new Spring 5 WebSockets API along with reactive features provided by Spring WebFlux.

WebSocket is a well-known protocol that enables full-duplex communication between client and server, generally used in web applications where the client and server need to exchange events at high frequency and with low latency.

Spring Framework 5 has modernized WebSockets support in the framework, adding reactive capabilities to this communication channel.

We can find more on Spring WebFlux here.

2. Maven Dependencies

We’re going to use the spring-boot-starters dependencies for spring-boot-integration and spring-boot-starter-webflux, currently available at Spring Milestone Repository.

In this example, we’re using the latest available version, 2.0.0.M7, but one should always get the latest version available in the Maven repository:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

3. WebSocket Configuration in Spring

Our configuration is pretty straightforward: We’ll inject the WebSocketHandler to handle the socket session in our Spring WebSocket application.

@Autowired
private WebSocketHandler webSocketHandler;

Furthermore, let’s create a HandlerMapping bean-annotated method that will be responsible for the mapping between requests and handler objects:

@Bean
public HandlerMapping webSocketHandlerMapping() {
    Map<String, WebSocketHandler> map = new HashMap<>();
    map.put("/event-emitter", webSocketHandler);

    SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
    handlerMapping.setOrder(1);
    handlerMapping.setUrlMap(map);
    return handlerMapping;
}

The URL we can connect to will be: ws://localhost:<port>/event-emitter.

4. WebSocket Message Handling in Spring

Our ReactiveWebSocketHandler class will be responsible for managing the WebSocket session on the server-side.

It implements the WebSocketHandler interface so we can override the handle method, which will be used to send the message to the WebSocket client:

@Component
public class ReactiveWebSocketHandler implements WebSocketHandler {
    
    // private fields ...

    @Override
    public Mono<Void> handle(WebSocketSession webSocketSession) {
        return webSocketSession.send(intervalFlux
          .map(webSocketSession::textMessage))
          .and(webSocketSession.receive()
            .map(WebSocketMessage::getPayloadAsText)
            .log());
    }
}

5. Creating a Simple Reactive WebSocket Client

Let’s now create a Spring Reactive WebSocket client that will be able to connect and exchange information with our WebSocket server.

5.1. Maven Dependency

First, the Maven dependencies.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

Here we’re using the same spring-boot-starter-webflux used previously to set up our reactive WebSocket server application.

5.2. WebSocket Client

Now, the let’s create the ReactiveClientWebSocket class, responsible for starting the communication with the server:

public class ReactiveJavaClientWebSocket {
 
    public static void main(String[] args) throws InterruptedException {
 
        WebSocketClient client = new ReactorNettyWebSocketClient();
        client.execute(
          URI.create("ws://localhost:8080/event-emitter"), 
          session -> session.send(
            Mono.just(session.textMessage("event-spring-reactive-client-websocket")))
            .thenMany(session.receive()
              .map(WebSocketMessage::getPayloadAsText)
              .log())
            .then())
            .block(Duration.ofSeconds(10L));
    }
}

In the code above we can see that we’re using the ReactorNettyWebSocketClient, which is the WebSocketClient implementation for use with Reactor Netty.

Additionally, the client connects to the WebSocket server through the URL ws://localhost:8080/event-emitter, establishing a session as soon as it is connected to the server.

We can also see that we are sending a message to the server (“event-spring-reactive-client-websocket“) along with the connection request.

Furthermore, the method send is invoked, expecting as a parameter a variable of type Publisher<T>, which in our case our Publisher<T> is Mono<T> and T is a simple String “event-me-from-reactive-java-client-websocket“.

Moreover, the thenMany(…) method expecting a Flux of type String is invoked. The receive() method gets the flux of incoming messages, which later are converted into strings.

Finally, the block() method forces the client to disconnect from the server after the given time (10 seconds in our example).

5.3. Starting the Client

To run it, make sure the Reactive WebSocket Server is up and running. Then, launch the ReactiveJavaClientWebSocket class, and we can see on the sysout log the events being emitted:

[reactor-http-nio-4] INFO reactor.Flux.Map.1 - 
onNext({"eventId":"6042b94f-fd02-47a1-911d-dacf97f12ba6",
"eventDt":"2018-01-11T23:29:26.900"})

We also can see in the log from our Reactive WebSocket server the message sent by the client during the connection attempt:

[reactor-http-nio-2] reactor.Flux.Map.1: 
onNext(event-me-from-reactive-java-client)

Also, we can see the message of terminated connection after the client finished its requests (in our case, after the 10 seconds):

[reactor-http-nio-2] reactor.Flux.Map.1: onComplete()

6. Creating a Browser WebSocket Client

Let’s create a simple HTML/Javascript client WebSocket to consume our reactive WebSocket server application.

<div class="events"></div>
<script>
    var clientWebSocket = new WebSocket("ws://localhost:8080/event-emitter");
    clientWebSocket.onopen = function() {
        console.log("clientWebSocket.onopen", clientWebSocket);
        console.log("clientWebSocket.readyState", "websocketstatus");
        clientWebSocket.send("event-me-from-browser");
    }
    clientWebSocket.onclose = function(error) {
        console.log("clientWebSocket.onclose", clientWebSocket, error);
        events("Closing connection");
    }
    clientWebSocket.onerror = function(error) {
        console.log("clientWebSocket.onerror", clientWebSocket, error);
        events("An error occured");
    }
    clientWebSocket.onmessage = function(error) {
        console.log("clientWebSocket.onmessage", clientWebSocket, error);
        events(error.data);
    }
    function events(responseEvent) {
        document.querySelector(".events").innerHTML += responseEvent + "<br>";
    }
</script>

With the WebSocket server running, opening this HTML file in a browser (e.g.: Chrome, Internet Explorer, Mozilla Firefox etc.), we should see the events being printed on the screen, with a delay of 1 second per event, as defined in our WebSocket server.

{"eventId":"c25975de-6775-4b0b-b974-b396847878e6","eventDt":"2018-01-11T23:56:09.780"}
{"eventId":"ac74170b-1f71-49d3-8737-b3f9a8a352f9","eventDt":"2018-01-11T23:56:09.781"}
{"eventId":"40d8f305-f252-4c14-86d7-ed134d3e10c6","eventDt":"2018-01-11T23:56:09.782"}

7. Conclusion

Here we’ve presented an example of how to create a WebSocket communication between server and client by using Spring 5 Framework, implementing the new reactive features provided by Spring Webflux.

As always, the full example can be found in our GitHub repository.

Related posts:

Handling Errors in Spring WebFlux
Java Program to Implement the Alexander Bogomolny’s UnOrdered Permutation Algorithm for Elements Fro...
Java Program to Implement Depth-limited Search
Java Program to Find Nearest Neighbor Using Linear Search
Java Program to Implement LinkedList API
Apache Commons Collections SetUtils
Deploy a Spring Boot WAR into a Tomcat Server
“Stream has already been operated upon or closed” Exception in Java
Hướng dẫn sử dụng Java Generics
Hướng dẫn Java Design Pattern – Null Object
Spring Boot - Scheduling
Java Program to Decode a Message Encoded Using Playfair Cipher
Get the workstation name or IP
Quick Guide to Spring Bean Scopes
Java Program to Find Inverse of a Matrix
Java Program to Find ith Largest Number from a Given List Using Order-Statistic Algorithm
Spring Boot Configuration with Jasypt
Custom HTTP Header with the HttpClient
Easy Ways to Write a Java InputStream to an OutputStream
Sử dụng JDBC API thực thi câu lệnh truy vấn dữ liệu
Hướng dẫn Java Design Pattern – Service Locator
Spring Boot - Flyway Database
Java Program to Solve TSP Using Minimum Spanning Trees
Java Program to Perform Complex Number Multiplication
Java Program to Implement Brent Cycle Algorithm
Java Program to Implement Sorted Singly Linked List
Exploring the Spring 5 WebFlux URL Matching
Java Program to Implement Expression Tree
Quản lý bộ nhớ trong Java với Heap Space vs Stack
Java Program to Implement Self organizing List
Spring Boot - Actuator
JUnit5 @RunWith