On this page
Netty Pipeline
The ChannelPipeline is the core of Netty’s extensibility — a chain of handlers that process inbound and outbound data, enabling protocol encoding/decoding, business logic, and cross-cutting concerns.
Pipeline Structure
Inbound: Socket → [Decoder] → [Business Handler] → [Inbound Adapter]
Outbound: Application → [Encoder] → [Outbound Adapter] → Socket
Each handler processes specific event types and passes to the next handler.
Handler Types
| Type | Direction | Purpose |
|---|---|---|
ChannelInboundHandlerAdapter |
Inbound | Read events |
ChannelOutboundHandlerAdapter |
Outbound | Write events |
SimpleChannelInboundHandler<T> |
Inbound | Typed message handling |
MessageToMessageDecoder |
Inbound | Transform messages |
MessageToByteEncoder |
Outbound | Serialize to bytes |
Custom Protocol Example
Protocol: [4-byte length][JSON payload]
// Decoder: bytes → JSON object
public class JsonFrameDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
if (in.readableBytes() < 4) return;
in.markReaderIndex();
int length = in.readInt();
if (in.readableBytes() < length) {
in.resetReaderIndex();
return;
}
byte[] data = new byte[length];
in.readBytes(data);
OrderMessage msg = objectMapper.readValue(data, OrderMessage.class);
out.add(msg);
}
}
// Encoder: JSON object → bytes
public class JsonFrameEncoder extends MessageToByteEncoder<OrderMessage> {
@Override
protected void encode(ChannelHandlerContext ctx, OrderMessage msg, ByteBuf out)
throws Exception {
byte[] data = objectMapper.writeValueAsBytes(msg);
out.writeInt(data.length);
out.writeBytes(data);
}
}
Pipeline Setup
ch.pipeline()
.addLast("frameDecoder", new JsonFrameDecoder())
.addLast("frameEncoder", new JsonFrameEncoder())
.addLast("idleStateHandler", new IdleStateHandler(60, 0, 0))
.addLast("businessHandler", new OrderHandler());
Business Handler
public class OrderHandler extends SimpleChannelInboundHandler<OrderMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, OrderMessage msg) {
switch (msg.getType()) {
case CREATE -> handleCreate(ctx, msg);
case CANCEL -> handleCancel(ctx, msg);
default -> ctx.writeAndFlush(new OrderMessage("ERROR", "Unknown type"));
}
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
if (evt instanceof IdleStateEvent) {
ctx.writeAndFlush(new OrderMessage("PING", ""));
}
}
}
Built-in Codecs
// HTTP
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new HttpObjectAggregator(65536));
// WebSocket
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
// Protobuf
pipeline.addLast(new ProtobufVarint32FrameDecoder());
pipeline.addLast(new ProtobufDecoder(OrderMessage.getDefaultInstance()));
// SSL/TLS
pipeline.addFirst("ssl", SslContextBuilder.forServer(cert, key).build().newHandler(ch.alloc()));
// Logging
pipeline.addFirst("logging", new LoggingHandler(LogLevel.DEBUG));
Sharable Handlers
Stateless handlers can be shared across channels:
@ChannelHandler.Sharable
public class AuthenticationHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (isAuthenticated(ctx)) {
ctx.fireChannelRead(msg); // pass to next handler
} else {
ctx.close();
}
}
}
// Reuse same instance
AuthenticationHandler authHandler = new AuthenticationHandler();
bootstrap.childHandler(new ChannelInitializer<>() {
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(authHandler); // shared instance OK
}
});
Dynamic Pipeline Modification
Add/remove handlers at runtime:
// After authentication, add encryption
ChannelPipeline pipeline = ctx.pipeline();
pipeline.addAfter("auth", "encryption", new EncryptionHandler());
// Remove handler after handshake
pipeline.remove("handshakeHandler");
Best Practices
- Order matters — decoders before business logic, encoders after
- Use
@Sharableonly for truly stateless handlers - Add idle state handler to detect and close dead connections
- Use
SimpleChannelInboundHandlerfor typed messages — automatic release - Separate protocol handling (codec) from business logic (handler)
- Add SSL handler first in the pipeline (before any data processing)