How to Build a Real‑Time Chat with Spring Boot WebSocket: Step‑by‑Step Guide
This article explains how to integrate WebSocket into a Spring Boot project to create a lightweight instant‑messaging system, covering dependency setup, configuration classes, core server implementation, required modules, common deployment issues, and practical solutions with complete code examples.
1. Implementation Steps
Integrating instant messaging into a project can be solved instantly by adding Spring Boot WebSocket support. The integration consists of three steps: adding the Maven dependency, creating a configuration class, and implementing the core WebSocket server class.
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
<code>import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}</code>1.3 Core Server Class
<code>@ServerEndpoint("/websocket/{userId}")
@Component
@Slf4j
public class WebSocketServer {
private static MessageStore messageStore;
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);
}
public void handleTextMessage(Session session, TextMessage message) throws Exception {
log.warn("Processing message: {}", message.getPayload());
}
}</code>1.4 Front‑end 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 provides full‑duplex communication, but a functional instant‑messaging system also needs several core modules.
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 environment, sessions may reside on different machines. The sending module forwards messages to a message broker (e.g., Kafka) so that each instance can deliver to its local sessions.
2.5 Message Push Module
Consumes messages from the broker and pushes them to the appropriate client sessions on the current node.
3. Problems Encountered and Solutions
3.1 Connection Auto‑Disconnect
WebSocket connections close after a period of inactivity. The solution is to send periodic heartbeat (ping/pong) messages, e.g., every 30 seconds.
3.2 Session Serialization Issue
WebSocket sessions cannot be serialized, so storing them in Redis is not feasible. The approach is to keep sessions in memory on each node and use a broker to route messages.
3.3 Bean Injection Failure in @ServerEndpoint
WebSocket endpoint instances are created per connection and are not Spring singletons, causing @Autowired/@Resource to fail. The fix is to expose static setter methods and inject beans during application startup.
<code>@ServerEndpoint("/websocket/{userId}")
@Component
@Slf4j
public class WebSocketServer {
private static MessageStore messageStore;
private static MessageSender messageSender;
public static void setMessageStore(MessageStore store) { messageStore = store; }
public static void setMessageSender(MessageSender sender) { messageSender = sender; }
}
@Service
public class MessageStore {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@PostConstruct
public void init() { WebSocketServer.setMessageStore(this); }
}</code>3.4 Distributed Message Delivery
When a user's session is on a different node, publish the message to a message‑queue broker. Each instance consumes the queue, checks if the target user’s session exists locally, and pushes the message if found.
3.5 Nginx Proxy Configuration
For a single‑layer proxy, a simple location block with upgrade headers is sufficient. With a double‑layer proxy, 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://backend.service;
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 Demo Effect
Open two browser windows, enter the peer’s user ID, type a message, and click Send. The message appears in the other window’s receive field.
4.2 Code Structure Diagram
4.3 Repository
Source code is available at https://github.com/yclxiao/spring-websocket.git .
5. Conclusion
The article demonstrated how to integrate WebSocket into Spring Boot, described the essential modules for a lightweight instant‑messaging system, discussed common deployment challenges, and provided complete code examples to help developers build real‑time communication features quickly.
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.