Backend Development 15 min read

Build a Real‑Time Chat with Spring Boot WebSocket: Step‑by‑Step Guide

This article explains how to integrate Spring Boot with WebSocket to create a lightweight instant‑messaging system, covering dependency setup, configuration classes, core server code, front‑end integration, essential IM modules, common deployment challenges, and provides complete example code and demos.

macrozheng
macrozheng
macrozheng
Build a Real‑Time Chat with Spring Boot WebSocket: Step‑by‑Step Guide

1. Solution Practice

Integration consists of three steps: add dependencies, create configuration and core classes, and integrate the front end.

1.1 Add Dependency

<code>&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-websocket&lt;/artifactId&gt;
    &lt;version&gt;2.1.13.RELEASE&lt;/version&gt;
&lt;/dependency&gt;</code>

1.2 Add WebSocket Configuration Class

<code>import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/** WebSocket configuration */
@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}
</code>

1.3 Add Core Server Class

<code>@ServerEndpoint("/websocket/{userId}")
@Component
@Slf4j
public class WebSocketServer {
    // Message storage
    private static MessageStore messageStore;
    // Message sender
    private static MessageSender messageSender;

    public static void setMessageStore(MessageStore store) { messageStore = store; }
    public static void setMessageSender(MessageSender sender) { messageSender = sender; }

    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        messageStore.saveSession(session);
    }

    @OnClose
    public void onClose(Session session, @PathParam("userId") String userId) {
        messageStore.deleteSession(session);
    }

    @OnMessage
    public void onMessage(String message, Session session) throws Exception {
        log.warn("Received from " + session.getId() + ": " + message);
        handleTextMessage(session, new TextMessage(message));
    }

    @OnError
    public void onError(Session session, @PathParam("userId") String userId, Throwable error) {
        log.error("Error occurred");
        error.printStackTrace();
    }

    public void handleTextMessage(Session session, TextMessage message) throws Exception {
        log.warn("Received message: {}", message.getPayload());
    }
}
</code>

1.4 Front‑End Page Integration

<code>&lt;!DOCTYPE html&gt;
&lt;html xmlns="http://www.w3.org/1999/html"&gt;
  &lt;head&gt;
    &lt;title&gt;WebSocket Example&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    登录用户ID:&lt;input type="text" id="sendUserId"/&gt;&lt;br&gt;
    接受用户ID:&lt;input type="text" id="receivedUserId"/&gt;&lt;br&gt;
    发送消息内容:&lt;input type="text" id="messageInput"/&gt;&lt;br&gt;
    接受消息内容:&lt;input type="text" id="messageReceive"/&gt;&lt;br&gt;
    &lt;button onclick="sendMessage()"&gt;Send&lt;/button&gt;
    &lt;script&gt;
      var socket = new WebSocket("ws://localhost:8080/websocket/aaa");
      var roomId = "123456";
      var sendUserId = Math.floor(Math.random() * 1000000);
      document.getElementById("sendUserId").value = sendUserId;
      var messageReceive = document.getElementById("messageReceive");

      socket.onopen = function(event) {
        console.log("WebSocket is open now.");
        let loginInfo = {
          msgType: 2,
          sendUserId: sendUserId,
          bizType: 1,
          bizOptModule: 1,
          roomId: roomId,
          msgBody: {}
        };
        socket.send(JSON.stringify(loginInfo));
      };

      socket.onmessage = function(event) {
        var message = event.data;
        console.log("Received message: " + message);
        messageReceive.value = message;
      };

      socket.onclose = function(event) {
        console.log("WebSocket is closed now.");
      };

      function sendMessage() {
        var message = document.getElementById("messageInput").value;
        var receivedUserId = document.getElementById("receivedUserId").value;
        let operateInfo = {
          msgType: 100,
          sendUserId: sendUserId,
          bizType: 1,
          bizOptModule: 1,
          roomId: roomId,
          receivedUserId: receivedUserId,
          msgBody: {
            operateType: 1,
            operateContent: "1"
          }
        };
        socket.send(JSON.stringify(operateInfo));
      }

      setInterval(() => { socket.send("ping"); }, 30000);
    &lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code>

2. Modules Required for a Small IM System

The basic WebSocket integration only provides full‑duplex communication. A complete IM solution needs additional modules, as illustrated below.

2.1 Architecture Diagram

IM architecture diagram
IM architecture diagram

2.2 Message Object Model

<code>public class SocketMsg<T> {
    /** Message type: 1 heartbeat, 2 login, 3 business operation */
    private Integer msgType;
    /** Sender user ID */
    private String sendUserId;
    /** Receiver user ID */
    private String receivedUserId;
    /** Business type */
    private Integer bizType;
    /** Business operation module */
    private Integer bizOptModule;
    /** Message body */
    private T msgBody;
}
</code>

2.3 Message Storage Module

Stores the mapping between user IDs and WebSocket sessions to prevent data loss on server restarts.

2.4 Message Sending Module

In a distributed cluster, a user's session may reside on a different node. The sending module forwards messages to a message broker (e.g., Kafka) so that the node holding the target session can deliver it.

2.5 Message Push Module

Consumes messages from the broker and pushes them to the appropriate local sessions.

3. Problems Encountered and Solutions

3.1 Connection Auto‑Disconnect

WebSocket connections close after a period of inactivity. Implement a heartbeat: the client sends a "ping" every 30 seconds and the server replies with "pong".

3.2 Session Serialization Issue

Sessions cannot be serialized, so storing them in Redis is not feasible. Instead, expose static setter methods in the WebSocket server and inject the required beans during application startup.

3.3 Bean Injection Failure in @ServerEndpoint

WebSocket endpoint instances are created per connection and are not managed as Spring singletons, causing @Autowired/@Resource injection to fail. The workaround is to set the beans via static methods after they are initialized.

3.4 Distributed Messaging

When a session is on a different node, publish the message to a message queue (e.g., Kafka). All nodes consume the queue, locate the session locally, and push the message.

3.5 Nginx Proxy Configuration

For a single‑layer Nginx, the configuration is straightforward. With two layers, additional headers (X‑Forwarded‑For, X‑Forwarded‑Proto) must be forwarded to preserve the original protocol and client IP.

<code>location ~* ^/api/websocket/* {
    proxy_pass http://mangodwsstest.mangod.top;
    proxy_read_timeout 300s;
    proxy_send_timeout 300s;
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
}
</code>

4. Complete Code and Demo

4.1 Page Effect

Open two browser windows, enter the peer's user ID, type a message, and click Send. The receiver sees the message instantly.

Chat page screenshot
Chat page screenshot
Chat page screenshot 2
Chat page screenshot 2

4.2 Code Structure

Project code structure
Project code structure

4.3 Repository

Source code: https://github.com/yclxiao/spring-websocket.git

5. Conclusion

This article covered how to integrate Spring Boot with WebSocket, the essential modules for a small instant‑messaging system, common development and deployment challenges, and provided a complete runnable example.

Distributed SystemsBackend DevelopmentSpring BootWebSocketNginxreal-time communicationInstant Messaging
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.