Christopher Anabo
Christopher Anabo
Senior Tech Lead
Christopher Anabo

Notes

Building Real-Time Communication with Java WebSockets: A Guide

Building Real-Time Communication with Java WebSockets: A Guide

In today's fast-paced digital landscape, real-time communication has become a cornerstone of modern web applications. Whether it's live chat functionality, collaborative editing tools, or dynamic data updates, the ability to push information to clients instantaneously is invaluable. Java, with its robust ecosystem and versatility, offers powerful tools for implementing real-time features, and one such tool is WebSockets.

Understanding WebSockets

WebSockets provide a full-duplex communication channel over a single, long-lived TCP connection. Unlike traditional HTTP requests, which are stateless and short-lived, WebSockets maintain a persistent connection between the client and the server, allowing for bi-directional communication. This persistent connection eliminates the need for repeated HTTP requests, reducing latency and overhead.

How it works

  • When a client wishes to establish a WebSocket connection, it sends an initial HTTP request to the server, known as the WebSocket handshake request.
  • The server recognizes this handshake request and responds with a WebSocket handshake response, indicating that it's willing to upgrade the connection to the WebSocket protocol.
  • Once the handshake is completed successfully, the connection transitions from HTTP to the WebSocket protocol, allowing full-duplex communication.

 

package com.edu.retail.ws;

import io.quarkus.scheduler.Scheduled;

import javax.enterprise.context.ApplicationScoped;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

@ServerEndpoint("/dashboard/{clientId}")
@ApplicationScoped
public class DashboardWebSocket {

    private Map sessions = new ConcurrentHashMap<>();
    private AtomicInteger totalOrders = new AtomicInteger();

    @OnOpen
    public void onOpen(Session session, @PathParam("clientId") String clientId) {
        sessions.put(clientId, session);
    }

    @OnClose
    public void onClose(Session session, @PathParam("clientId") String clientId) {
        sessions.remove(clientId);
    }

    @OnError
    public void onError(Session session, @PathParam("clientId") String clientId, Throwable throwable) {
        sessions.remove(clientId);
    }

    @Scheduled(every="5s")
    void increment() {
        if (sessions != null) {
            totalOrders.incrementAndGet();
            broadcast(String.valueOf(totalOrders));
        }
    }

    private void broadcast(String message) {
        sessions.values().forEach(s -> {
            s.getAsyncRemote().sendObject(message, result ->  {
                if (result.getException() != null) {
                    System.out.println("Unable to send message: " + result.getException());
                }
            });
        });
    }

}

 

Client Dashboard

Upon loading, the dashboard establishes a WebSocket connection with the Quarkus application and begins listening for incoming data. As new data arrives, the dashboard updates its UI to display the current number of sales orders received today.

For simplicity, the entire dashboard is implemented using plain HTML and JavaScript, with Bootstrap and jQuery as the only external libraries.

The dashboard implementation is located in the META-INF/resources directory of the Quarkus application, which automatically serves static resources from this directory.

The logic for handling the WebSocket connection is found in the file META-INF/resources/js/dashboard.js.

"use strict"
var connected = false;
var socket;

function connect() {
    if (! connected) {
        var clientId = generateClientId(6);
        socket = new WebSocket("ws://" + location.host + "/dashboard/" + clientId);

        socket.onopen = function() {
            connected = true;
            console.log("Connected to the web socket with clientId [" + clientId + "]");
            $("#connect").attr("disabled", true);
            $("#connect").text("Connected");
        };
        socket.onmessage =function(m) {
            console.log("Got message: " + m.data);
            $("#totalOrders").text(m.data);
        };
    }
}

function generateClientId(length) {
   var result           = '';
   var characters       = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
   var charactersLength = characters.length;
   for ( var i = 0; i < length; i++ ) {
      result += characters.charAt(Math.floor(Math.random() * charactersLength));
   }
   return result;
}

 

Clicking on the Connect button establishes a WebSocket connection with the server. Since both client and server are running on the same process, we can use localhost as the hostname. The code generates a random string and uses it as the client ID.

socket.onmessage binds to a callback function that updates the content of a div with received data.

socket.onmessage =function(m) {
    console.log("Got message: " + m.data);
    $("#totalOrders").text(m.data);
};
 
 

Backend Scheduled task

So far, we have looked at the WebSocket server and client. Now we need to push some sales data to clients. To mimic a real-world scenario, I have created a scheduled task to write random values to all the sessions periodically.

Creating a scheduled task in Quarkus is very easy. You need to add the scheduler extension to the project’s POM file and create the method that needs to executed on the given period.

In this example, I’ve created a new method increment() in the same DashboardWebSocket class. It increments the value of totalOrders and broadcasts to all connected dashboards every 5 seconds.

@Scheduled(every="5s")
void increment() {
    if (sessions != null) {
        totalOrders.incrementAndGet();
        broadcast(String.valueOf(totalOrders));
    }
}

 

Running the application

Now, let’s see our application in action. Using a terminal, navigate to the location where you’ve cloned the repository and issue the following commands.

cd quarkus-websockets-dashboard
./mvnw compile quarkus: dev

Then open your browser window to http://localhost:8080/. Click on the Connect button and see the total number of sales orders changing every 5 seconds.

 

References

Quarkus — Using WebSockets

Quarkus — Scheduling Periodic Tasks

 

Inspiration

https://medium.com/event-driven-utopia/building-a-real-time-sales-dashboard-with-websockets-and-quarkus-d57c3f1554ce

https://github.com/dunithd/edu-samples/tree/main/quarkus-websockets-dashboard

https://docs.quarkiverse.io/quarkus-reactive-messaging-http/dev/reactive-messaging-websocket.html