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.
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><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>2.1.13.RELEASE</version>
</dependency></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><!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/html">
<head>
<title>WebSocket Example</title>
</head>
<body>
登录用户ID:<input type="text" id="sendUserId"/><br>
接受用户ID:<input type="text" id="receivedUserId"/><br>
发送消息内容:<input type="text" id="messageInput"/><br>
接受消息内容:<input type="text" id="messageReceive"/><br>
<button onclick="sendMessage()">Send</button>
<script>
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);
</script>
</body>
</html>
</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
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.
4.2 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.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.