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.
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.
Result: a simple Netty WebSocket push example is completed.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
IoT Full-Stack Technology
Dedicated to sharing IoT cloud services, embedded systems, and mobile client technology, with no spam ads.
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.
