Using WebSocket to build an interactive web application
This guide walks you through the process of creating a “Hello, world” application that sends messages back and forth between a browser and a server. WebSocket is a thin, lightweight layer above TCP. This makes it suitable for using “subprotocols” to embed messages. In this guide, we use STOMP messaging with Spring to create an interactive web application. STOMP is a subprotocol operating on top of the lower-level WebSocket.
What You Will build
You will build a server that accepts a message that carries a user’s name. In response, the server will push a greeting into a queue to which the client is subscribed.
Java 17 or later
You can also import the code straight into your IDE:
Like most Spring Getting Started guides , you can start from scratch and complete each step or you can bypass basic setup steps that are already familiar to you. Either way, you end up with working code.
To start from scratch , move on to Starting with Spring Initializr .
To skip the basics , do the following:
Download
and unzip the source repository for this guide, or clone it using
Git
:
git clone
https://github.com/spring-guides/gs-messaging-stomp-websocket.git
cd into
gs-messaging-stomp-websocket/initial
Jump ahead to Create a Resource Representation Class .
When you finish
, you can check your results against the code in
gs-messaging-stomp-websocket/complete
.
You can use this pre-initialized project and click Generate to download a ZIP file. This project is configured to fit the examples in this tutorial.
To manually initialize the project:
Navigate to https://start.spring.io . This service pulls in all the dependencies you need for an application and does most of the setup for you.
Choose either Gradle or Maven and the language you want to use. This guide assumes that you chose Java.
Click Dependencies and select Websocket .
Click Generate .
Download the resulting ZIP file, which is an archive of a web application that is configured with your choices.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.0</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>messaging-stomp-websocket-complete</artifactId> <version>0.0.1-SNAPSHOT</version> <name>messaging-stomp-websocket-complete</name> <description>Demo project for Spring Boot</description> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>webjars-locator-core</artifactId> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>sockjs-client</artifactId> <version>1.0.2</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>stomp-websocket</artifactId> <version>2.3.3</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>3.3.7</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.1.1-1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
implementation 'org.webjars:webjars-locator-core'
implementation 'org.webjars:sockjs-client:1.0.2'
implementation 'org.webjars:stomp-websocket:2.3.3'
implementation 'org.webjars:bootstrap:3.3.7'
implementation 'org.webjars:jquery:3.1.1-1'
plugins { id 'org.springframework.boot' version '3.0.0' id 'io.spring.dependency-management' version '1.1.0' id 'java' group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = '17' repositories { mavenCentral() dependencies { implementation 'org.springframework.boot:spring-boot-starter-websocket' implementation 'org.webjars:webjars-locator-core' implementation 'org.webjars:sockjs-client:1.0.2' implementation 'org.webjars:stomp-websocket:2.3.3' implementation 'org.webjars:bootstrap:3.3.7' implementation 'org.webjars:jquery:3.1.1-1' testImplementation 'org.springframework.boot:spring-boot-starter-test' test { useJUnitPlatform()Now that you have set up the project and build system, you can create your STOMP message service.
Begin the process by thinking about service interactions.
The service will accept messages that contain a name in a STOMP message whose body is a JSON object. If the name is
Fred
, the message might resemble the following:Spring will use the Jackson JSON library to automatically marshal instances of type
Greeting
into JSON.Next, you will create a controller to receive the hello message and send a greeting message.
In Spring’s approach to working with STOMP messaging, STOMP messages can be routed to
@Controller
classes. For example, theGreetingController
(fromsrc/main/java/com/example/messagingstompwebsocket/GreetingController.java
) is mapped to handle messages to the/hello
destination, as the following listing shows:package com.example.messagingstompwebsocket; 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; @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()) + "!");
This controller is concise and simple, but plenty is going on. We break it down step by step.
The
@MessageMapping
annotation ensures that, if a message is sent to the/hello
destination, thegreeting()
method is called.The payload of the message is bound to a
HelloMessage
object, which is passed intogreeting()
.Internally, the implementation of the method simulates a processing delay by causing the thread to sleep for one second. This is to demonstrate that, after the client sends a message, the server can take as long as it needs to asynchronously process the message. The client can continue with whatever work it needs to do without waiting for the response.
After the one-second delay, the
greeting()
method creates aGreeting
object and returns it. The return value is broadcast to all subscribers of/topic/greetings
, as specified in the@SendTo
annotation. Note that the name from the input message is sanitized, since, in this case, it will be echoed back and re-rendered in the browser DOM on the client side.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 resembles the following listing (fromsrc/main/java/com/example/messagingstompwebsocket/WebSocketConfig.java
):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('/gs-guide-websocket'); 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); function disconnect() { if (stompClient !== null) { stompClient.disconnect(); setConnected(false); console.log("Disconnected"); function sendName() { stompClient.send("/app/hello", {}, JSON.stringify({'name': $("#name").val()})); function showGreeting(message) { $("#greetings").append("<tr><td>" + message + "</td></tr>"); $(function () { $("form").on('submit', function (e) { e.preventDefault(); $( "#connect" ).click(function() { connect(); }); $( "#disconnect" ).click(function() { disconnect(); }); $( "#send" ).click(function() { sendName(); });package com.example.messagingstompwebsocket; 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("/gs-guide-websocket").withSockJS();
WebSocketConfig
is annotated with@Configuration
to indicate that it is a Spring configuration class. It is also annotated with@EnableWebSocketMessageBroker
. As its name suggests,@EnableWebSocketMessageBroker
enables WebSocket message handling, backed by a message broker.The
configureMessageBroker()
method implements the default method inWebSocketMessageBrokerConfigurer
to configure the message broker. It starts by callingenableSimpleBroker()
to enable a simple memory-based message broker to carry the greeting messages back to the client on destinations prefixed with/topic
. It also designates the/app
prefix for messages that are bound for methods annotated with@MessageMapping
. This prefix will be used to define all the message mappings. For example,/app/hello
is the endpoint that theGreetingController.greeting()
method is mapped to handle.The
registerStompEndpoints()
method registers the/gs-guide-websocket
endpoint, enabling SockJS fallback options so that alternate transports can be used if WebSocket is not available. The SockJS client will attempt to connect to/gs-guide-websocket
and use the best available transport (websocket, xhr-streaming, xhr-polling, and so on).With the server-side pieces in place, you can turn your attention to the JavaScript client that will send messages to and receive messages from the server side.
Create an
<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> </head> <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"> <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> </form> <div class="col-md-6"> <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..."> <button id="send" class="btn btn-default" type="submit">Send</button> </form> <div class="row"> <div class="col-md-12"> <table id="conversation" class="table table-striped"> <thead> <th>Greetings</th> </thead> <tbody id="greetings"> </tbody> </table> </body> </html>index.html
file similar to the following listing (fromsrc/main/resources/static/index.html
):The main pieces of this JavaScript file to understand are the
connect()
andsendName()
functions.The
connect()
function uses SockJS and stomp.js to open a connection to/gs-guide-websocket
, which is where our SockJS server waits for connections. Upon a successful connection, the client subscribes to the/topic/greetings
destination, where the server will publish greeting messages. When a greeting is received on that destination, it will append a paragraph element to the DOM to display the greeting message.The
sendName()
function retrieves the name entered by the user and uses the STOMP client to send it to the/app/hello
destination (whereGreetingController.greeting()
will receive it).The
main.css
can be omitted if you like, or you can create an empty one, just so the<link>
can be resolved.package com.example.messagingstompwebsocket; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class MessagingStompWebsocketApplication { public static void main(String[] args) { SpringApplication.run(MessagingStompWebsocketApplication.class, args);
@Configuration
: Tags the class as a source of bean definitions for the application context.
@EnableAutoConfiguration
: Tells Spring Boot to start adding beans based on classpath settings, other beans, and various property settings. For example, ifspring-webmvc
is on the classpath, this annotation flags the application as a web application and activates key behaviors, such as setting up aDispatcherServlet
.
@ComponentScan
: Tells Spring to look for other components, configurations, and services in thecom/example
package, letting it find the controllers.The
main()
method uses Spring Boot’sSpringApplication.run()
method to launch an application. Did you notice that there was not a single line of XML? There is noweb.xml
file, either. This web application is 100% pure Java and you did not have to deal with configuring any plumbing or infrastructure.Build an executable JAR
You can run the application from the command line with Gradle or Maven. You can also build a single executable JAR file that contains all the necessary dependencies, classes, and resources and run that. Building an executable jar makes it easy to ship, version, and deploy the service as an application throughout the development lifecycle, across different environments, and so forth.
If you use Gradle, you can run the application by using
./gradlew bootRun
. Alternatively, you can build the JAR file by using./gradlew build
and then run the JAR file, as follows:Now that the service is running, point your browser at http://localhost:8080 and click the Connect button.
Upon opening a connection, you are asked for your name. Enter your name and click Send. Your name is sent to the server as a JSON message over STOMP. After a one-second simulated delay, the server sends a message back with a “Hello” greeting that is displayed on the page. At this point, you can send another name or you can click the Disconnect button to close the connection.