Back-end
8 minute read

Using Spring Boot for WebSocket Implementation with STOMP

Tomasz has 10+ years of experience with Java apps, and worked for companies like Hewlett-Packard, as well as Silicon Valley startups.

The WebSocket protocol is one of the ways to make your application handle real-time messages. The most common alternatives are long polling and server-sent events. Each of these solutions has its advantages and drawbacks. In this article, I am going to show you how to implement WebSockets with the Spring Boot Framework. I will cover both the server-side and the client-side setup, and we will use STOMP over WebSocket protocol to communicate with each other.

The server-side will be coded purely in Java. But, in the case of the client, I will show snippets written both in Java and in JavaScript (SockJS) since, typically, WebSockets clients are embedded in front-end applications. The code examples will demonstrate how to broadcast messages to multiple users using the pub-sub model as well as how to send messages only to a single user. In a further part of the article, I will briefly discuss securing WebSockets and how we can ensure that our WebSocket-based solution will stay operational even when the environment does not support the WebSocket protocol.

Please note that the topic of securing WebSockets will only briefly be touched on here since it is a complex enough topic for a separate article. Due to this, and several other factors that I touch on in the WebSocket in Production? section in the end, I recommend making modifications before using this setup in production, read until the end for a production-ready setup with security measures in place.

WebSocket and STOMP Protocols

The WebSocket protocol allows you to implement bidirectional communication between applications. It is important to know that HTTP is used only for the initial handshake. After it happens, the HTTP connection is upgraded to a newly opened TCP/IP connection that is used by a WebSocket.

The WebSocket protocol is a rather low-level protocol. It defines how a stream of bytes is transformed into frames. A frame may contain a text or a binary message. Because the message itself does not provide any additional information on how to route or process it, It is difficult to implement more complex applications without writing additional code. Fortunately, the WebSocket specification allows using of sub-protocols that operate on a higher, application level. One of them, supported by the Spring Framework, is STOMP.

STOMP is a simple text-based messaging protocol that was initially created for scripting languages such as Ruby, Python, and Perl to connect to enterprise message brokers. Thanks to STOMP, clients and brokers developed in different languages can send and receive messages to and from each other. The WebSocket protocol is sometimes called TCP for Web. Analogically, STOMP is called HTTP for Web. It defines a handful of frame types that are mapped onto WebSockets frames, e.g., CONNECT, SUBSCRIBE, UNSUBSCRIBE, ACK, or SEND. On one hand, these commands are very handy to manage communication while, on the other, they allow us to implement solutions with more sophisticated features like message acknowledgment.

The Server-side: Spring Boot and WebSockets

To build the WebSocket server-side, we will utilize the Spring Boot framework which significantly speeds up the development of standalone and web applications in Java. Spring Boot includes the spring-WebSocket module, which is compatible with the Java WebSocket API standard (JSR-356).

Implementing the WebSocket server-side with Spring Boot is not a very complex task and includes only a couple of steps, which we will walk through one by one.

Step 1. First, we need to add the WebSocket library dependency.

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

If you plan to use JSON format for transmitted messages, you may want to include also the GSON or Jackson dependency. Quite likely, you may additionally need a security framework, for instance, Spring Security.

Step 2. Then, we can configure Spring to enable WebSocket and STOMP messaging.

Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

  @Override
  public void registerStompEndpoints(StompEndpointRegistry
   registry) {
    registry.addEndpoint("/mywebsockets")
        .setAllowedOrigins("mydomain.com").withSockJS();
  }

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

The method configureMessageBroker does two things:

  1. Creates the in-memory message broker with one or more destinations for sending and receiving messages. In the example above, there are two destination prefixes defined: topic and queue. They follow the convention that destinations for messages to be carried on to all subscribed clients via the pub-sub model should be prefixed with topic. On the other hand, destinations for private messages are typically prefixed by queue.
  2. Defines the prefix app that is used to filter destinations handled by methods annotated with @MessageMapping which you will implement in a controller. The controller, after processing the message, will send it to the broker.

