Spring Boot WebSocket STOMP SockJS Example

In this article, we will create a WebSocket implementation using Spring boot and STOMP that sends messages back and forth, between a browser and the server. 

WebSocket is a very thin, lightweight layer above TCP. It makes it very suitable to use "subprotocols" to embed messages. In this article, we’ll dive in and use STOMP messaging with Spring boot to create an interactive web application.
 
Before getting started, let's familiarize a few concepts like what is WebSocket, STOMP, SOCKJS, etc.

WebSocket Overview?

The WebSocket protocol provides a standardized way to establish a full-duplex, two-way communication channel between client and server over a single TCP connection. It is a different TCP protocol from HTTP but is designed to work over HTTP, using ports 80 and 443 and allowing the re-use of existing firewall rules.
A WebSocket interaction begins with an HTTP request that uses the HTTP Upgrade header to upgrade or, in this case, to switch to the WebSocket protocol. 
The following example shows such an interaction:
GET /spring-websocket-portfolio/portfolio HTTP/1.1
Host: localhost:8080
Upgrade: websocket 
Connection: Upgrade 
Sec-WebSocket-Key: Uc9l9TMkWGbHFD2qnFHltg==
Sec-WebSocket-Protocol: v10.stomp, v11.stomp
Sec-WebSocket-Version: 13
Origin: http://localhost:8080
  • The Upgrade header.
  • Using the Upgrade connection.
Instead of the usual 200 status code, a server with WebSocket support returns output similar to the following:
HTTP/1.1 101 Switching Protocols 
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: 1qVdfYHU9hPOl4JYYNXF623Gzn0=
Sec-WebSocket-Protocol: v10.stomp
Protocol switch
After a successful handshake, the TCP socket underlying the HTTP upgrade request remains open for both the client and the server to continue to send and receive messages.

The below diagram demonstrates how WebSocket interaction begins with an HTTP request that uses the HTTP Upgrade header to upgrade or, in this case, to switch to the WebSocket protocol.

What is STOMP?

STOMP stands for Simple Text Oriented Messaging Protocol. It is a messaging protocol that defines the format and rules for data exchange.
 Why do we need STOMP? 
Well, WebSocket is just a communication protocol. It doesn’t define things like - How to send a message only to users who are subscribed to a particular topic, or how to send a message to a particular user. We need STOMP for these functionalities. 

What is SockJS?

SockJS is a browser JavaScript library that provides a WebSocket-like object. SockJS gives you a coherent, cross-browser, Javascript API which creates a low latency, full duplex, cross-domain communication channel between the browser and the web server.
Under the hood, SockJS tries to use native WebSockets first. If that fails it can use a variety of browser-specific transport protocols and presents them through WebSocket-like abstractions.
SockJS is used to enable fallback options for browsers that don’t support WebSocket. Spring provides a server-side implementation of SockJS and we will see how to use the same in this example.

Spring WebSocket

Spring WebSocket is the Spring module that enables WebSocket-style messaging support. As Spring WebSocket’s documentation states, the WebSocket protocol defines an important new capability for web applications: full-duplex, two-way communication between client and server.
Well, now that we know what WebSocket, STOMP, SockJS, and Spring WebSocket are. Next, let’s jump into the implementation of our Spring boot WebSocket Application. 

Tools and Technologies Used

  • Spring Boot - 3+
  • JDK - 17 or later
  • Spring Framework - 6+
  • webjars
  • SOCKJS
  • STOMP
  • Spring WebSocket
  • Maven - 3.2+
  • IDE - Eclipse or Spring Tool Suite (STS)

1. Creating a Spring Boot Application

There are many ways to create a Spring Boot application. You can refer below articles to create a Spring Boot application.

2. Project Structure

Refer below screenshot to create a project or packaging structure:

3. Create a resource representation class

We are exchanging messages in JSON format over STOMP protocol so let's create two Java classes Greeting and HelloMessage

HelloMessage

package net.javaguides.springboot.websocket.model

public class HelloMessage {

    private String name;

    public HelloMessage() {}

