Building Real-Time Applications with Spring Boot and WebSockets

Traditional HTTP request-response mechanisms are often not sufficient for real-time applications due to the latency involved. WebSockets offer an efficient solution by enabling full-duplex, real-time communication between the server and the client.

In this article, we'll dive into what WebSockets are, how they work, and how you can integrate them into a Spring Boot application, along with a focus on securing WebSocket connections using Spring Security's updated API.

What are WebSockets?

WebSockets are a communication protocol that provides full-duplex, bidirectional communication channels over a single TCP connection. Unlike traditional HTTP, WebSockets allow the server and client to send messages to each other asynchronously without closing the connection after every exchange. This persistent connection is perfect for real-time use cases.

Key Characteristics:

  • Persistent Connection: The WebSocket connection stays open until explicitly closed by the client or server, unlike HTTP where each request opens and closes a new connection.
  • Full-Duplex: Both the client and server can communicate independently and simultaneously.
  • Low Latency: WebSocket messages don’t have the overhead of HTTP, making them faster for real-time communication.

How WebSockets Work:

  1. Initial Handshake: The WebSocket protocol starts with an HTTP handshake initiated by the client. The server acknowledges the handshake and upgrades the connection to a WebSocket.
  2. Data Transmission: After the connection is established, data can flow in both directions without additional requests.
  3. Connection Maintenance: The connection remains open for real-time communication, only closing when either side decides to terminate it.

Setting Up WebSockets in Spring Boot

Spring Boot provides seamless WebSocket integration, enabling you to build real-time applications easily. Let's walk through the process of setting up WebSockets in a Spring Boot project.

Step 1: Add the WebSocket Dependency

First, add the necessary dependencies in your project. If you're using Maven, include the following in your pom.xml:

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

For Gradle, add this line to your build.gradle file:

implementation 'org.springframework.boot:spring-boot-starter-websocket'

Step 2: WebSocket Configuration

Next, create a configuration class to register WebSocket handlers. This class implements WebSocketConfigurer to register WebSocket endpoints.

import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new MyWebSocketHandler(), "/ws")
                .setAllowedOrigins("*");  // Allow all origins, can be restricted in production
    }
}
  • @EnableWebSocket: Enables WebSocket support in the Spring Boot application.
  • registerWebSocketHandlers: Registers WebSocket endpoints with specified URLs (e.g., /ws in this case).

Step 3: WebSocket Handler

You need a handler to process incoming WebSocket messages from clients. Here's a basic implementation of a WebSocket handler by extending TextWebSocketHandler.

import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

public class MyWebSocketHandler extends TextWebSocketHandler {

    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String clientMessage = message.getPayload();
        System.out.println("Received: " + clientMessage);

        // Responding back to the client
        session.sendMessage(new TextMessage("Hello, " + clientMessage + "!"));
    }
}

This handler processes incoming text messages. It reads the message, logs it, and then sends a response back to the client.

Step 4: Frontend WebSocket Client

To interact with the WebSocket server, you can create a simple WebSocket client using JavaScript in a web page.

<!DOCTYPE html>
<html>
<head>
    <title>WebSocket Test</title>
</head>
<body>
    <script>
        var ws = new WebSocket("ws://localhost:8080/ws");

        ws.onopen = function() {
            console.log("Connection opened");
            ws.send("Hello from client");
        };

        ws.onmessage = function(event) {
            console.log("Message received: " + event.data);
        };

        ws.onclose = function() {
            console.log("Connection closed");
        };
    </script>
</body>
</html>

This JavaScript code opens a WebSocket connection, sends a message to the server, and logs responses from the server.

Broadcasting Messages to Multiple Clients

In many real-time applications (e.g., a chat application), you will need to broadcast messages to multiple clients. To do this, you can store all active WebSocket sessions and iterate through them when sending a broadcast.

import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.util.ArrayList;
import java.util.List;

public class MyWebSocketHandler extends TextWebSocketHandler {

    private List<WebSocketSession> sessions = new ArrayList<>();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        sessions.add(session);
    }

    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String clientMessage = message.getPayload();

        // Broadcast the message to all connected clients
        for (WebSocketSession webSocketSession : sessions) {
            if (webSocketSession.isOpen()) {
                webSocketSession.sendMessage(new TextMessage("Broadcast: " + clientMessage));
            }
        }
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        sessions.remove(session);
    }
}
  • afterConnectionEstablished: Adds new WebSocket sessions to the list when a client connects.
  • handleTextMessage: Broadcasts incoming messages to all connected clients.
  • afterConnectionClosed: Removes disconnected sessions.

Securing WebSockets with Spring Security (Updated)

With Spring Security 5.7+, WebSecurityConfigurerAdapter has been deprecated. Here’s how to secure WebSocket endpoints using the new component-based approach with Spring Security.

Step 1: Add Spring Security Dependency

Add the following dependency for Spring Security in your pom.xml:

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

Step 2: Define a Security Configuration

In the new approach, you create a SecurityFilterChain bean to define security rules for your WebSocket endpoints.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class WebSocketSecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorizeRequests ->
                authorizeRequests
                    .requestMatchers("/ws/**").authenticated()  // Protect WebSocket endpoint
                    .anyRequest().permitAll()
            )
            .httpBasic();  // Enable basic authentication for simplicity
        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        var user = User.withUsername("user")
                .password("{noop}password")  // No encoding for simplicity
                .roles("USER")
                .build();

        return new InMemoryUserDetailsManager(user);
    }
}

This configuration secures the WebSocket endpoint (/ws/**) and requires basic authentication for accessing it.

Real-World Use Cases for WebSockets

  1. Real-Time Chat Applications: Allow users to communicate instantly, with new messages appearing in real-time without refreshing the page.
  2. Live Notifications: Push notifications to users in real-time, such as social media notifications, stock price updates, or system alerts.
  3. Collaborative Tools: Collaborative platforms like Google Docs or Trello use WebSockets to ensure changes made by one user are reflected for others in real-time.
  4. Online Gaming: Multiplayer games rely heavily on WebSockets to sync game states and player actions across multiple clients in real-time.
  5. Financial Trading Platforms: Real-time stock prices, currency exchange rates, and trading activities are instantly updated using WebSockets.

Conclusion

WebSockets provide a powerful solution for building real-time, interactive applications. By leveraging Spring Boot's WebSocket support, you can easily integrate WebSockets into your applications to create chat systems, real-time notifications, and much more. With the recent changes in Spring Security, securing your WebSocket connections is more intuitive and aligned with modern security practices. Whether you're building a small real-time feature or a fully-fledged collaborative tool, WebSockets are an essential technology to master.