Real‑Time Communication with Spring Boot + WebSocket: Multi‑Client Sessions, Private Chat, and Broadcast

This article demonstrates how to use Spring Boot 3.5.0 with WebSocket + STOMP to build real‑time messaging, covering environment setup, WebSocket configuration, a user‑binding interceptor, and concrete code for private‑user messages, point‑to‑point chats, and global broadcasts, complete with sample results.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Real‑Time Communication with Spring Boot + WebSocket: Multi‑Client Sessions, Private Chat, and Broadcast

1. Introduction

Real‑time scenarios such as system notifications, private chat and global announcements require low‑latency communication. Traditional polling is inefficient, while WebSocket provides a full‑duplex long‑lived connection, making it the optimal solution.

2. Environment

Spring Boot 3.5.0 is used.

3. Configuration

Include the spring-boot-starter-websocket dependency. Define a configuration class:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(new StompUserInterceptor());
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        // client subscribe prefixes: /topic (broadcast), /queue (point‑to‑point)
        config.enableSimpleBroker("/topic", "/queue");
        // client send prefix
        config.setApplicationDestinationPrefixes("/app");
        // user prefix (default /user)
        config.setUserDestinationPrefix("/user");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // register endpoint, allow cross‑origin
        registry.addEndpoint("/ws").setAllowedOriginPatterns("*").withSockJS();
    }
}

Define an interceptor that binds a username to the STOMP session:

public class StompUserInterceptor implements ChannelInterceptor {
    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
        if (accessor == null) {
            return message;
        }
        // When the client initiates CONNECT
        if (StompCommand.CONNECT.equals(accessor.getCommand())) {
            String username = accessor.getFirstNativeHeader("username");
            if (username != null) {
                accessor.setUser(new Principal() {
                    @Override
                    public String getName() {
                        return username;
                    }
                });
            }
        }
        return message;
    }
}

4. Message Sending

4.1 Specific‑User Message

Backend method:

@MessageMapping("/message")
@SendToUser("/queue/replay")
public String chat(String message, Principal principal) {
    System.err.println(String.format("[%s] received: %s", principal.getName(), message));
    return String.format("Reply: %s", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
}

Front‑end (JavaScript):

let username = ...;
const socket = new SockJS(`http://localhost:8080/ws?username=${username}`);
const client = Stomp.over(socket);
const headers = { username };
client.connect(headers, frame => {
    client.subscribe('/user/queue/replay', message => {
        console.log(`Specific‑user message: ${message.body}`);
    });
});

4.2 Point‑to‑Point (Private) Message

Backend endpoint:

@GetMapping("/push/user")
@ResponseBody
public String sendToUser(@RequestParam String targetUsername, @RequestParam String msg) {
    messagingTemplate.convertAndSendToUser(targetUsername, "/queue/private", msg);
    return "Send success";
}

Front‑end subscription:

client.subscribe('/user/queue/private', message => {
    console.log(`Point‑to‑point message: ${message.body}`);
});

4.3 Broadcast Message

Backend method:

@MessageMapping("/broadcast")
@SendTo("/topic/broadcast")
public String broadcast(String message) {
    return String.format("%s", message);
}

Front‑end subscription:

client.subscribe('/topic/broadcast', message => {
    console.log(`Broadcast message: ${message.body}`);
});

5. Results

Screenshots show that multiple browsers logged in with the same username receive the same messages (multi‑client session). Separate users only receive messages addressed to them, while broadcast messages are delivered to every connected client.

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.

JavaSpring Bootwebsocketreal-time-communicationbroadcaststompprivate-chat
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

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.