Skip navigation

Netty 5 Migration Guide

Did you know this page is automatically generated from a Github Wiki page? You can improve it by yourself here!

Netty 5 Migration Guide

Package

To allow have netty 5 and netty 4 co-exist on the same classpath we changed the package name of netty 5 classes to io.netty5.*.

Buffers

Netty 5 introduces a new Buffer API, that is simpler and safer to use than ByteBuf.

Capacity

The Netty 4.1 ByteBuf would automatically expand in capacity as needed when written to, until a certain maximum capacity.

The new Buffer API no longer do this, and no longer have a capacity and max-capacity distinction. Code should instead allocate properly sized buffers (the size argument is now mandatory), and/or call ensureWritable() as needed. The ensureWritable() method can now also take arguments that allow it to a compaction (the same as the old discardReadBytes()) and expansion at the same time, in a single memory copy.

Adaptors

A set of adaptors are included, which can convert between the two APIs, and allow them to co-exist, until all handles and relevant code has been migrated.

The ByteBufBuffer.wrap() method takes a ByteBuf instance and returns a Buffer - a buffer instance of the new API. Conversely, the ByteBufAdaptor.intoByteBuf method takes a Buffer and returns a ByteBuf. Both of these methods will do the conversion in the most efficient way possible, and multiple conversions will undo each other to avoid nesting adaptors.

A BufferConversionHandler is also included, which can be inserted into the pipeline between handles that rely on different APIs. Note that the BufferConversionHandler is unable to convert any ByteBufHolder or BufferHolder objects. Objects that contain Buffer or ByteBuf instances will need to be converted with e.g. a custom MessageToMessageCodec.

Changes in API

The key difference in the new Buffer API is that aliasing is no longer allowed. Aliasing is when two or more buffer objects refer to the same underlying memory. This means that methods like slice() and duplicate() are no longer available. On the other hand, the life-cycle handling is simplified, and reference counting can be entirely removed from the API.

Where you would have used slice(), duplicate(), and their retain* variants, and the retain() family of methods, you will now instead use split(), readSplit(), and copy().

The split family of methods are similar to slice(), except they can only slice at the ends of the buffer, and the returned buffer slice is removed from the original buffer, thus preventing aliasing.

The retain() and release() methods are gone. Buffers instead have a close() method, which will be called the end of the lifetime for the buffer. The buffers implement AutoCloseable as a convenience when the scope and lifetime of a buffer is entirely local.

Most places where retain() was used were effectively to cancel the effect of an unconditional release() in a super-class or sub-class. In these cases, split() can be used instead, since these cases usually involve passing around a readable section of the buffer, and split() results in two buffers that must each be closed.

There is a new send() method, which can be used to encode in the type system that the "ownership" of a buffer moves from one place to another. For instance, this is used by the CompositeBuffer factory methods to ensure that the composite buffer obtains exclusive access to the component buffers. This prevents aliasing via buffer composition.

The buffers are now always big-endian, and the *LE accessor methods are gone. To perform little-endian reads or writes, use the reverseBytes methods in Integer or Long, in conjunction with the big-endian read or write.

Future / Promise

ChannelFuture / ChannelPromise removal

To simplify the API and type-hierarchy we decided to remove the ChannelFuture / ChannelPromise (and all it sub-types / implementations) completely. As a replacement Future<Void> and Promise<Void> are use directly.

ProgressiveFuture / ProgressivePromise and ChannelProgressiveFuture / ChannelProgressivePromise removal

The support for ProgressiveFuture / ProgressivePromise was completely removed in netty 5. The reason for this was that while it may have been useful sometimes it also did require that all handlers in the pipeline take special action here if they did chain up promises. This was not really the case and is quite cumbersome to do in reality.

Due the stated problems we decided that it would be best to just remove this feature all together as there was not much usage of this feature anyway. It is better to not support something at all than have something only work "sometimes". This also means there is less code to maintain.

VoidChannelPromise removal

In netty 4.1.x it was possible to use the voidPromise() method to obtain a special ChannelPromise implementation that could be used for various IO operations (like write) to reduce the number of objects created. While the motivation of this feature was good it turned out that this special case of a ChannelPromise did actually bring a lot of problems:

API changes to Promise and Future

  • The Future.addListeners(), Future.removeListeners(), and Future.removeListener() have been removed. We removed the ability to remove previous added listeners. This feature was not really used and so it allowed us to remove some complexity and remove some API surface.
  • A Future.isFailed() method has been added, that checks that the future is both completed and failed. This is similar to the existing Future.isSuccess() which checks that a future is both completed and successfully so.
  • New Future.map() and Future.flatMap() methods have been added, which makes it easy to compose and create new futures based on existing ones. These methods handle failures and cancellation properly through propagation.
  • New methods were added to convert to CompletionStage and so make it easier to interopt with other APIs.
  • All blocking methods were removed from the Future interface as it was easy for people to miss-use these and so block the EventLoop. If you still need to block from outside the EventLoop you will need to convert the Future via Future.asStage(). The returned FutureCompletionStage provides blocking methods.

Channel

Channel.eventLoop() renamed to Channel.executor()

In netty 5.x we added the executor() method to ChannelOutboundInvoker and as this method returns EventExecutor we did decide to remove the eventLoop() method from Channel and just override executor() to return EventLoop for Channel.

Return types changes from ChannelFuture to Future<Void>

As we removed ChannelFuture / ChannelPromise we also changed the return type of the methods to Future<Void>

