Backend Development 11 min read

Building a Netty + WebSocket Message Push Server in Java

This article presents a step‑by‑step guide to building a Netty‑based WebSocket server in Java, covering server initialization, channel configuration, custom handlers, push‑message services, testing procedures, and includes complete source code, while also containing promotional material for unrelated AI services.

Top Architect
Top Architect
Top Architect
Building a Netty + WebSocket Message Push Server in Java

The author, a senior architect, introduces Netty as a popular NIO framework and demonstrates how to create a simple Netty + WebSocket message‑push application.

Netty Server

@Component
public class NettyServer {
    static final Logger log = LoggerFactory.getLogger(NettyServer.class);
    /**
     * Port number
     */
    @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);
            // Set NIO channel type
            bootstrap.channel(NioServerSocketChannel.class);
            // Set listening port
            bootstrap.localAddress(new InetSocketAddress(port));
            // Set pipeline
            bootstrap.childHandler(nettyInitializer);
            // Bind server and wait until successful
            ChannelFuture channelFuture = null;
            try {
                channelFuture = bootstrap.bind().sync();
                log.info("Server started and listen on:{}", channelFuture.channel().localAddress());
                // Listen for channel close
                channelFuture.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
    /**
     * Release resources
     */
    @PreDestroy
    public void destroy() throws InterruptedException {
        if (bossGroup != null) {
            bossGroup.shutdownGracefully().sync();
        }
        if (workGroup != null) {
            workGroup.shutdownGracefully().sync();
        }
    }
}

Netty Configuration

public class NettyConfig {
    /**
     * Global singleton channel group managing all channels
     */
    private static volatile ChannelGroup channelGroup = null;
    /**
     * Map of request ID to channel
     */
    private static volatile ConcurrentHashMap
channelMap = null;
    /**
     * Two lock objects
     */
    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
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
{
    /** WebSocket protocol name */
    static final String WEBSOCKET_PROTOCOL = "WebSocket";
    /** WebSocket path */
    @Value("${webSocket.netty.path:/webSocket}")
    String webSocketPath;
    @Autowired
    WebSocketHandler webSocketHandler;
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        // Set pipeline
        ChannelPipeline pipeline = socketChannel.pipeline();
        // HTTP codec (WebSocket is based on HTTP)
        pipeline.addLast(new HttpServerCodec());
        pipeline.addLast(new ObjectEncoder());
        // Chunked writer
        pipeline.addLast(new ChunkedWriteHandler());
        pipeline.addLast(new HttpObjectAggregator(8192));
        pipeline.addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 10));
        // Custom business handler
        pipeline.addLast(webSocketHandler);
    }
}

Custom Handler

@Component
@ChannelHandler.Sharable
public class WebSocketHandler extends SimpleChannelInboundHandler
{
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);
    /** Called when a new connection is added */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        log.info("New client connected: [{}]", ctx.channel().id().asLongText());
        NettyConfig.getChannelGroup().add(ctx.channel());
    }
    /** Read incoming data */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        log.info("Server received message: {}", msg.text());
        JSONObject jsonObject = JSONUtil.parseObj(msg.text());
        String uid = jsonObject.getStr("uid");
        NettyConfig.getChannelMap().put(uid, ctx.channel());
        AttributeKey
key = AttributeKey.valueOf("userId");
        ctx.channel().attr(key).setIfAbsent(uid);
        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();
    }
    /** Remove user‑channel mapping */
    private void removeUserId(ChannelHandlerContext ctx) {
        AttributeKey
key = AttributeKey.valueOf("userId");
        String userId = ctx.channel().attr(key).get();
        NettyConfig.getChannelMap().remove(userId);
    }
}

Push Message Service and Implementation

public interface PushMsgService {
    /** Push to a specific user */
    void pushMsgToOne(String userId, String msg);
    /** Push 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

The article includes screenshots showing how to start the server, connect a client, send messages, and invoke the push‑message API to broadcast to front‑end clients.

Promotional Content

Following the technical tutorial, the author adds several promotional sections advertising private AI consulting services, paid ChatGPT accounts, a knowledge‑sharing community, and various discount offers. These sections are unrelated to the Netty tutorial.

backendJavaSpringNettymessage-pushWebSocket
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login 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.