Practical Spring Boot + Netty + WebSocket Message Push

This article walks through building a simple Netty server integrated with Spring Boot and WebSocket, showing how to configure the server, manage channel groups, implement custom handlers, and expose a service that can push messages to individual users or broadcast to all connected clients.

IoT Full-Stack Technology
IoT Full-Stack Technology
IoT Full-Stack Technology
Practical Spring Boot + Netty + WebSocket Message Push

Netty provides a convenient wrapper around Java NIO with a simple API and a large open‑source community. This guide demonstrates a basic Netty + WebSocket message‑push example built on Spring Boot.

Netty Server

@Component
public class NettyServer {
    static final Logger log = LoggerFactory.getLogger(NettyServer.class);
    @Value("${webSocket.netty.port:8888}")
    int port;
    EventLoopGroup bossGroup;
    EventLoopGroup workGroup;
    @Autowired
    ProjectInitializer nettyInitializer;

    @PostConstruct
    public void start() throws InterruptedException {
        new Thread(() -> {
            bossGroup = new NioEventLoopGroup();
            workGroup = new NioEventLoopGroup();
            ServerBootstrap bootstrap = new ServerBootstrap();
            // bossGroup handles TCP connection requests, workGroup handles read/write
            bootstrap.group(bossGroup, workGroup);
            // use NIO channel type
            bootstrap.channel(NioServerSocketChannel.class);
            // bind listening port
            bootstrap.localAddress(new InetSocketAddress(port));
            // set pipeline
            bootstrap.childHandler(nettyInitializer);
            // bind server and block until success
            ChannelFuture channelFuture = null;
            try {
                channelFuture = bootstrap.bind().sync();
                log.info("Server started and listen on:{}", channelFuture.channel().localAddress());
                // wait for channel close
                channelFuture.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }

    @PreDestroy
    public void destroy() throws InterruptedException {
        if (bossGroup != null) {
            bossGroup.shutdownGracefully().sync();
        }
        if (workGroup != null) {
            workGroup.shutdownGracefully().sync();
        }
    }
}

Netty Configuration

public class NettyConfig {
    /** Define a global singleton channel group to manage all channels */
    private static volatile ChannelGroup channelGroup = null;
    /** Map request ID to channel */
    private static volatile ConcurrentHashMap<String, Channel> channelMap = null;
    /** Two locks for lazy initialization */
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static ChannelGroup getChannelGroup() {
        if (channelGroup == null) {
            synchronized (lock1) {
                if (channelGroup == null) {
                    channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
                }
            }
        }
        return channelGroup;
    }

    public static ConcurrentHashMap<String, Channel> getChannelMap() {
        if (channelMap == null) {
            synchronized (lock2) {
                if (channelMap == null) {
                    channelMap = new ConcurrentHashMap<>();
                }
            }
        }
        return channelMap;
    }

    public static Channel getChannel(String userId) {
        if (channelMap == null) {
            return getChannelMap().get(userId);
        }
        return channelMap.get(userId);
    }
}

Pipeline Configuration

@Component
public class ProjectInitializer extends ChannelInitializer<SocketChannel> {
    static final String WEBSOCKET_PROTOCOL = "WebSocket";
    @Value("${webSocket.netty.path:/webSocket}")
    String webSocketPath;
    @Autowired
    WebSocketHandler webSocketHandler;

    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        // HTTP codec for the WebSocket handshake
        pipeline.addLast(new HttpServerCodec());
        pipeline.addLast(new ObjectEncoder());
        // Chunked writer
        pipeline.addLast(new ChunkedWriteHandler());
        pipeline.addLast(new HttpObjectAggregator(8192));
        // WebSocket protocol handler
        pipeline.addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 10));
        // Custom business logic handler
        pipeline.addLast(webSocketHandler);
    }
}

Custom Handler

@Component
@ChannelHandler.Sharable
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        log.info("New client connected: [{}]", ctx.channel().id().asLongText());
        NettyConfig.getChannelGroup().add(ctx.channel());
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        log.info("Server received message: {}", msg.text());
        // Parse user ID and bind channel
        JSONObject jsonObject = JSONUtil.parseObj(msg.text());
        String uid = jsonObject.getStr("uid");
        NettyConfig.getChannelMap().put(uid, ctx.channel());
        AttributeKey<String> key = AttributeKey.valueOf("userId");
        ctx.channel().attr(key).setIfAbsent(uid);
        // Echo response
        ctx.channel().writeAndFlush(new TextWebSocketFrame("Server received your message"));
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        log.info("User offline: [{}]", ctx.channel().id().asLongText());
        NettyConfig.getChannelGroup().remove(ctx.channel());
        removeUserId(ctx);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.info("Exception: {}", cause.getMessage());
        NettyConfig.getChannelGroup().remove(ctx.channel());
        removeUserId(ctx);
        ctx.close();
    }

    private void removeUserId(ChannelHandlerContext ctx) {
        AttributeKey<String> key = AttributeKey.valueOf("userId");
        String userId = ctx.channel().attr(key).get();
        NettyConfig.getChannelMap().remove(userId);
    }
}

Push Message Service Interface and Implementation

public interface PushMsgService {
    /** Push to a specific user */
    void pushMsgToOne(String userId, String msg);
    /** Broadcast to all users */
    void pushMsgToAll(String msg);
}

@Service
public class PushMsgServiceImpl implements PushMsgService {
    @Override
    public void pushMsgToOne(String userId, String msg) {
        Channel channel = NettyConfig.getChannel(userId);
        if (Objects.isNull(channel)) {
            throw new RuntimeException("Socket server not connected");
        }
        channel.writeAndFlush(new TextWebSocketFrame(msg));
    }

    @Override
    public void pushMsgToAll(String msg) {
        NettyConfig.getChannelGroup().writeAndFlush(new TextWebSocketFrame(msg));
    }
}

Testing Steps

1. Start the Spring Boot application; the Netty server will bind to the configured port (default 8888).

2. Open the provided HTML page (shown in the images) to establish a WebSocket connection to ws://{host}:{port}/webSocket.

3. Send a JSON payload containing a uid field, e.g. {"uid":"user1","msg":"hello"}. The server logs the connection and stores the channel in the global map.

4. Call the pushMsgToOne or pushMsgToAll methods (e.g., via a REST controller) to push messages back to the client(s).

5. Observe the client receiving the echoed message and the server logs confirming successful push.

All steps complete a simple Netty‑WebSocket message‑push demo.

Image 1
Image 1
Image 2
Image 2
Image 3
Image 3
Image 4
Image 4
Image 5
Image 5
Image 6
Image 6
Image 7
Image 7

Result: a simple Netty WebSocket push example is completed.

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.

backendJavaNettymessage pushSpring Bootwebsocket
IoT Full-Stack Technology
Written by

IoT Full-Stack Technology

Dedicated to sharing IoT cloud services, embedded systems, and mobile client technology, with no spam ads.

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.