← Back to list

Spring Boot WebSockets & SimpUserRegistry

Spring Boot 4.0.0 — How to use SimpUserRegistry & StompJs@7.2.0 connectHeaders

Willy De Keyser in JavaToDev · 2025-10-18 10:21 · 10 claps · 2.7 min read
#spring-boot #websocket #stomp #stompjs #spring
Open on Medium ↗
Wiki topics: 🔒 · Cybersecurity

Spring Boot WebSockets & SimpUserRegistry

Spring Boot 4.0.0 — How to use SimpUserRegistry & StompJs@7.2.0 connectHeaders.

Youtube: after 05 november 2025

A simple example

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>4.0.0-SNAPSHOT</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>willydekeyser</groupId>
    <artifactId>websockets_username</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>websockets_username</name>
    <description>websockets_username</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>25</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webmvc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <releases>
                <enabled>false</enabled>
            </releases>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <releases>
                <enabled>false</enabled>
            </releases>
        </pluginRepository>
    </pluginRepositories>

</project>

WebSocketConfiguration:

package willydekeyser.websockets_username.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/topic");
        registry.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/chat");
    }

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(new UserNameChannelInterceptor());
    }
}

UserNameChannelInterceptor:

package willydekeyser.websockets_username.config;

import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.stereotype.Component;

@Component
public class UserNameChannelInterceptor implements ChannelInterceptor {

    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {

        StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
        assert accessor != null;
        if (StompCommand.CONNECT.equals(accessor.getCommand())) {
            String login = accessor.getFirstNativeHeader("login");
            accessor.setUser(() -> login);
        }
        return message;
    }
}

WebSocketService:

package willydekeyser.websockets_username.service;

import org.springframework.messaging.simp.user.SimpUserRegistry;
import org.springframework.stereotype.Service;
import willydekeyser.websockets_username.model.Session;
import willydekeyser.websockets_username.model.User;
import willydekeyser.websockets_username.model.Users;

import java.util.List;

@Service
public class WebSocketService {

    private final SimpUserRegistry simpUserRegistry;

    public WebSocketService(SimpUserRegistry simpUserRegistry) {
        this.simpUserRegistry = simpUserRegistry;
    }

    public Users getUsers() {
        return new Users(simpUserRegistry.getUsers().size(),
                simpUserRegistry.getUsers().stream()
                        .map(user -> new User(user.getName(),
                                user.getSessions().size(),
                                user.getSessions().stream()
                                        .map(session -> new Session(session.getId(),
                                                session.getSubscriptions().size(),
                                                session.getSubscriptions().stream()
                                                        .map(subscription ->
                                                                subscription.getId() + " - " +
                                                                        subscription.getDestination())
                                                        .toList()))
                                        .toList()
                        ))
                        .toList()
        );
    }
}

WebSocketController:

package willydekeyser.websockets_username.controller;

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import willydekeyser.websockets_username.model.Users;
import willydekeyser.websockets_username.service.WebSocketService;

@Controller
public class WebSocketController {

    private final WebSocketService webSocketService;

    public WebSocketController(WebSocketService webSocketService) {
        this.webSocketService = webSocketService;
    }

    @MessageMapping("/message")
    @SendTo("/topic/greetings")
    String greet(String request) {
        return "Hello, " + request;
    }

    @MessageMapping("/user")
    @SendTo("/topic/users")
    Users users() {
        return webSocketService.getUsers();
    }

}

Model:

Users & Sessions

package willydekeyser.websockets_username.model;

import java.util.List;

public record Users(
        Integer number_of_users,
        List<User> name
) {}
package willydekeyser.websockets_username.model;

import java.util.List;

public record User(
        String user,
                   Integer number_of_sessions,
                   List<Session> sessions) {
}
package willydekeyser.websockets_username.model;

import java.util.List;

public record Session(
        String session,
        Integer number_of_subscriptions,
        List<String> subscriptions
) {
}

main.js:

'use strict';
let userName;
let url;
let client;
let inputUsername
let buttonConnect;
let buttonDisConnect;
let users_log;
let messages_log;

function connectWebSocket(username) {
    url = "ws://localhost:8080/chat";
    client = new StompJs.Client({
        brokerURL: url,
        connectHeaders: {
            login: username,
        },
        debug: (str) => console.log(str),
    })

    client.onConnect = () => {
        client.subscribe('/topic/greetings', (greeting) => {
            printMessage(messages_log, greeting.body);
        });
        client.subscribe('/topic/users', (user) => {
            printMessage(users_log, user.body);
        }, {});
        client.publish({
            destination: '/app/message',
            body: 'Spring Boot Tutorial',
        });
        client.publish({
            destination: '/app/user',
        });
    };

    client.onWebSocketError = (error) => {
        console.error('Error with websocket', error);
    };

    client.onStompError = (frame) => {
        console.error('Broker reported error: ' + frame.headers['message']);
        console.error('Additional details: ' + frame.body);
    };
}

window.addEventListener('DOMContentLoaded', () => {
    buttonConnect = document.getElementById("connect");
    buttonDisConnect = document.getElementById("disconnect");
    inputUsername = document.getElementById("username");
    users_log = document.getElementById("users_log");
    messages_log = document.getElementById("messages_log");

    buttonConnect.disabled = false;
    buttonDisConnect.disabled = true;
})

function connect() {
    if (inputUsername.value === '') {
        alert("UserName is empty");
        return;
    }
    clearMessage(users_log);
    clearMessage(messages_log);
    buttonConnect.disabled = true;
    buttonDisConnect.disabled = false;
    userName = inputUsername.value;
    connectWebSocket(userName);
    client.activate();
}

function disconnect() {
    client.deactivate();
    buttonConnect.disabled = false;
    buttonDisConnect.disabled = true;
    clearMessage(users_log);
    clearMessage(messages_log);
}

function printMessage(textId, data) {
    textId.innerHTML += data + "<br/>";
}

function clearMessage(textId) {
    console.log('clearMessage', textId);
    textId.innerHTML = "";
}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Spring Boot Tutorial</title>
    <script src="https://cdn.jsdelivr.net/npm/@stomp/stompjs@7.2.0/bundles/stomp.umd.min.js"></script>
    <script src="main.js"></script>
</head>
<body>
<h1>Spring Boot Tutorial</h1>
<label for="username">Username: </label><input type="text" id="username" placeholder="Username here..." size="50" onfocus=this.value='' /><br/><br/>
<input id="connect" type="button" onclick="connect()" value="Connect" />
<input id="disconnect" type="button" onclick="disconnect()" value="Disconnect" /><br/><br/>
<p>Users: <br/> <span  id="users_log"></span></p>
<p>Messages:<br/> <span  id="messages_log"></span></p>
</body>
</html>

GitHub: https://github.com/wdkeyser02/websockets_username

Willy De Keyser


메타데이터
post_id
3ef7f6205301
slug
spring-boot-websockets-simpuserregistry-3ef7f6205301
url
https://medium.com/@wdkeyser/spring-boot-websockets-simpuserregistry-3ef7f6205301
canonical_url
https://medium.com/@wdkeyser/spring-boot-websockets-simpuserregistry-3ef7f6205301
author_url
https://medium.com/@wdkeyser
status
ok
fetched_at
2026-07-16 14:59:26