How to Implement Real-Time WebSocket Messaging in Spring Boot

This article explains how to integrate WebSocket into a Spring Boot application, covering the protocol basics, Maven dependencies, configuration classes, server endpoint implementation, controller and front‑end code, and troubleshooting tips for full‑duplex real‑time communication.

Programmer DD
Programmer DD
Programmer DD
How to Implement Real-Time WebSocket Messaging in Spring Boot

Introduction

During a project we used Netty and MQTT and needed the backend to push messages to the front‑end, so we recorded the use of WebSocket.

What is WebSocket?

WebSocket is a TCP‑based protocol that enables full‑duplex communication between client and server, allowing the server to actively send messages to the client, which is ideal for push notifications and multi‑user chat.

Steps

1. Add Dependency

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

2. Enable Spring Boot WebSocket Support

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
 * @Auther: 马超伟
 * @Date: 2020/06/16/14:35
 * @Description: 开启WebSocket支持
 */
@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

3. Core Configuration: WebSocketServer

The @ServerEndpoint annotation turns the class into a WebSocket server endpoint. A ConcurrentHashMap (or a thread‑safe Set) stores the sessions for each userId to enable targeted push.

@ServerEndpoint defines the URL that clients use to connect.

A ConcurrentHashMap (webSocketMap) holds the current userId‑WebSocket mapping for message pushing.

package cc.mrbird.febs.external.webScoket;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * Created with IntelliJ IDEA.
 * @Auther: 马超伟
 * @Date: 2020/06/16/14:35
 * @Description: 开启WebSocket支持
 */
@Component
@Slf4j
@Service
@ServerEndpoint("/api/websocket/{sid}")
public class WebSocketServer {
    // static variable to record online count (thread‑safe)
    private static int onlineCount = 0;
    // thread‑safe Set to store each client’s WebSocketServer instance
    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
    // session for sending data to the client
    private Session session;
    // received sid
    private String sid = "";

    /** Connection established */
    @OnOpen
    public void onOpen(Session session, @PathParam("sid") String sid) {
        this.session = session;
        webSocketSet.add(this);
        this.sid = sid;
        addOnlineCount();
        try {
            sendMessage("conn_success");
            log.info("New window listening: " + sid + ", online count: " + getOnlineCount());
        } catch (IOException e) {
            log.error("websocket IO Exception");
        }
    }

    /** Connection closed */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);
        subOnlineCount();
        log.info("Released sid: " + sid);
        log.info("Connection closed, online count: " + getOnlineCount());
    }

    /** Receive message */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("Received from window " + sid + ": " + message);
        // broadcast to all
        for (WebSocketServer item : webSocketSet) {
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

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

    /** Server push */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }

    /** Push custom message to a specific sid (null = broadcast) */
    public static void sendInfo(String message, @PathParam("sid") String sid) throws IOException {
        log.info("Push to sid " + sid + ", content: " + message);
        for (WebSocketServer item : webSocketSet) {
            try {
                if (sid == null) {
                    // broadcast (commented out in original)
                } else if (item.sid.equals(sid)) {
                    item.sendMessage(message);
                }
            } catch (IOException e) {
                continue;
            }
        }
    }

    public static synchronized int getOnlineCount() { return onlineCount; }
    public static synchronized void addOnlineCount() { onlineCount++; }
    public static synchronized void subOnlineCount() { onlineCount--; }
    public static CopyOnWriteArraySet<WebSocketServer> getWebSocketSet() { return webSocketSet; }
}

4. Test Controller

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * Created with IntelliJ IDEA.
 * @Auther: 马超伟
 * @Date: 2020/06/16/14:38
 */
@Controller("web_Scoket_system")
@RequestMapping("/api/socket")
public class SystemController {
    // page request
    @GetMapping("/index/{userId}")
    public ModelAndView socket(@PathVariable String userId) {
        ModelAndView mav = new ModelAndView("/socket1");
        mav.addObject("userId", userId);
        return mav;
    }

    // push data interface
    @ResponseBody
    @RequestMapping("/socket/push/{cid}")
    public Map<String, Object> pushToWeb(@PathVariable String cid, String message) {
        Map<String, Object> result = new HashMap<>();
        try {
            WebSocketServer.sendInfo(message, cid);
            result.put("code", cid);
            result.put("msg", message);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }
}

5. Test Page (index.html)

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Java后端WebSocket的Tomcat实现</title>
    <script type="text/javascript" src="js/jquery.min.js"></script>
</head>
<body>
    <div id="main" style="width:1200px;height:800px;"></div>
    Welcome<br/>
    <input id="text" type="text"/>
    <button onclick="send()">发送消息</button>
    <hr/>
    <button onclick="closeWebSocket()">关闭WebSocket连接</button>
    <hr/>
    <div id="message"></div>
</body>
<script type="text/javascript">
    var websocket = null;
    if ('WebSocket' in window) {
        // replace with your address
        websocket = new WebSocket("ws://192.168.100.196:8082/api/websocket/100");
    } else {
        alert('当前浏览器 Not support websocket');
    }
    websocket.onerror = function () { setMessageInnerHTML("WebSocket连接发生错误"); };
    websocket.onopen = function () { setMessageInnerHTML("WebSocket连接成功"); };
    websocket.onmessage = function (event) {
        console.log(event);
        setMessageInnerHTML(event);
    };
    websocket.onclose = function () { setMessageInnerHTML("WebSocket连接关闭"); };
    window.onbeforeunload = function () { closeWebSocket(); };
    function setMessageInnerHTML(innerHTML) {
        document.getElementById('message').innerHTML += innerHTML + '<br/>';
    }
    function closeWebSocket() { websocket.close(); }
    function send() {
        var message = document.getElementById('text').value;
        websocket.send('{"msg":"' + message + '"}');
        setMessageInnerHTML(message + "
");
    }
</script>
</html>

Result

Backend logs show connection requests and message handling.

Front‑end displays the received messages.

Conclusion

WebSocket starts before the Spring container, causing a NullPointerException when injecting services. Initialising required services statically inside WebSocketServer (as shown in the diagram) resolves the issue.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Javareal-time messagingBackend DevelopmentSpring BootWebSocket
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

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.