Backend Development 10 min read

Building a Netty + WebSocket Message Push Server in Java

This article demonstrates how to build a Netty‑based WebSocket server for message pushing in Java, provides complete source code for the server, configuration, pipeline, custom handler, and push service, and also contains promotional material for AI services and a community group.

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

In this tutorial the author, a senior architect, introduces Netty and its advantages for NIO, then walks through the creation of a simple Netty + WebSocket message‑push application.

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 connection requests, workGroup handles I/O
            bootstrap.group(bossGroup, workGroup);
            bootstrap.channel(NioServerSocketChannel.class);
            bootstrap.localAddress(new InetSocketAddress(port));
            bootstrap.childHandler(nettyInitializer);
            ChannelFuture channelFuture = null;
            try {
                channelFuture = bootstrap.bind().sync();
                log.info("Server started and listen on:{}", channelFuture.channel().localAddress());
                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 {
    private static volatile ChannelGroup channelGroup = null;
    private static volatile ConcurrentHashMap
channelMap = null;
    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
{
    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();
        pipeline.addLast(new HttpServerCodec());
        pipeline.addLast(new ObjectEncoder());
        pipeline.addLast(new ChunkedWriteHandler());
        pipeline.addLast(new HttpObjectAggregator(8192));
        pipeline.addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 10));
        pipeline.addLast(webSocketHandler);
    }
}

Custom WebSocket Handler

@Component
@ChannelHandler.Sharable
public class WebSocketHandler extends SimpleChannelInboundHandler
{
    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: {}", 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();
    }
    private void removeUserId(ChannelHandlerContext ctx) {
        AttributeKey
key = AttributeKey.valueOf("userId");
        String userId = ctx.channel().attr(key).get();
        NettyConfig.getChannelMap().remove(userId);
    }
}

Push Message Service

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 and Demonstration

The article includes screenshots showing how to start the server, connect clients, send messages, and verify that messages are pushed to the front‑end.

Promotional Content

After the technical tutorial the author adds extensive promotional material for a ChatGPT‑related community, private‑account services, and various paid offerings, including pricing, benefits, and links to external resources.

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