ChannelHandler and ChannelPipeline
This chapter covers
- The
ChannelHandlerandChannelPipelineAPIs - Detecting resource leaks
- Exception handling
The ChannelHandler family
The Channel lifecycle
Interface Channel defines a simple but powerful state model that’s closely related to the ChannelInboundHandler API. The four Channel states are listed in the following table.
The normal lifecycle of a Channel is shown in figure 6.1.

The ChannelHandler lifecycle
The lifecycle operations defined by interface ChannelHandler, listed in the following table, are called after a ChannelHandler has been added to, or removed from, a ChannelPipeline.
Netty defines the following two important subinterfaces of ChannelHandler:
ChannelInboundHandler— Processes inbound data and state changes of all kindsChannelOutboundHandler— Processes outbound data and allows interception of all operations
Interface ChannelInboundHandler
The following table lists the lifecycle methods of interface ChannelInboundHandler. These are called when data is received or when the state of the associated Channel changes.
When a ChannelInboundHandler implementation overrides channelRead(), it is responsible for explicitly releasing the memory associated with pooled ByteBuf instances. Netty provides a utility method for this purpose, ReferenceCountUtil.release(), as shown next.
A simpler alternative is to use SimpleChannelInboundHandler.
Because SimpleChannelInboundHandler releases resources automatically, you shouldn’t store references to any messages for later use, as these will become invalid.
Interface ChannelOutboundHandler
The following table shows all of the methods defined locally by ChannelOutboundHandler(leaving out those inherited from ChannelHandler).
CHANNELPROMISE VS. CHANNELFUTURE
Most of the methods inChannelOutboundHandlertake aChannelPromiseargument to be notified when the operation completes.ChannelPromiseis a subinterface ofChannelFuturethat defines the writable methods, such assetSuccess()orsetFailure(), thus makingChannelFutureimmutable.
ChannelHandler adapters
The resulting class hierarchy is shown in figure 6.2.
The method bodies provided in ChannelInboundHandlerAdapter and ChannelOutboundHandlerAdapter call the equivalent methods on the associated ChannelHandlerContext, thereby forwarding events to the next ChannelHandler in the pipeline.
Resource management
Netty provides class ResourceLeakDetector, which will sample about 1% of your application’s buffer allocations to check for memory leaks. If a leak is detected, a log message similar to the following will be produced:
1 | LEAK: ByteBuf.release() was not called before it's garbage-collected. Enable |
Netty currently defines the four leak detection levels, as listed in the following table.
The leak-detection level is defined by setting the following Java system property to one of the values in the table:1
java -Dio.netty.leakDetectionLevel=ADVANCED
This listing shows how to release the message.
On the outbound side, if you handle a write() operation and discard a message, you’re responsible for releasing it. The next listing shows an implementation that discards all written data.
It’s important not only to release resources but also to notify the ChannelPromise. Otherwise a situation might arise where a ChannelFutureListener has not been notified about a message that has been handled.
In sum, it is the responsibility of the user to call ReferenceCountUtil.release() if a message is consumed or discarded and not passed to the next ChannelOutboundHandler in the ChannelPipeline. If the message reaches the actual transport layer, it will be released automatically when it’s written or the Channel is closed.
Interface ChannelPipeline
Every new Channel that’s created is assigned a new ChannelPipeline. This association is permanent.
ChannelHandlerContext
AChannelHandlerContextenables aChannelHandlerto interact with itsChannelPipelineand with other handlers. A handler can notify the nextChannelHandlerin theChannelPipelineand even dynamically modify theChannelPipelineit belongs to.
Modifying a ChannelPipeline
A ChannelHandler can modify the layout of a ChannelPipeline in real time by adding, removing, or replacing other ChannelHandlers. (It can remove itself from the ChannelPipeline as well.) The relevant methods are listed in the following table.
This listing shows these methods in use.
ChannelHandler execution and blocking
Normally eachChannelHandlerin theChannelPipelineprocesses events that are passed to it by itsEventLoop(the I/O thread). It’s critically important not to block this thread as it would have a negative effect on the overall handling of I/O.
The following table shows the ChannelPipeline operations for accessing ChannelHandlers.
Firing events
The following table lists the inbound operations, which notify ChannelInboundHandlers of events occurring in the ChannelPipeline.
The following table lists the outbound operations of the ChannelPipeline API.
In summary,
- A
ChannelPipelineholds theChannelHandlersassociated with aChannel. - A
ChannelPipelinecan be modified dynamically by adding and removingChannelHandlersas needed. ChannelPipelinehas a rich API for invoking actions in response to inbound and outbound events.
Interface ChannelHandlerContext
A ChannelHandlerContext represents an association between a ChannelHandler and a ChannelPipeline and is created whenever a ChannelHandler is added to a ChannelPipeline.
The methods called on a ChannelHandlerContext will start at the current associated ChannelHandler and propagate only to the next ChannelHandler in the pipeline that is capable of handling the event.
The following table summarizes the ChannelHandlerContext API.
When using the ChannelHandlerContext API, please keep the following points in mind:
- The
ChannelHandlerContextassociated with aChannelHandlernever changes, so it’s safe to cache a reference to it. ChannelHandlerContextmethods involve a shorter event flow than do the identically named methods available on other classes. This should be exploited where possible to provide maximum performance.
Using ChannelHandlerContext
Figure 6.4 shows the relationships.
In the following listing you acquire a reference to the Channel from a ChannelHandlerContext. Calling write() on the Channel causes a write event to flow all the way through the pipeline.
The next listing shows a similar example, but writing this time to a ChannelPipeline.
As you can see in figure 6.5, the flows in listings 6.6 and 6.7 are identical. It’s important to note that although the write() invoked on either the Channel or the ChannelPipeline operation propagates the event all the way through the pipeline, the movement from one handler to the next at the ChannelHandler level is invoked on the ChannelHandlerContext.
If you want to propagate an event starting at a specific point in the ChannelPipeline, the following listing and figure 6.6 illustrate this use.
As shown in figure 6.6, the message flows through the ChannelPipeline starting at the next ChannelHandler, bypassing all the preceding ones.
Advanced uses of ChannelHandler and ChannelHandlerContext
The following list shows caching a ChannelHandlerContext.
Because a ChannelHandler can belong to more than one ChannelPipeline, it can be bound to multiple ChannelHandlerContext instances. A ChannelHandler intended for this use must be annotated with @Sharable; otherwise, attempting to add it to more than one ChannelPipeline will trigger an exception. This listing shows a correct implementation of this pattern.
Conversely, the code in listing 6.11 will cause problems.
In summary, use @Sharable only if you’re certain that your ChannelHandler is thread-safe.
Exception handling
Handling inbound exceptions
The following listing shows a simple example that closes the Channel and prints the exception’s stack trace.
To summarize,
- The default implementation of
ChannelHandler.exceptionCaught()forwards the current exception to the next handler in the pipeline. - If an exception reaches the end of the pipeline, it’s logged as unhandled.
- To define custom handling, you override
exceptionCaught(). It’s then your decision whether to propagate the exception beyond that point.
Handling outbound exceptions
Notification mechanisms:
- Every outbound operation returns a
ChannelFuture. TheChannelFutureListenersregistered with aChannelFutureare notified of success or error when the operation completes. - Almost all methods of
ChannelOutboundHandlerare passed an instance ofChannelPromise. As a subclass ofChannelFuture,ChannelPromisecan also be assigned listeners for asynchronous notification. ButChannelPromisealso has writable methods that provide for immediate notification:1
2ChannelPromise setSuccess();
ChannelPromise setFailure(Throwable cause);
The following listing uses this approach to add a ChannelFutureListener that will print the stack trace and then close the Channel.
The code shown next will have the same effect as the previous listing.