    public HelloMessage(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Greeting

package net.javaguides.springboot.websocket.model

public class Greeting {

    private String content;

    public Greeting() {}

    public Greeting(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }
}
Spring will use the Jackson JSON library to automatically marshal instances of type Greeting into JSON.

Next, you’ll create a controller to receive the hello message and send a greeting message.

4. Create a message-handling controller

In Spring’s approach to working with STOMP messaging, STOMP messages can be routed to @Controller classes. For example, the GreetingController is mapped to handle messages to the destination "/hello".
package net.javaguides.springboot.websocket.controller;

import org.springframework.messaging.handler.annotation.MessageMapping;

import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import org.springframework.web.util.HtmlUtils;

import net.javaguides.springboot.websocket.model.Greeting;
import net.javaguides.springboot.websocket.model.HelloMessage;

@Controller
public class GreetingController {


    @MessageMapping("/hello")
    @SendTo("/topic/greetings")
    public Greeting greeting(HelloMessage message) throws Exception {
        Thread.sleep(1000); // simulated delay
        return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
    }

}
The @MessageMapping annotation ensures that if a message is sent to the destination "/hello", then the greeting() method is called. The payload of the message is bound to a HelloMessage object which is passed into greeting().

5. Configure Spring for STOMP messaging

Now that the essential components of the service are created, you can configure Spring to enable WebSocket and STOMP messaging.
 
Create a Java class named WebSocketConfig that looks like this:
package net.javaguides.springboot.websocket.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws").withSockJS();
    }

}
WebSocketConfig is annotated with @Configuration to indicate that it is a Spring configuration class. The @EnableWebSocketMessageBroker enables WebSocket message handling, backed by a message broker. 

6. Create a browser client

With the server-side pieces in place, now let’s turn our attention to the JavaScript client that will send messages to and receive messages from the server side.
 
Create an index.html file that looks like this:
<!DOCTYPE html>
<html>
<head>
    <title>Hello WebSocket</title>
    <link href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet">
    <link href="/main.css" rel="stylesheet">
    <script src="/webjars/jquery/jquery.min.js"></script>
    <script src="/webjars/sockjs-client/sockjs.min.js"></script>
    <script src="/webjars/stomp-websocket/stomp.min.js"></script>
    <script src="/app.js"></script>
    <script type="text/javascript" src="/stompobject.js"></script>
</head>
<body>
<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being
    enabled. Please enable
    Javascript and reload this page!</h2></noscript>
<div id="main-content" class="container">
  <div class="row">
   <h1> Spring Boot + Websocket Demo</h1>
  </div>
  <br>
    <div class="row">
        <div class="col-md-6">
            <form class="form-inline">
                <div class="form-group">
                    <label for="connect">WebSocket connection:</label>
                    <button id="connect" class="btn btn-default" type="submit">Connect</button>
                    <button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect
                    </button>
                </div>
            </form>
            
            <br>
             <form class="form-inline">
                <div class="form-group">
                    <label for="name">What is your name?</label>
                    <input type="text" id="name" class="form-control" placeholder="Your name here...">
                </div>
                <button id="send" class="btn btn-default" type="submit">Send</button>
            </form>
        </div>
    </div>
    <div class="row">
        <div class="col-md-6">
            <table id="conversation" class="table table-striped">
                <thead>
                <tr>
                    <th>Greetings</th>
                </tr>
                </thead>
                <tbody id="greetings">
                </tbody>
            </table>
        </div>
    </div>
</div>
</body>
</html>
This HTML file imports the SockJS and STOMP javascript libraries that will be used to communicate with our server using STOMP over WebSocket. We’re also importing here an app.js which contains the logic of our client application. 

Let’s create that file: src/main/resources/static/app.js:
var stompClient = null;

function setConnected(connected) {
    $("#connect").prop("disabled", connected);
    $("#disconnect").prop("disabled", !connected);
    if (connected) {
        $("#conversation").show();
    }
    else {
        $("#conversation").hide();
    }
    $("#greetings").html("");
}

function connect() {
    var socket = new SockJS('/ws');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        setConnected(true);
        console.log('Connected: ' + frame);
        stompClient.subscribe('/topic/greetings', function (greeting) {
            showGreeting(JSON.parse(greeting.body).content);
        });
    });
    
    displayStompObject();
}

function disconnect() {
    if (stompClient !== null) {
        stompClient.disconnect();
    }
    setConnected(false);
    console.log("Disconnected");
}

function showGreeting(message) {
    $("#greetings").append("<tr><td>" + message + "</td></tr>");
}

function sendName() {
 stompClient.send("/app/hello", {}, JSON.stringify({
  'name' : $("#name").val()
 }));
}


$(function () {
    $("form").on('submit', function (e) {
        e.preventDefault();
    });
    $( "#connect" ).click(function() { connect(); });
    $( "#disconnect" ).click(function() { disconnect(); });
    $( "#send" ).click(function() { sendName(); });
});

7. Running the Application

We have successfully developed a Spring boot WebSocket example. Now it's time to deploy our application in a servlet container(embedded tomcat). 

Two ways we can start the standalone Spring boot application. 

From the root directory of the application and type the following command to run it -
$ mvn spring-boot:run

From your IDE, run the Application.main() method as a standalone Java class that will start the embedded Tomcat server on port 8080 and point the browser to http://localhost:8080/.

Demo

Watch this GIF for a complete end to end demo of this spring boot WebSocket example:

GitHub Repository 

The source code examples are available on my GitHub Repository.

Comments