Environment: Windows 10, JDK 17, Spring Boot 3
Introduction to Netty
Nety is an asynchronous, event-driven network application framework for Java that simplifies the development of high-performance, reliible network servers and clients. It supports multiple protocols including TCP, UDP, and HTTP, and abstracts low-level networking complexities through its pipeline-based architecture.
Implementation Guide
Dependency Configuration
Add the Netty dependency to your pom.xml:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.84.Final</version>
</dependency>
If using Spring Data Redis, exclude its bundled Netty modules to avoid version conflicts:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-common</artifactId>
</exclusion>
</exclusions>
</dependency>
Netty Server Bootstrap
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.net.InetSocketAddress;
@Slf4j
@Component
public class NettyServer {
public void start(InetSocketAddress address) {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap()
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.localAddress(address)
.childHandler(new NettyChannelInitializer())
.option(ChannelOption.SO_BACKLOG, 12800)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = bootstrap.bind(address).sync();
log.info("Netty server listening on port: {}", address.getPort());
future.channel().closeFuture().sync();
} catch (Exception e) {
log.error("Server startup failed", e);
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
Channel Pipeline Initialization
import io.netty.channel.ChannelInitializer;
import io.netty.channel.FixedRecvByteBufAllocator;
import io.netty.channel.socket.SocketChannel;
public class NettyChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) {
ch.config().setRecvByteBufAllocator(new FixedRecvByteBufAllocator(2048));
ch.pipeline()
.addLast("decoder", new HexMessageDecoder())
.addLast("encoder", new HexMessageEncoder())
.addLast(new MessageForwardingHandler());
}
}
Custom Decoder (Hex String)
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
public class HexMessageDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
byte[] raw = new byte[in.readableBytes()];
in.readBytes(raw);
out.add(bytesToHex(raw));
}
private String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b & 0xFF));
}
return sb.toString();
}
}
Custom Encoder (Hex String)
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
public class HexMessageEncoder extends MessageToByteEncoder<String> {
@Override
protected void encode(ChannelHandlerContext ctx, String hexStr, ByteBuf out) {
out.writeBytes(hexStringToByteArray(hexStr));
}
private static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i + 1), 16));
}
return data;
}
}
Message Forwarding Handler
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelId;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.net.InetSocketAddress;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Component
public class MessageForwardingHandler extends ChannelInboundHandlerAdapter {
private static MessageForwardingHandler INSTANCE;
private static final ConcurrentHashMap<ChannelId, ClientInfo> CHANNELS = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
INSTANCE = this;
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
InetSocketAddress addr = (InetSocketAddress) ctx.channel().remoteAddress();
ClientInfo info = new ClientInfo(ctx, addr.getAddress().getHostAddress(), addr.getPort());
CHANNELS.put(ctx.channel().id(), info);
log.info("Client connected: {}:{} [{}]", info.ip, info.port, ctx.channel().id());
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
ClientInfo removed = CHANNELS.remove(ctx.channel().id());
if (removed != null) {
log.info("Client disconnected: {}:{} [{}]", removed.ip, removed.port, ctx.channel().id());
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
String payload = msg.toString();
log.info("Received from client: {}", payload);
// Forward logic can be implemented here
// e.g., forwardViaHttpClient(payload) or forwardViaNettyClient(payload)
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
if (evt instanceof IdleStateEvent) {
log.warn("Idle timeout detected, closing connection");
ctx.close();
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
log.error("Unexpected error", cause);
ctx.close();
}
public static class ClientInfo {
final ChannelHandlerContext ctx;
final String ip;
final int port;
ClientInfo(ChannelHandlerContext ctx, String ip, int port) {
this.ctx = ctx;
this.ip = ip;
this.port = port;
}
}
}
Netty-Based Forwarding Client
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ForwardingClient {
private volatile Channel channel;
private final EventLoopGroup group = new NioEventLoopGroup();
private final String targetHost;
private final int targetPort;
public ForwardingClient(String host, int port) {
this.targetHost = host;
this.targetPort = port;
connect();
}
public void forward(String message) {
if (channel == null || !channel.isActive()) {
log.warn("Reconnecting before forwarding");
connect();
}
if (channel != null && channel.isActive()) {
channel.writeAndFlush(message).addListener(future -> {
if (future.isSuccess()) {
log.debug("Forwarded: {}", message);
} else {
log.error("Forward failed", future.cause());
}
});
}
}
private void connect() {
try {
Bootstrap bootstrap = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) {
ch.pipeline()
.addLast(new StringEncoder(CharsetUtil.UTF_8))
.addLast(new StringDecoder(CharsetUtil.UTF_8));
}
});
ChannelFuture future = bootstrap.connect(targetHost, targetPort).sync();
this.channel = future.channel();
log.info("Forwarding client connected to {}:{}", targetHost, targetPort);
} catch (Exception e) {
log.error("Failed to connect forwarding client", e);
group.shutdownGracefully();
}
}
public void shutdown() {
group.shutdownGracefully();
}
}
Auto-Start Netty Server on Application Launch
@Component
public class NettyAutoStarter implements ApplicationRunner {
@Autowired
private NettyServer nettyServer;
@Value("${netty.server.host:0.0.0.0}")
private String host;
@Value("${netty.server.port:9999}")
private int port;
@Override
public void run(ApplicationArguments args) {
nettyServer.start(new InetSocketAddress(host, port));
}
}