Spring Boot WebSocket: How messages are handled on server-side

How messages are handled on server-side (source: Spring documentation)


Going back to the snippet above—probably you have noticed a call to the method withSockJS()—it enables SockJS fallback options. To keep things short, it will let our WebSockets work even if the WebSocket protocol is not supported by an internet browser. I will discuss this topic in greater detail a bit further.

There is one more thing that needs clarifying—why we call setAllowedOrigins() method on the endpoint. It is often required because the default behavior of WebSocket and SockJS is to accept only same-origin requests. So, if your client and the server-side use different domains, this method needs to be called to allow the communication between them.

Step 3. Implement a controller that will handle user requests. It will broadcast received message to all users subscribed to a given topic.

Here is a sample method that sends messages to the destination /topic/news.

@MessageMapping("/news")
@SendTo("/topic/news")
public void broadcastNews(@Payload String message) {
  return message;
}

Instead of the annotation @SendTo, you can also use SimpMessagingTemplate which you can autowire inside your controller.

@MessageMapping("/news")
public void broadcastNews(@Payload String message) {
  this.simpMessagingTemplate.convertAndSend("/topic/news", message)
}

In later steps, you may want to add some additional classes to secure your endpoints, like ResourceServerConfigurerAdapter or WebSecurityConfigurerAdapter from the Spring Security framework. Also, it is often beneficial to implement the message model so that transmitted JSON can be mapped to objects.

Building the WebSocket Client

Implementing a client is an even simpler task.

Step 1. Autowire Spring STOMP client.

@Autowired
private WebSocketStompClient stompClient;

Step 2. Open a connection.

StompSessionHandler sessionHandler = new CustmStompSessionHandler();

StompSession stompSession = stompClient.connect(loggerServerQueueUrl, 
sessionHandler).get();

Once this is done, it is possible to send a message to a destination. The message will be sent to all users subscribed to a topic.

stompSession.send("topic/greetings", "Hello new user");

It is also possible to subscribe for messages.

session.subscribe("topic/greetings", this);

@Override
public void handleFrame(StompHeaders headers, Object payload) {
    Message msg = (Message) payload;
    logger.info("Received : " + msg.getText()+ " from : " + 
    msg.getFrom());
}

Sometimes it is needed to send a message only to a dedicated user (for instance when implementing a chat). Then, the client and the server-side must use a separate destination dedicated to this private conversation. The name of the destination may be created by appending a unique identifier to a general destination name, e.g., /queue/chat-user123. HTTP Session or STOMP session identifiers can be utilized for this purpose.

Spring makes sending private messages a lot easier. We only need to annotate a Controller’s method with @SendToUser. Then, this destination will be handled by UserDestinationMessageHandler, which relies on a session identifier. On the client-side, when a client subscribes to a destination prefixed with /user, this destination is transformed into a destination unique for this user. On the server-side, a user destination is resolved based on a user’s Principal.

Sample server-side code with @SendToUser annotation:

@MessageMapping("/greetings")
@SendToUser("/queue/greetings")
public String reply(@Payload String message,
   Principal user) {
 return  "Hello " + message;
}

Or you can use SimpMessagingTemplate:

String username = ...
this.simpMessagingTemplate.convertAndSendToUser();
   username, "/queue/greetings", "Hello " + username);

Let’s now look at how to implement a JavaScript (SockJS) client capable of receiving private messages which could be sent by the Java code in the example above. It is worth knowing that WebSockets are a part of HTML5 specification and are supported by most modern browsers (Internet Explorer supports them since version 10).

function connect() {
 var socket = new SockJS('/greetings');
 stompClient = Stomp.over(socket);
 stompClient.connect({}, function (frame) {
   stompClient.subscribe('/user/queue/greetings', function (greeting) {
     showGreeting(JSON.parse(greeting.body).name);
   });
 });
}

