Netty is an asynchronous event-driven network application framework that simplifies the development of high-performance protocol servers and clients. This article walks through a minimal but complete client-server communication example built with Netty 4.1, using the Reactor threading model (Main Reactor + Sub Reactors) and line-delimited string messages over TCP.
Server Implementation
The server is responsible for accepting connections, receiving messages, processing them, and sending responses back to the client. The implementation separates bootstrap configuration, channel pipeline setup, and business logic.
Bootstrap and Listening
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class AppServer {
private static final int PORT = 8090;
private static EventLoopGroup bossGroup = new NioEventLoopGroup();
private static EventLoopGroup workerGroup = new NioEventLoopGroup();
private static ServerBootstrap bootstrap = new ServerBootstrap();
public static void main(String[] args) throws InterruptedException {
try {
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ServerChannelInitializer());
ChannelFuture future = bootstrap.bind(PORT).sync();
System.out.println("Server started on port " + PORT);
future.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
Business Logic Handler
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.util.Date;
public class ServerMessageHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("Server received: " + msg);
if ("quit".equals(msg)) {
ctx.close();
return;
}
String reply = "Reply at " + new Date() + "\n";
ctx.writeAndFlush(reply);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("Remote client connected: " + ctx.channel().remoteAddress());
ctx.writeAndFlush("Welcome, you are connected from " + ctx.channel().remoteAddress() + "\n");
super.channelActive(ctx);
}
}
Channel Initializer (Pipeline Setup)
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// Frame decoder that splits messages on newline
pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast("decoder", new StringDecoder());
pipeline.addLast("encoder", new StringEncoder());
pipeline.addLast("handler", new ServerMessageHandler());
}
}
Client Implementation
The client connects to the server, sends a message, processes the response, and can send additional data.
Client Bootstrap
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.io.IOException;
public class ClientApp {
private static final String HOST = "127.0.0.1";
private static final int PORT = 8090;
private static EventLoopGroup group = new NioEventLoopGroup();
private static Bootstrap bootstrap = new Bootstrap();
private static Channel channel;
public static void main(String[] args) throws InterruptedException, IOException {
try {
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ClientChannelInitializer());
channel = bootstrap.connect(HOST, PORT).sync().channel();
sendMessage();
} finally {
// Group shutdown handled on exit
}
}
private static void sendMessage() throws IOException {
String message = "Hello from Netty client";
channel.writeAndFlush(message + "\r\n");
System.out.println("Client sent: " + message);
}
}
Client Handler
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class ClientMessageHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("Client received: " + msg);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("Connection established...");
super.channelActive(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("Connection closed.");
super.channelInactive(ctx);
}
}
Client Channel Initializer
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
public class ClientChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast("decoder", new StringDecoder());
pipeline.addLast("encoder", new StringEncoder());
pipeline.addLast("handler", new ClientMessageHandler());
}
}
Example Output
After starting the server, runing the client yields messages similar to the screenshots below.