How to Integrate WebSocket with Spring Boot for Real‑Time Messaging

This guide walks through the problem of server‑initiated push in a Spring Boot project, explains why WebSocket is needed over HTTP, and provides step‑by‑step Maven setup, configuration classes, controller code, a test HTML page, and troubleshooting tips for proper initialization.

Java Web Project
Java Web Project
Java Web Project
How to Integrate WebSocket with Spring Boot for Real‑Time Messaging

Introduction

During a project that used Netty and MQTT for message exchange, the need arose for the backend to push messages to the frontend. WebSocket was chosen because it supports full‑duplex communication, allowing the server to initiate messages.

What is WebSocket?

WebSocket is a TCP‑based protocol that provides a persistent, full‑duplex channel between client and server. Unlike HTTP, which is request‑response (single‑duplex), WebSocket lets the server actively send data, making it suitable for push notifications and real‑time chat.

Why not HTTP?

HTTP requires the client to initiate every request, which is cumbersome for scenarios where the server must broadcast updates without a preceding client request. WebSocket solves this by establishing a persistent connection.

Step‑by‑Step Integration

1. Add Maven Dependency

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

2. Enable WebSocket Support in Spring Boot

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

/**
 * @author 马超伟
 * @date 2020/06/16
 * @description Enable WebSocket support
 */
@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

3. Core Configuration – WebSocketServer

The server endpoint is defined as a class annotated with @ServerEndpoint. It maintains a thread‑safe CopyOnWriteArraySet of active connections and a static online‑user counter.

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;

/**
 * @author 马超伟
 * @date 2020/06/16
 * @description WebSocket server endpoint
 */
@Component
@Slf4j
@Service
@ServerEndpoint("/api/websocket/{sid}")
public class WebSocketServer {
    // Thread‑safe online counter
    private static int onlineCount = 0;
    // Store each client connection
    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
    private Session session;
    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 connection: " + sid + ", online: " + getOnlineCount());
        } catch (IOException e) {
            log.error("WebSocket IO Exception");
        }
    }

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

    /** Message received from client */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("Received from sid " + sid + ": " + message);
        // Broadcast to all connected clients
        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‑initiated push */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }

    public static void sendInfo(String message, @PathParam("sid") String sid) throws IOException {
        log.info("Push to sid " + sid + ", content: " + message);
        for (WebSocketServer item : webSocketSet) {
            if (sid == null) {
                // broadcast to all
                // item.sendMessage(message);
            } else if (item.sid.equals(sid)) {
                item.sendMessage(message);
            }
        }
    }

    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;

/**
 * @author 马超伟
 * @date 2020/06/16
 */
@Controller("web_Scoket_system")
@RequestMapping("/api/socket")
public class SystemController {
    // Render test page
    @GetMapping("/index/{userId}")
    public ModelAndView socket(@PathVariable String userId) {
        ModelAndView mav = new ModelAndView("/socket1");
        mav.addObject("userId", userId);
        return mav;
    }

    // Push data endpoint
    @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 Backend WebSocket Demo</title>
    <script src="js/jquery.min.js" type="text/javascript"></script>
</head>
<body>
    <div id="main" style="width:1200px;height:800px;"></div>
    Welcome<br/>
    <input id="text" type="text"/>
    <button onclick="send()">Send Message</button>
    <hr/>
    <button onclick="closeWebSocket()">Close WebSocket</button>
    <hr/>
    <div id="message"></div>
    <script type="text/javascript">
        var websocket = null;
        if ('WebSocket' in window) {
            // Replace with actual server address
            websocket = new WebSocket("ws://192.168.100.196:8082/api/websocket/100");
        } else {
            alert('Current browser does not support WebSocket');
        }
        websocket.onerror = function () { setMessageInnerHTML("WebSocket error"); };
        websocket.onopen = function () { setMessageInnerHTML("WebSocket opened"); };
        websocket.onmessage = function (event) {
            console.log(event);
            setMessageInnerHTML(event.data);
        };
        websocket.onclose = function () { setMessageInnerHTML("WebSocket closed"); };
        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 + "&#13;");
        }
    </script>
</body>
</html>

6. Result Demonstration

When a client connects, the backend logs the connection and increments the online counter. The front‑end page displays incoming messages in real time. Screenshots (omitted here) show the server console and the browser UI.

Summary of a Common Pitfall

The WebSocket endpoint is instantiated before the Spring container, so injecting Spring services directly into WebSocketServer can cause NullPointerException. The workaround is to obtain required beans statically (e.g., via ApplicationContext) and assign them after the Spring context is ready, as illustrated in the accompanying configuration diagrams.

backendJavareal-timeWebSocketMessagingSpringBoot
Java Web Project
Written by

Java Web Project

Focused on Java backend technologies, trending internet tech, and the latest industry developments. The platform serves over 200,000 Java developers, inviting you to learn and exchange ideas together. Check the menu for Java learning resources.

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.