function sendName() {
 stompClient.send("/app/greetings", {}, $("#name").val());
}

As you have probably noted, to receive private messages, the client needs to subscribe to a general destination /queue/greetings prefixed with /user. It does not have to bother with any unique identifiers. However, the client needs to login to the application before, so the Principal object on the server-side is initialized.

Securing WebSockets

Many web applications use cookie-based authentication. For instance, we can use Spring Security to restrict access to certain pages or Controllers only to logged users. User security context is then maintained through cookie-based HTTP session that is later associated with WebSocket or SockJS sessions created for that user. WebSockets endpoints can be secured as any other requests, e.g., in Spring’s WebSecurityConfigurerAdapter.

Nowadays, web applications often use REST APIs as their back-end and OAuth/JWT tokens for user authentication and authorization. The WebSocket protocol does not describe how servers should authenticate clients during HTTP handshake. In practice, standard HTTP headers (e.g., Authorization) are used for this purpose. Unfortunately, it is not supported by all STOMP clients. Spring Java’s STOMP client allows to set headers for the handshake:

WebSocketHttpHeaders handshakeHeaders = new WebSocketHttpHeaders();
handshakeHeaders.add(principalRequestHeader, principalRequestValue);

But the SockJS JavaScript client does not support sending authorization header with a SockJS request. However, it allows for sending query parameters that can be used to pass a token. This approach requires writing custom code in the server-side that will read the token from the query parameters and validate it. It is also important to make sure that tokens are not logged together with requests (or logs are well protected) since this could introduce a serious security violation.

SockJS Fallback Options

Integration with WebSocket may not always go smoothly. Some browsers (e.g., IE 9) do not support WebSockets. What is more, restrictive proxies may make it impossible to perform the HTTP upgrade or they cut connections that are open for too long. In such cases, SockJS comes to the rescue.

SockJS transports fall in three general categories: WebSockets, HTTP Streaming, and HTTP Long Polling. The communication starts with SockJS sending GET /info to obtain basic information from the server. Basing on the response, SockJS decides on the transport to be used. The first choice are WebSockets. If they are not supported, then, if possible, Streaming is used. If this option is also not possible, then Polling is chosen as a transport method.

WebSocket in Production?

While this setup works, it isn’t the “best.” Spring Boot allows you to use any full-fledged messaging system with the STOMP protocol (e.g., ActiveMQ, RabbitMQ), and an external broker may support more STOMP operations (e.g., acknowledges, receipts) than the simple broker we used. STOMP Over WebSocket provides interesting information about WebSockets and STOMP protocol. It lists messaging systems that handle STOMP protocol and could be a better solution to use in production. Especially if, due to the high number of requests, the message broker needs to be clustered. (Spring’s simple message broker is not suitable for clustering.) Then, instead of enabling the simple broker in WebSocketConfig, it is required to enable the Stomp broker relay that forwards messages to and from an external message broker. To sum up, an external message broker may help you build a more scalable and robust solution.

If you’re ready to continue your Java Developer journey exploring Spring Boot, try reading Using Spring Boot for OAuth2 and JWT REST Protection next.

Understanding the basics

What is STOMP?

STOMP is the Simple (or Streaming) Text Oriented Messaging Protocol. It uses a set of commands like CONNECT, SEND, or SUBSCRIBE to manage the conversation. STOMP clients, written in any language, can talk with any message broker supporting the protocol.

What are WebSockets used for?

WebSockets are typically used to make web applications more interactive. They can be helpful when implementing social feeds, online chats, news updates, or location-based apps.

How does a WebSocket work?

WebSockets provide bidirectional communication channel over a single TCP connection. The client establishes a persistent connection through a process known as the WebSocket handshake. The connection allows exchanging messages in real time.

What is Spring Boot and why it is used?

Spring Boot is a Java-based framework that makes it easier to implement standalone applications or microservices. It is commonly used because it greatly simplifies integration with various products and frameworks. It also contains an embedded web server so there is no need to deploy WAR files.