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