Netty 5 Migration Guide
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.*
.
Netty 5 introduces a new Buffer API, that is simpler and safer to use than ByteBuf
.
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.
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
.
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.
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.
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:
- The
Future.addListeners()
,Future.removeListeners()
, andFuture.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 existingFuture.isSuccess()
which checks that a future is both completed and successfully so. - New
Future.map()
andFuture.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 theEventLoop
. If you still need to block from outside theEventLoop
you will need to convert theFuture
viaFuture.asStage()
. The returnedFutureCompletionStage
provides blocking methods.
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
.
As we removed ChannelFuture
/ ChannelPromise
we also changed the return type of the methods to Future<Void>
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.
We Changed ChannelHandlerContext to not extend AttributeMap anymore. In the case of you using attributes you should directly use Channel
which still extends AttributeMap
.
The Channel.Unsafe interface was completely removed so its not possible for the end-user to mess-up internals.
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
.
In netty 4.x we added the ability to add ChannelHandler
s 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 thathandlerRemoved(...)
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 calledchannelRead(...)
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.
As we removed ChannelFuture
/ ChannelPromise
we also changed the return type of the methods to Future<Void>
Netty 5 greatly simplify the type-hierarchy for ChannelHandler
s.
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.
If you are using [SimpleChannelInboundHandler
], you have to rename channelRead0()
to messageReceived()
.
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.
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.
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.
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.
New methods were added to the EventLoop
interface to allow registration and deregistration of Channel
s. These should not be used by the user directly but by Channel
implementations itself.
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