Building a Java WebSocket Chat Server and Client with SpringBoot

This guide walks through creating a Java WebSocket chat application, covering a SpringBoot server implementation, client setup using java‑websocket, configuration pitfalls, a bean‑based scheduler, and a test script that simulates multiple users joining, messaging, and leaving the chat room.

FunTester
FunTester
FunTester
Building a Java WebSocket Chat Server and Client with SpringBoot

To test a WebSocket‑based chatroom, a Java solution was built using the org.java_websocket.client.WebSocketClient library for the client and a Spring Boot server that exposes a WebSocket endpoint with the @ServerEndpoint annotation.

Spring Boot WebSocket Server

@Component
@ServerEndpoint("/ws/{username}")
public class SocketS {
    private static final Logger logger = LoggerFactory.getLogger(SocketS.class);
    private static final Map<String, SocketS> clients = new ConcurrentHashMap<>();
    private Session session;
    private String username;

    @OnOpen
    public void onOpen(@PathParam("username") String username, Session session) throws IOException {
        this.username = username;
        this.session = session;
        clients.put(username, this);
        logger.info("用户:{}已连接", username);
        sendMessageAll("用户:" + username + "已经上线了!");
    }

    @OnClose
    public void onClose() throws IOException {
        clients.remove(username);
        sendMessageAll("用户:" + username + "已经离线了!");
    }

    @OnMessage
    public void onMessage(String message) throws IOException {
        logger.info(message);
        sendMessageAll(this.username + ":" + message);
    }

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

    public void sendMessageTo(String message, String to) throws IOException {
        for (SocketS item : clients.values()) {
            if (item.username.equals(to)) {
                synchronized (item.session) {
                    item.session.getBasicRemote().sendText(message);
                }
            }
        }
    }

    public void sendMessageAll(String message) throws IOException {
        for (SocketS item : clients.values()) {
            synchronized (item.session) {
                item.session.getBasicRemote().sendText("世界喊话器      " + message + "   ");
            }
        }
    }
}

Annotation Conflict and Fix

Adding both @EnableWebSocket and @EnableScheduling to the main application class caused a startup failure because the two annotations try to create incompatible beans (e.g., defaultSockJsTaskScheduler). The fix is to remove the annotations from the entry class and provide the required beans in separate configuration classes.

Scheduled Task Configuration

package com.okay.family.common;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

@Configuration
public class ScheduledConfig {
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10);
        scheduler.initialize();
        return scheduler;
    }
}

WebSocket Configuration

package com.okay.family.common;

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

@Configuration
@EnableWebSocket
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

Java WebSocket Client and Test Harness

The client extends WebSocketClient and implements the standard lifecycle callbacks. A test harness creates three threads, each representing a distinct user that connects, sends a greeting, waits a random interval, sends a farewell, and then disconnects.

package com.fun.ztest;

import com.fun.frame.SourceCode;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.enums.ReadyState;
import org.java_websocket.handshake.ServerHandshake;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicInteger;

public class SocketTest extends WebSocketClient {
    private static final Logger logger = LoggerFactory.getLogger(SocketTest.class);

    public SocketTest(String url) throws URISyntaxException {
        super(new URI(url));
    }

    public static SocketTest getInstance(String url) {
        try {
            return new SocketTest(url);
        } catch (URISyntaxException e) {
            logger.error("Failed to create WebSocketClient", e);
            return null;
        }
    }

    @Override
    public void onOpen(ServerHandshake handshake) {
        logger.info("Opening WebSocket connection");
        handshake.iterateHttpFields().forEachRemaining(key ->
            logger.debug(key + ":" + handshake.getFieldValue(key)));
    }

    @Override
    public void onMessage(String message) {
        logger.warn(message);
    }

    @Override
    public void onClose(int code, String reason, boolean remote) {
        logger.info("WebSocket closed");
    }

    @Override
    public void onError(Exception ex) {
        logger.error("WebSocket error", ex);
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Message());
        Thread t2 = new Thread(new Message());
        Thread t3 = new Thread(new Message());
        t1.start();
        t2.start();
        t3.start();
        t1.join();
        t2.join();
        t3.join();
    }

    static class Message extends SourceCode implements Runnable {
        private static final AtomicInteger counter = new AtomicInteger(1);
        private final String name;
        private final String url;
        private final SocketTest client;

        Message() {
            this.name = "User" + counter.getAndIncrement();
            this.url = "ws://127.0.0.1:8080/ws/" + name;
            this.client = SocketTest.getInstance(url);
            output(name);
        }

        @Override
        public void run() {
            try {
                client.connect();
                // Wait until the connection is open
                while (!client.getReadyState().equals(ReadyState.OPEN)) {
                    SourceCode.sleep(2000);
                    logger.warn("Connection not yet open, retrying...");
                    client.reconnect();
                }
                logger.info("WebSocket connection established");
                client.send("我是" + name);
                sleep(getRandomInt(5));
                client.send("我有事先走了!!!");
                sleep(getRandomInt(5));
            } catch (Exception e) {
                logger.error("Exception in client thread", e);
            } finally {
                client.close();
            }
        }
    }
}

Sample Console Output (trimmed)

INFO -> 开始建立socket连接...
WARN -> 世界喊话器      用户:User2已经上线了!
INFO -> 建立websocket连接
WARN -> 世界喊话器      User2:我是User2
WARN -> 世界喊话器      User2:我有事先走了!!!
WARN -> 世界喊话器      用户:User2已经离线了!
INFO -> socket关闭...

Key Takeaways

Using org.java_websocket.client.WebSocketClient provides a lightweight client implementation that can be wrapped for custom behavior.

Spring Boot’s @ServerEndpoint together with a dedicated ServerEndpointExporter enables a standards‑compliant WebSocket server without additional servlet configuration.

Annotations @EnableWebSocket and @EnableScheduling must not be placed on the same class; separating their bean definitions avoids BeanNotOfRequiredTypeException at startup.

The test harness demonstrates concurrent connections, message broadcasting, and graceful shutdown, which are essential for validating a chatroom service.

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.

JavatestingWebSocketSpringBootServerclient
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

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.