Integrating WebSocket with Spring Boot: A Step‑by‑Step Guide

This article introduces WebSocket, explains why it is preferred over HTTP for real‑time communication, and provides a complete Spring Boot integration tutorial with Maven dependencies, configuration, server endpoint code, controller, and a simple HTML client, all illustrated with runnable examples.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Integrating WebSocket with Spring Boot: A Step‑by‑Step Guide

In a project that uses Netty and MQTT for message exchange, the need arose to push messages from the backend to the frontend, leading to the adoption of WebSocket. This article explains what WebSocket is, why it is preferred over HTTP, and provides a complete Spring Boot integration example with step‑by‑step instructions.

1. 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. This matches the requirements of push notifications and multi‑user chat applications.

Unlike HTTP, which is single‑direction and requires the client to initiate every request, WebSocket supports bidirectional communication, making it ideal for real‑time scenarios.

2. Integration Steps

2.1 Add Maven Dependency

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

2.2 Enable WebSocket Support in Spring Boot

Enable WebSocket support with a few lines of configuration code.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
 * @Author: Ma Chaowei
 * @Date: 2020/06/16/14:35
 * @Description: Enable WebSocket support
 */
@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

2.3 Core Configuration: WebSocketServer

The @ServerEndpoint annotation turns the class into a WebSocket server endpoint. A ConcurrentHashMap (or CopyOnWriteArraySet) is used to store active connections identified by sid for targeted push.

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.
 * @Author: Ma Chaowei
 * @Date: 2020/06/16/14:35
 */
@Component
@Slf4j
@Service
@ServerEndpoint("/api/websocket/{sid}")
public class WebSocketServer {
    // Current online connection count
    private static int onlineCount = 0;
    // Store each client’s WebSocketServer instance
    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();

    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 + ", current 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("A connection closed! Current online count: " + getOnlineCount());
    }

    /** Message received from client */
    @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();
            }
        }
    }

    /** Error handling */
    @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);
    }

    /** Broadcast custom message */
    public static void sendInfo(String message, @PathParam("sid") String sid) throws IOException {
        log.info("Push message to window " + sid + ", content: " + message);
        for (WebSocketServer item : webSocketSet) {
            try {
                if (sid == null) {
                    // item.sendMessage(message); // broadcast to all
                } 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; }
}

2.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: Ma Chaowei
 * @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 API
    @ResponseBody
    @RequestMapping("/socket/push/{cid}")
    public Map 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;
    }
}

2.5 Test Page (index.html)

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Java Backend WebSocket Demo</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()">Send Message</button>
    <hr/>
    <button onclick="closeWebSocket()">Close WebSocket</button>
    <hr/>
    <div id="message"></div>
  </body>
  <script type="text/javascript">
    var websocket = null;
    // Check if browser supports WebSocket
    if ('WebSocket' in window) {
      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 connection error"); };
    websocket.onopen = function() { setMessageInnerHTML("WebSocket connection opened"); };
    websocket.onmessage = function(event) { console.log(event); setMessageInnerHTML(event); setechart(); };
    websocket.onclose = function() { setMessageInnerHTML("WebSocket connection 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>
</html>

3. Result Demonstration

Backend console shows connection requests and message pushes.

Frontend displays received messages in real time.

4. Summary

During development I discovered that the WebSocket server starts before the Spring container, causing a NullPointerException when trying to inject a service into WebSocketServer. The solution is to manually obtain and assign required services as static fields inside the server class and add the necessary Spring configuration.

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.

BackendJavaSpring BootWebSocketreal-time communication
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

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.