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.
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.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.