Half-closure

Netty5 has support for half-closure build in its core now. For this ChannelHandler.shutdown and ChannelHandler.channelShutdown were introduced. Beside this also Channel.isShutdown(...) and ChannelOutboundInvoker.shutdown(...) where added. This replace the old DuplexChannel abstraction, which was completely removed. For more details see the pull request #12468.

ChannelHandlerContext doesn't extend AttributeMap anymore

We Changed ChannelHandlerContext to not extend AttributeMap anymore. In the case of you using attributes you should directly use Channel which still extends AttributeMap.

Channel.Unsafe removal

The Channel.Unsafe interface was completely removed so its not possible for the end-user to mess-up internals.

ChannelOutboundBuffer not part of the Channel API anymore

The ChannelOutboundBuffer is an implementation details of our AbstractChannel implementation and so was removed for the Channel itself completely.

Channel.beforeBeforeWritable() removed and Channel.bytesBeforeUnwritable() renamed to writableBytes().

We removed the Channel.beforeBeforeWritable() method as it was not used at all and renamed Channel.bytesBeforeUnwritable() to Channel.writableBytes.

ChannelPipeline

ChannelPipeline.add*(EventExecutorGroup...) removed

In netty 4.x we added the ability to add ChannelHandlers to a ChannelPipeline with an explicit EventExecutorGroup. While this seemed like a good idea it turned out that it has quite some problems when it comes to life-cycles:

  • handlerRemoved(...) , handlerAdded(...) may be called at the "wrong time". This can result in a lot of problems. At worse it could mean that handlerRemoved(...) is called and the handler frees some native memory as it expect that the handler will never used anymore. What could happen here is that after it is called channelRead(...) may be called which then try to access the previous freed memory and so crash the JVM.
  • correctly implement "visibility" in terms of concurrent access / modifications of the pipeline is quite problematic as well.

With this in mind we realised that really what the user mostly want is to have the incoming messages handled by another thread to process the business-logic. This is better be done in a custom implementation provided by the user as the user has a better handle on when things can be destroyed or not.

Return types changes from ChannelFuture to Future<Void>

As we removed ChannelFuture / ChannelPromise we also changed the return type of the methods to Future<Void>

ChannelHandler

Netty 5 greatly simplify the type-hierarchy for ChannelHandlers.

Simplified handler type hierarchy

ChannelInboundHandler and ChannelOutboundHandler have been merged into [ChannelHandler]. [ChannelHandler] now has both inbound and outbound handler methods. All outbound methods that took a ChannelPromise have been changed to return Future<Void>. This change makes usage less error-prone and simplifies the API.

ChannelInboundHandlerAdapter, ChannelOutboundHandlerAdapter, and ChannelDuplexHandlerAdapter have been removed and replaced by [ChannelHandlerAdapter].

Because it is now impossible to tell if a handler is an inbound handler or an outbound handler, CombinedChannelDuplexHandler has been replaced by [ChannelHandlerAppender].

For more information about this change, please refer to the pull request #1999.

channelRead0()messageReceived()

If you are using [SimpleChannelInboundHandler], you have to rename channelRead0() to messageReceived().

User events

It's now possible to fire user / custom events in both directions through the pipeline. For inbound events you would use fireChannelInboundEvent(...) (replacement of fireUserEventTriggered(...) and for outbound events sendOutboundEvent(...). Both of these can be intercepted by methods defined in ChannelHandler as usual.

ChannelHandler.pendingOutboundBytes(...) method added

It's now easily possible to influence the writability of the Channel by the contained ChannelHandler in the ChannelPipeline. This makes it possible to influence back pressure when the ChannelHandler itself buffers outbound data.

EventLoopGroup / EventLoop

While in netty 4.x we had different EventLoopGroup / EventLoop implementations for the various different transports (i.e. NioEventLoopGroup, EpollEventLoopGroup....) we changed this in netty 5 to just have one EventLoopGroup implementation which is called MultiThreadEventLoopGroup. This MultiThreadEventLoopGroup takes an IoHandlerFactory which is specific to the transport itself (i.e. NioHandler.newFactory(), EpollHandler.newFactory()....). This change gives a lot of advantages. For example it is easy to extend the MultiThreadEventLoopGroup and so decorate things or add custom metrics etc. This implementation can then be re-used across different transports. This is very similar to what the JDK provides with ThreadPoolExecutor in terms of the possibility of customisations.

EventLoopGroup.isCompatible(...) method added

It's now possible to check if a Channel sub-type is compatible with the EventLoopGroup / EventLoop before trying to use it. This can help to select the correct Channel sub-type.

EventLoop.registerForIo(...) and EventLoop.deregisterForIo added

New methods were added to the EventLoop interface to allow registration and deregistration of Channels. These should not be used by the user directly but by Channel implementations itself.

Rarely used codecs moved to Netty Contrib

To slim the code base down and ease the maintenance burden, the following codecs and handlers have been moved to Netty Contrib:

  • netty-codec-xml
  • netty-codec-redis
  • netty-codec-memcache
  • netty-codec-stomp
  • netty-codec-haproxy
  • netty-codec-mqtt
  • netty-codec-socks
  • netty-handler-proxy
  • io.netty.handler.codec.json
  • io.netty.handler.codec.marshalling
  • io.netty.handler.codec.protobuf
  • io.netty.handler.codec.serialization
  • io.netty.handler.codec.xml
  • io.netty.handler.pcap
Last retrieved on 16-Apr-2024