You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ta...@apache.org on 2023/03/21 22:41:54 UTC

[qpid-protonj2] branch main updated: PROTON-2694 Ensure that the AMQP test peer work on Netty 4 and 5

This is an automated email from the ASF dual-hosted git repository.

tabish pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/qpid-protonj2.git


The following commit(s) were added to refs/heads/main by this push:
     new ba7bb1ee PROTON-2694 Ensure that the AMQP test peer work on Netty 4 and 5
ba7bb1ee is described below

commit ba7bb1eee4d5e5bfea6a0e6974c533ecb37207c5
Author: Timothy Bish <ta...@gmail.com>
AuthorDate: Tue Mar 21 18:40:47 2023 -0400

    PROTON-2694 Ensure that the AMQP test peer work on Netty 4 and 5
    
    Allows users with applications that currently use Netty 4 and want to test
    AMQP behaviors to use the test peer.
---
 protonj2-test-driver/pom.xml                       |  33 ++
 .../qpid/protonj2/test/driver/AMQPTestDriver.java  | 199 ++++-----
 .../qpid/protonj2/test/driver/FrameDecoder.java    |  79 ++--
 .../qpid/protonj2/test/driver/FrameEncoder.java    | 110 ++---
 .../qpid/protonj2/test/driver/LinkTracker.java     |   6 +-
 .../protonj2/test/driver/ProtonTestClient.java     |  78 +---
 .../protonj2/test/driver/ProtonTestServer.java     |  72 +---
 .../qpid/protonj2/test/driver/ReceiverTracker.java |   6 +-
 .../qpid/protonj2/test/driver/ScriptedAction.java  |  21 +-
 .../qpid/protonj2/test/driver/SenderTracker.java   |   6 +-
 .../qpid/protonj2/test/driver/SessionTracker.java  |   7 +-
 .../actions/AbstractPerformativeInjectAction.java  |   6 +-
 .../driver/actions/ByteBufferInjectAction.java     |   8 +-
 .../test/driver/actions/RawBytesInjectAction.java  |  12 +-
 .../test/driver/actions/TransferInjectAction.java  |  78 ++--
 .../protonj2/test/driver/codec/ArrayElement.java   |  98 ++---
 .../protonj2/test/driver/codec/BinaryElement.java  |  45 +-
 .../protonj2/test/driver/codec/BooleanElement.java |  15 +-
 .../protonj2/test/driver/codec/ByteElement.java    |  23 +-
 .../protonj2/test/driver/codec/CharElement.java    |  17 +-
 .../qpid/protonj2/test/driver/codec/Codec.java     |   8 +-
 .../qpid/protonj2/test/driver/codec/CodecImpl.java |  34 +-
 .../test/driver/codec/Decimal128Element.java       |  21 +-
 .../test/driver/codec/Decimal32Element.java        |  19 +-
 .../test/driver/codec/Decimal64Element.java        |  21 +-
 .../test/driver/codec/DescribedTypeElement.java    |  27 +-
 .../protonj2/test/driver/codec/DoubleElement.java  |  17 +-
 .../qpid/protonj2/test/driver/codec/Element.java   |   4 +-
 .../protonj2/test/driver/codec/FloatElement.java   |  17 +-
 .../protonj2/test/driver/codec/IntegerElement.java |  22 +-
 .../protonj2/test/driver/codec/ListElement.java    |  55 +--
 .../protonj2/test/driver/codec/LongElement.java    |  38 +-
 .../protonj2/test/driver/codec/MapElement.java     |  53 +--
 .../protonj2/test/driver/codec/NullElement.java    |  13 +-
 .../protonj2/test/driver/codec/ShortElement.java   |  23 +-
 .../protonj2/test/driver/codec/StringElement.java  |  47 ++-
 .../protonj2/test/driver/codec/SymbolElement.java  |  43 +-
 .../test/driver/codec/TimestampElement.java        |  28 +-
 .../protonj2/test/driver/codec/TypeDecoder.java    | 395 +++++++++--------
 .../protonj2/test/driver/codec/UUIDElement.java    |  22 +-
 .../test/driver/codec/UnsignedByteElement.java     |  25 +-
 .../test/driver/codec/UnsignedIntegerElement.java  |  53 +--
 .../test/driver/codec/UnsignedLongElement.java     |  53 +--
 .../test/driver/codec/UnsignedShortElement.java    |  25 +-
 .../test/driver/codec/transport/Attach.java        |   5 +-
 .../test/driver/codec/transport/Begin.java         |   5 +-
 .../test/driver/codec/transport/Close.java         |   5 +-
 .../test/driver/codec/transport/Detach.java        |   5 +-
 .../test/driver/codec/transport/Disposition.java   |   5 +-
 .../protonj2/test/driver/codec/transport/End.java  |   5 +-
 .../protonj2/test/driver/codec/transport/Flow.java |   5 +-
 .../test/driver/codec/transport/HeartBeat.java     |   4 +-
 .../protonj2/test/driver/codec/transport/Open.java |   5 +-
 .../codec/transport/PerformativeDescribedType.java |  25 +-
 .../test/driver/codec/transport/Transfer.java      |   5 +-
 .../driver/expectations/AMQPHeaderExpectation.java |  12 +-
 .../driver/expectations/AbstractExpectation.java   |  30 +-
 .../driver/expectations/AttachExpectation.java     |   5 +-
 .../test/driver/expectations/BeginExpectation.java |   5 +-
 .../test/driver/expectations/CloseExpectation.java |   5 +-
 .../driver/expectations/DetachExpectation.java     |   5 +-
 .../expectations/DispositionExpectation.java       |   5 +-
 .../test/driver/expectations/EndExpectation.java   |   5 +-
 .../test/driver/expectations/FlowExpectation.java  |   5 +-
 .../test/driver/expectations/OpenExpectation.java  |   5 +-
 .../driver/expectations/TransferExpectation.java   |  22 +-
 .../messaging/AbstractMessageSectionMatcher.java   |   7 +-
 .../transport/TransferPayloadCompositeMatcher.java | 149 ++++---
 .../matchers/types/EncodedAmqpTypeMatcher.java     |  12 +-
 .../EncodedCompositingDataSectionMatcher.java      |  86 ++--
 .../types/EncodedPartialDataSectionMatcher.java    |  76 ++--
 .../protonj2/test/driver/netty/NettyClient.java    | 468 ++-------------------
 .../protonj2/test/driver/netty/NettyEventLoop.java |  52 +++
 .../protonj2/test/driver/netty/NettyIOBuilder.java |  80 ++++
 .../protonj2/test/driver/netty/NettyServer.java    | 421 ++----------------
 .../{NettyClient.java => netty4/Netty4Client.java} | 199 +++++----
 .../netty4/Netty4EventLoop.java}                   |  36 +-
 .../{NettyServer.java => netty4/Netty4Server.java} | 258 +++++++-----
 .../netty4/Netty4Support.java}                     |  46 +-
 .../test/driver/netty/{ => netty4}/SslSupport.java |  10 +-
 .../{NettyClient.java => netty5/Netty5Client.java} |  63 ++-
 .../netty5/Netty5EventLoop.java}                   |  36 +-
 .../{NettyServer.java => netty5/Netty5Server.java} |  74 +++-
 .../netty5/Netty5Support.java}                     |  46 +-
 .../test/driver/netty/{ => netty5}/SslSupport.java |   6 +-
 .../qpid/protonj2/codec/benchmark/Benchmark.java   | 103 +++--
 .../protonj2/test/driver/codec/DataImplTest.java   |  82 ++--
 ...LegacyCodecTransferFramesTestDataGenerator.java |  32 +-
 .../EncodedCompositingDataSectionMatcherTest.java  | 216 +++++-----
 .../EncodedPartialDataSectionMatcherTest.java      | 120 +++---
 90 files changed, 2262 insertions(+), 2576 deletions(-)

diff --git a/protonj2-test-driver/pom.xml b/protonj2-test-driver/pom.xml
index 45a338ce..8f5df1cf 100644
--- a/protonj2-test-driver/pom.xml
+++ b/protonj2-test-driver/pom.xml
@@ -28,26 +28,59 @@
   <name>Qpid ProtonJ2 AMQP Test Driver</name>
 
   <dependencies>
+    <!-- Netty 4 Development dependencies -->
+    <dependency>
+      <groupId>io.netty</groupId>
+      <artifactId>netty-buffer</artifactId>
+      <scope>${netty-scope}</scope>
+    </dependency>
+    <dependency>
+      <groupId>io.netty</groupId>
+      <artifactId>netty-common</artifactId>
+      <scope>${netty-scope}</scope>
+    </dependency>
+    <dependency>
+      <groupId>io.netty</groupId>
+      <artifactId>netty-handler</artifactId>
+      <scope>${netty-scope}</scope>
+    </dependency>
+    <dependency>
+      <groupId>io.netty</groupId>
+      <artifactId>netty-transport</artifactId>
+      <scope>${netty-scope}</scope>
+    </dependency>
+    <dependency>
+      <groupId>io.netty</groupId>
+      <artifactId>netty-codec-http</artifactId>
+      <scope>${netty-scope}</scope>
+    </dependency>
+    <!-- Netty 5 Development dependencies -->
     <dependency>
       <groupId>io.netty</groupId>
       <artifactId>netty5-buffer</artifactId>
+      <scope>${netty5-scope}</scope>
     </dependency>
     <dependency>
       <groupId>io.netty</groupId>
       <artifactId>netty5-common</artifactId>
+      <scope>${netty5-scope}</scope>
     </dependency>
     <dependency>
       <groupId>io.netty</groupId>
       <artifactId>netty5-handler</artifactId>
+      <scope>${netty5-scope}</scope>
     </dependency>
     <dependency>
       <groupId>io.netty</groupId>
       <artifactId>netty5-transport</artifactId>
+      <scope>${netty5-scope}</scope>
     </dependency>
     <dependency>
       <groupId>io.netty</groupId>
       <artifactId>netty5-codec-http</artifactId>
+      <scope>${netty5-scope}</scope>
     </dependency>
+    <!-- All other test peer dependencies -->
     <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/AMQPTestDriver.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/AMQPTestDriver.java
index f3bf0e2b..a277de7a 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/AMQPTestDriver.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/AMQPTestDriver.java
@@ -18,6 +18,9 @@ package org.apache.qpid.protonj2.test.driver;
 
 import java.nio.ByteBuffer;
 import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 import java.util.Objects;
 import java.util.Queue;
 import java.util.concurrent.TimeUnit;
@@ -33,14 +36,10 @@ import org.apache.qpid.protonj2.test.driver.codec.transport.HeartBeat;
 import org.apache.qpid.protonj2.test.driver.codec.transport.Open;
 import org.apache.qpid.protonj2.test.driver.codec.transport.PerformativeDescribedType;
 import org.apache.qpid.protonj2.test.driver.exceptions.UnexpectedPerformativeError;
+import org.apache.qpid.protonj2.test.driver.netty.NettyEventLoop;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import io.netty5.buffer.Buffer;
-import io.netty5.buffer.BufferAllocator;
-import io.netty5.buffer.CompositeBuffer;
-import io.netty5.channel.EventLoop;
-
 /**
  * Test driver object used to drive inputs and inspect outputs of an Engine.
  */
@@ -59,9 +58,9 @@ public class AMQPTestDriver implements Consumer<ByteBuffer> {
 
     private final Consumer<ByteBuffer> frameConsumer;
     private final Consumer<AssertionError> assertionConsumer;
-    private final Supplier<EventLoop> schedulerSupplier;
+    private final Supplier<NettyEventLoop> schedulerSupplier;
 
-    private volatile CompositeBuffer deferredWrites;
+    private volatile List<ByteBuffer> deferredWrites = new ArrayList<>();
     private volatile AssertionError failureCause;
 
     private int advertisedIdleTimeout = 0;
@@ -90,7 +89,7 @@ public class AMQPTestDriver implements Consumer<ByteBuffer> {
      * @param scheduler
      *      A {@link Supplier} that will provide this driver with a scheduler service for delayed actions
      */
-    public AMQPTestDriver(String name, Consumer<ByteBuffer> frameConsumer, Supplier<EventLoop> scheduler) {
+    public AMQPTestDriver(String name, Consumer<ByteBuffer> frameConsumer, Supplier<NettyEventLoop> scheduler) {
         this(name, frameConsumer, null, scheduler);
     }
 
@@ -106,7 +105,7 @@ public class AMQPTestDriver implements Consumer<ByteBuffer> {
      * @param scheduler
      *      A {@link Supplier} that will provide this driver with a scheduler service for delayed actions
      */
-    public AMQPTestDriver(String name, Consumer<ByteBuffer> frameConsumer, Consumer<AssertionError> assertionConsumer, Supplier<EventLoop> scheduler) {
+    public AMQPTestDriver(String name, Consumer<ByteBuffer> frameConsumer, Consumer<AssertionError> assertionConsumer, Supplier<NettyEventLoop> scheduler) {
         this.frameConsumer = frameConsumer;
         this.assertionConsumer = assertionConsumer;
         this.schedulerSupplier = scheduler;
@@ -207,28 +206,22 @@ public class AMQPTestDriver implements Consumer<ByteBuffer> {
 
     //----- Accepts encoded AMQP frames for processing
 
-    @Override
-    public void accept(ByteBuffer buffer) {
-        try (Buffer copy = BufferAllocator.onHeapUnpooled().copyOf(buffer)) {
-            accept(copy);
-        }
-    }
-
     /**
-     * Supply incoming bytes read to the test driver via an Netty {@link Buffer} instance.
+     * Supply incoming bytes read to the test driver via an Netty {@link ByteBuffer} instance.
      *
      * @param buffer
-     * 		The Netty {@link Buffer} that contains new incoming bytes read.
+     * 		The Netty {@link ByteBuffer} that contains new incoming bytes read.
      */
-    public void accept(Buffer buffer) {
-        LOG.trace("{} processing new inbound buffer of size: {}", driverName, buffer.readableBytes());
+    @Override
+    public void accept(ByteBuffer buffer) {
+        LOG.trace("{} processing new inbound buffer of size: {}", driverName, buffer.remaining());
 
         try {
             // Process off all encoded frames from this buffer one at a time.
-            while (buffer.readableBytes() > 0 && failureCause == null) {
-                LOG.trace("{} ingesting {} bytes.", driverName, buffer.readableBytes());
+            while (buffer.remaining() > 0 && failureCause == null) {
+                LOG.trace("{} ingestion of {} bytes starting now.", driverName, buffer.remaining());
                 frameParser.ingest(buffer);
-                LOG.trace("{} ingestion completed cycle, remaining bytes in buffer: {}", driverName, buffer.readableBytes());
+                LOG.trace("{} ingestion completed cycle, remaining bytes in buffer: {}", driverName, buffer.remaining());
             }
         } catch (AssertionError e) {
             signalFailure(e);
@@ -284,7 +277,7 @@ public class AMQPTestDriver implements Consumer<ByteBuffer> {
         }
     }
 
-    void handleSaslPerformative(int frameSize, SaslDescribedType sasl, int channel, Buffer payload) throws AssertionError {
+    void handleSaslPerformative(int frameSize, SaslDescribedType sasl, int channel, ByteBuffer payload) throws AssertionError {
         synchronized (script) {
             final ScriptedElement scriptEntry = script.poll();
             if (scriptEntry == null) {
@@ -316,7 +309,7 @@ public class AMQPTestDriver implements Consumer<ByteBuffer> {
         }
     }
 
-    void handlePerformative(int frameSize, PerformativeDescribedType amqp, int channel, Buffer payload) throws AssertionError {
+    void handlePerformative(int frameSize, PerformativeDescribedType amqp, int channel, ByteBuffer payload) throws AssertionError {
         switch (amqp.getPerformativeType()) {
             case HEARTBEAT:
                 break;
@@ -368,7 +361,7 @@ public class AMQPTestDriver implements Consumer<ByteBuffer> {
      */
     public synchronized void afterDelay(int delay, ScriptedAction action) {
         Objects.requireNonNull(schedulerSupplier, "This driver cannot schedule delayed events, no scheduler available");
-        EventLoop scheduler = schedulerSupplier.get();
+        NettyEventLoop scheduler = schedulerSupplier.get();
         Objects.requireNonNull(scheduler, "This driver cannot schedule delayed events, no scheduler available");
 
         scheduler.schedule(() -> {
@@ -503,7 +496,7 @@ public class AMQPTestDriver implements Consumer<ByteBuffer> {
      * @param payload
      *      The payload to include in the encoded frame.
      */
-    public final void sendAMQPFrame(int channel, DescribedType performative, Buffer payload) {
+    public final void sendAMQPFrame(int channel, DescribedType performative, ByteBuffer payload) {
         sendAMQPFrame(channel, performative, payload, false);
     }
 
@@ -520,14 +513,10 @@ public class AMQPTestDriver implements Consumer<ByteBuffer> {
      * @param splitWrite
      * 		Should the data be written in multiple chunks
      */
-    public void deferAMQPFrame(int channel, DescribedType performative, Buffer payload, boolean splitWrite) {
+    public void deferAMQPFrame(int channel, DescribedType performative, ByteBuffer payload, boolean splitWrite) {
         LOG.trace("{} Deferring write of performative: {}", driverName, performative);
-        try (Buffer buffer = frameEncoder.handleWrite(performative, channel, payload, null)) {
-            if (deferredWrites == null) {
-                deferredWrites = BufferAllocator.onHeapUnpooled().compose();
-            }
-
-            deferredWrites.extendWith(buffer.send());
+        try {
+            deferredWrites.add(frameEncoder.handleWrite(performative, channel, payload, null));
         } catch (Throwable t) {
             signalFailure(new AssertionError("Frame was not written due to error.", t));
         }
@@ -551,12 +540,8 @@ public class AMQPTestDriver implements Consumer<ByteBuffer> {
 
         LOG.trace("{} Deferring SASL performative write: {}", driverName, performative);
 
-        try (Buffer buffer = frameEncoder.handleWrite(performative, channel)) {
-            if (deferredWrites == null) {
-                deferredWrites = BufferAllocator.onHeapUnpooled().compose();
-            }
-
-            deferredWrites.extendWith(buffer.send());
+        try {
+            deferredWrites.add(frameEncoder.handleWrite(performative, channel));
         } catch (Throwable t) {
             signalFailure(new AssertionError("Frame was not written due to error.", t));
         }
@@ -572,11 +557,7 @@ public class AMQPTestDriver implements Consumer<ByteBuffer> {
     public void deferHeader(AMQPHeader header) {
         LOG.trace("{} Deferring AMQP Header write: {}", driverName, header);
         try {
-            if (deferredWrites == null) {
-                deferredWrites = BufferAllocator.onHeapUnpooled().compose();
-            }
-
-            deferredWrites.extendWith(BufferAllocator.onHeapUnpooled().copyOf(header.getBuffer()).send());
+            deferredWrites.add(ByteBuffer.wrap(Arrays.copyOf(header.getBuffer(), AMQPHeader.HEADER_SIZE_BYTES)));
         } catch (Throwable t) {
             signalFailure(new AssertionError("Frame was not consumed due to error.", t));
         }
@@ -594,7 +575,7 @@ public class AMQPTestDriver implements Consumer<ByteBuffer> {
      * @param splitWrite
      * 		Should the data be written in multiple chunks
      */
-    public void sendAMQPFrame(int channel, DescribedType performative, Buffer payload, boolean splitWrite) {
+    public void sendAMQPFrame(int channel, DescribedType performative, ByteBuffer payload, boolean splitWrite) {
         LOG.trace("{} Sending performative: {}", driverName, performative);
 
         if (performative instanceof PerformativeDescribedType) {
@@ -606,35 +587,37 @@ public class AMQPTestDriver implements Consumer<ByteBuffer> {
             }
         }
 
-        try (Buffer buffer = frameEncoder.handleWrite(performative, channel, payload, null)) {
-            final Buffer buffered;
-            if (deferredWrites != null) {
-                deferredWrites.extendWith(buffer.send());
-                buffered = deferredWrites;
-                deferredWrites = null;
-                LOG.trace("{} appending deferred buffer {} to next write.", driverName, buffered);
-            } else {
-                buffered = buffer;
+        final ByteBuffer output;
+        final ByteBuffer buffer = frameEncoder.handleWrite(performative, channel, payload, null);
+
+        if (deferredWrites != null) {
+            deferredWrites.add(buffer);
+            try {
+                output = composeDefferedWrites(deferredWrites).asReadOnlyBuffer();
+            } finally {
+                deferredWrites.clear();
             }
+            LOG.trace("{} appending deferred buffer {} to next write.", driverName, output);
+        } else {
+            output = buffer;
+        }
 
-            LOG.trace("{} Writing out buffer {} to consumer: {}", driverName, buffered, frameConsumer);
+        try {
+           LOG.trace("{} Writing out buffer {} to consumer: {}", driverName, output, frameConsumer);
 
             if (splitWrite) {
-                final int bufferSplitPoint = buffered.readableBytes() / 2;
+                final int bufferSplitPoint = output.remaining() / 2;
 
-                final ByteBuffer front = ByteBuffer.allocate(bufferSplitPoint - buffered.readerOffset());
-                final ByteBuffer rear = ByteBuffer.allocate(buffered.readableBytes() - bufferSplitPoint);
+                final byte[] front = new byte[bufferSplitPoint - output.position()];
+                final byte[] rear = new byte[output.remaining() - bufferSplitPoint];
 
-                buffered.readBytes(front);
-                buffered.readBytes(rear);
+                output.get(front);
+                output.get(rear);
 
-                frameConsumer.accept(front.flip());
-                frameConsumer.accept(rear.flip());
+                frameConsumer.accept(ByteBuffer.wrap(front));
+                frameConsumer.accept(ByteBuffer.wrap(rear));
             } else {
-                final ByteBuffer output = ByteBuffer.allocate(buffered.readableBytes());
-                buffered.readBytes(output);
-
-                frameConsumer.accept(output.flip());
+                frameConsumer.accept(output);
             }
         } catch (Throwable t) {
             signalFailure(new AssertionError("Frame was not written due to error.", t));
@@ -656,23 +639,26 @@ public class AMQPTestDriver implements Consumer<ByteBuffer> {
             frameParser.resetToExpectingHeader();
         }
 
-        try (Buffer buffer = frameEncoder.handleWrite(performative, channel)) {
-            final Buffer buffered;
+        final ByteBuffer output;
+
+        try {
+            final ByteBuffer buffer = frameEncoder.handleWrite(performative, channel);
+
             if (deferredWrites != null) {
-                deferredWrites.extendWith(buffer.send());
-                buffered = deferredWrites;
-                deferredWrites = null;
-                LOG.trace("{} appending deferred buffer {} to next write.", driverName, buffered);
+                deferredWrites.add(buffer);
+                try {
+                    output = composeDefferedWrites(deferredWrites).asReadOnlyBuffer();
+                } finally {
+                    deferredWrites.clear();
+                }
+                LOG.trace("{} appending deferred buffer {} to next write.", driverName, output);
             } else {
-                buffered = buffer;
+                output = buffer;
             }
 
             LOG.trace("{} Sending SASL performative: {}", driverName, performative);
 
-            final ByteBuffer output = ByteBuffer.allocate(buffered.readableBytes());
-            buffered.readBytes(output);
-
-            frameConsumer.accept(output.flip());
+            frameConsumer.accept(output);
         } catch (Throwable t) {
             signalFailure(new AssertionError("Frame was not written due to error.", t));
         }
@@ -690,17 +676,19 @@ public class AMQPTestDriver implements Consumer<ByteBuffer> {
 
             if (deferredWrites != null) {
                 LOG.trace("{} appending deferred buffer {} to next write.", driverName, deferredWrites);
-                deferredWrites.extendWith(BufferAllocator.onHeapUnpooled().copyOf(header.getBuffer()).send());
-                output = ByteBuffer.allocate(deferredWrites.readableBytes());
-                deferredWrites.readBytes(output);
-                output.flip();
-                deferredWrites = null;
+                deferredWrites.add(ByteBuffer.wrap(header.getBuffer()).asReadOnlyBuffer());
+
+                try {
+                    output = composeDefferedWrites(deferredWrites).asReadOnlyBuffer();
+                } finally {
+                    deferredWrites.clear();
+                }
             } else {
-                output = ByteBuffer.wrap(header.getBuffer());
+                output = ByteBuffer.wrap(header.getBuffer()).asReadOnlyBuffer();
             }
 
             LOG.trace("{} Sending AMQP Header: {}", driverName, header);
-            frameConsumer.accept(ByteBuffer.wrap(header.getBuffer()));
+            frameConsumer.accept(output);
         } catch (Throwable t) {
             signalFailure(new AssertionError("Frame was not consumed due to error.", t));
         }
@@ -713,11 +701,8 @@ public class AMQPTestDriver implements Consumer<ByteBuffer> {
      *      the channel on which to send the empty frame.
      */
     public void sendEmptyFrame(int channel) {
-        try (Buffer buffer = frameEncoder.handleWrite(null, channel, null, null)) {
-            final ByteBuffer output = ByteBuffer.allocate(buffer.readableBytes());
-            buffer.readBytes(output);
-
-            frameConsumer.accept(output.flip());
+        try {
+            frameConsumer.accept(frameEncoder.handleWrite(null, channel, null, null));
         } catch (Throwable t) {
             signalFailure(new AssertionError("Frame was not consumed due to error.", t));
         }
@@ -738,24 +723,6 @@ public class AMQPTestDriver implements Consumer<ByteBuffer> {
         }
     }
 
-    /**
-     * Send the specific ProtonBuffer bytes to the remote frame consumer.
-
-     * @param buffer
-     *      The buffer whose contents are to be written to the frame consumer.
-     */
-    public void sendBytes(Buffer buffer) {
-        LOG.trace("{} Sending bytes from ProtonBuffer: {}", driverName, buffer);
-        try (Buffer bytes = buffer) {
-            final ByteBuffer output = ByteBuffer.allocate(bytes.readableBytes());
-            buffer.readBytes(output);
-
-            frameConsumer.accept(output.flip());
-        } catch (Throwable t) {
-            signalFailure(new AssertionError("Buffer was not consumed due to error.", t));
-        }
-    }
-
     /**
      * Throw an exception from processing incoming data which should be handled by the peer under test.
      *
@@ -796,6 +763,22 @@ public class AMQPTestDriver implements Consumer<ByteBuffer> {
 
     //----- Internal implementation
 
+    private static final ByteBuffer composeDefferedWrites(List<ByteBuffer> deferredWrites) {
+        int totalSize = 0;
+        for (ByteBuffer component : deferredWrites) {
+            totalSize += component.remaining();
+        }
+
+        final ByteBuffer composite = ByteBuffer.allocate(totalSize);
+        for (ByteBuffer component : deferredWrites) {
+            composite.put(component);
+        }
+
+        deferredWrites.clear();
+
+        return composite.flip().asReadOnlyBuffer();
+    }
+
     private void searchForScriptioCompletionAndTrigger() {
         script.forEach(element -> {
             if (element instanceof ScriptCompleteAction) {
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/FrameDecoder.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/FrameDecoder.java
index 6d3a3aed..3826bbe6 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/FrameDecoder.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/FrameDecoder.java
@@ -16,6 +16,8 @@
  */
 package org.apache.qpid.protonj2.test.driver;
 
+import java.nio.ByteBuffer;
+
 import org.apache.qpid.protonj2.test.driver.codec.Codec;
 import org.apache.qpid.protonj2.test.driver.codec.security.SaslDescribedType;
 import org.apache.qpid.protonj2.test.driver.codec.transport.AMQPHeader;
@@ -24,9 +26,6 @@ import org.apache.qpid.protonj2.test.driver.codec.transport.PerformativeDescribe
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import io.netty5.buffer.Buffer;
-import io.netty5.buffer.BufferAllocator;
-
 class FrameDecoder {
 
     private static final Logger LOG = LoggerFactory.getLogger(AMQPTestDriver.class);
@@ -49,7 +48,7 @@ class FrameDecoder {
         this.driver = driver;
     }
 
-    public void ingest(Buffer buffer) throws AssertionError {
+    public void ingest(ByteBuffer buffer) throws AssertionError {
         try {
             // Parses in-incoming data and emit one complete frame before returning, caller should
             // ensure that the input buffer is drained into the engine or stop if the engine
@@ -103,11 +102,11 @@ class FrameDecoder {
          * based on the contents of that data.
          *
          * @param input
-         *      The ByteBuf containing new data to be parsed.
+         *      The buffer containing new data to be parsed.
          *
          * @throws AssertionError if an error occurs while parsing incoming data.
          */
-        void parse(Buffer input) throws AssertionError;
+        void parse(ByteBuffer input) throws AssertionError;
 
         /**
          * Reset the stage to its defaults for a new cycle of parsing.
@@ -130,9 +129,9 @@ class FrameDecoder {
         private int headerByte;
 
         @Override
-        public void parse(Buffer incoming) throws AssertionError {
-            while (incoming.readableBytes() > 0 && headerByte < AMQPHeader.HEADER_SIZE_BYTES) {
-                headerBytes[headerByte++] = incoming.readByte();
+        public void parse(ByteBuffer incoming) throws AssertionError {
+            while (incoming.remaining() > 0 && headerByte < AMQPHeader.HEADER_SIZE_BYTES) {
+                headerBytes[headerByte++] = incoming.get();
             }
 
             if (headerByte == AMQPHeader.HEADER_SIZE_BYTES) {
@@ -163,9 +162,9 @@ class FrameDecoder {
         private int multiplier = FRAME_SIZE_BYTES;
 
         @Override
-        public void parse(Buffer input) throws AssertionError {
-            while (input.readableBytes() > 0) {
-                frameSize |= (input.readByte() & 0xFF) << (--multiplier * Byte.SIZE);
+        public void parse(ByteBuffer input) throws AssertionError {
+            while (input.remaining() > 0) {
+                frameSize |= (input.get() & 0xFF) << (--multiplier * Byte.SIZE);
                 if (multiplier == 0) {
                     break;
                 }
@@ -177,7 +176,7 @@ class FrameDecoder {
                 // Normalize the frame size to the reminder portion
                 int length = frameSize - FRAME_SIZE_BYTES;
 
-                if (input.readableBytes() < length) {
+                if (input.remaining() < length) {
                     transitionToFrameBufferingStage(length);
                 } else {
                     initializeFrameBodyParsingStage(length);
@@ -209,23 +208,25 @@ class FrameDecoder {
 
     private class FrameBufferingStage implements FrameParserStage {
 
-        private Buffer buffer;
+        private ByteBuffer buffer;
 
         @Override
-        public void parse(Buffer input) throws AssertionError {
-            if (input.readableBytes() < buffer.writableBytes()) {
-                buffer.writeBytes(input);
+        public void parse(ByteBuffer input) throws AssertionError {
+            if (input.remaining() < buffer.remaining()) {
+                buffer.put(input);
             } else {
-                final int remaining = buffer.writableBytes();
+                final int oldLimit = input.limit();
 
-                input.copyInto(input.readerOffset(), buffer, buffer.writerOffset(), remaining);
-                input.skipReadableBytes(remaining);
-                buffer.skipWritableBytes(remaining);
+                try {
+                    buffer.put(input.limit(input.position() + buffer.remaining())).flip();
+                } finally {
+                    input.limit(oldLimit);
+                }
 
                 // Now we can consume the buffer frame body.
-                initializeFrameBodyParsingStage(buffer.readableBytes());
-                try (Buffer frame = buffer.makeReadOnly()){
-                    stage.parse(frame);
+                initializeFrameBodyParsingStage(buffer.remaining());
+                try {
+                    stage.parse(buffer.asReadOnlyBuffer());
                 } finally {
                     buffer = null;
                 }
@@ -234,7 +235,7 @@ class FrameDecoder {
 
         @Override
         public FrameBufferingStage reset(int frameSize) {
-            buffer = BufferAllocator.onHeapUnpooled().allocate(frameSize).implicitCapacityLimit(frameSize);
+            buffer = ByteBuffer.allocate(frameSize);
             return this;
         }
     }
@@ -244,30 +245,30 @@ class FrameDecoder {
         private int frameSize;
 
         @Override
-        public void parse(Buffer input) throws AssertionError {
-            int dataOffset = (input.readByte() << 2) & 0x3FF;
+        public void parse(ByteBuffer input) throws AssertionError {
+            int dataOffset = (input.get() << 2) & 0x3FF;
             int frameSize = this.frameSize + FRAME_SIZE_BYTES;
 
             validateDataOffset(dataOffset, frameSize);
 
-            int type = input.readByte() & 0xFF;
-            short channel = input.readShort();
+            int type = input.get() & 0xFF;
+            short channel = input.getShort();
 
             // note that this skips over the extended header if it's present
             if (dataOffset != 8) {
-                input.readerOffset(input.readerOffset() + dataOffset - 8);
+                input.position(input.position() + dataOffset - 8);
             }
 
             final int frameBodySize = frameSize - dataOffset;
 
-            Buffer payload = null;
+            ByteBuffer payload = null;
             Object val = null;
 
             if (frameBodySize > 0) {
-                int frameBodyStartIndex = input.readerOffset();
+                final int decodedBytes;
 
                 try {
-                    codec.decode(input);
+                    decodedBytes = (int) codec.decode(input);
                 } catch (Exception e) {
                     throw new AssertionError("Decoder failed reading remote input:", e);
                 }
@@ -287,12 +288,14 @@ class FrameDecoder {
                 // Slice to the known Frame body size and use that as the buffer for any payload once
                 // the actual Performative has been decoded.  The implies that the data comprising the
                 // performative will be held as long as the payload buffer is kept.
-                if (input.readableBytes() > 0) {
+                if (input.remaining() > 0) {
                     // Check that the remaining bytes aren't part of another frame.
-                    int payloadSize = frameBodySize - (input.readerOffset() - frameBodyStartIndex);
+                    // TODO int payloadSize = frameBodySize - (input.position() - frameBodyStartIndex);
+                    int payloadSize = frameBodySize - decodedBytes;
                     if (payloadSize > 0) {
-                        payload = input.copy(input.readerOffset(), payloadSize, true);
-                        input.skipReadableBytes(payloadSize);
+                        final byte[] payloadBytes = new byte[payloadSize];
+                        input.get(payloadBytes);
+                        payload = ByteBuffer.wrap(payloadBytes);
                     }
                 }
             } else {
@@ -349,7 +352,7 @@ class FrameDecoder {
         }
 
         @Override
-        public void parse(Buffer input) throws AssertionError {
+        public void parse(ByteBuffer input) throws AssertionError {
             throw parsingError;
         }
 
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/FrameEncoder.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/FrameEncoder.java
index e0577695..b9fa20e0 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/FrameEncoder.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/FrameEncoder.java
@@ -16,12 +16,14 @@
  */
 package org.apache.qpid.protonj2.test.driver;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.ByteBuffer;
+
 import org.apache.qpid.protonj2.test.driver.codec.Codec;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.DescribedType;
 
-import io.netty5.buffer.Buffer;
-import io.netty5.buffer.BufferAllocator;
-
 /**
  * Encodes AMQP performatives into frames for transmission
  */
@@ -31,13 +33,15 @@ public class FrameEncoder {
     public static final byte SASL_FRAME_TYPE = (byte) 1;
 
     private static final int AMQP_PERFORMATIVE_PAD = 512;
-    private static final int FRAME_HEADER_SIZE = 8;
 
     private static final int FRAME_START_BYTE = 0;
     private static final int FRAME_DOFF_BYTE = 4;
     private static final int FRAME_DOFF_SIZE = 2;
     private static final int FRAME_TYPE_BYTE = 5;
     private static final int FRAME_CHANNEL_BYTE = 6;
+    private static final int FRAME_HEADER_SIZE = 8;
+
+    private static final byte[] FRAME_HEADER_RESERVED = new byte[FRAME_HEADER_SIZE];
 
     private final AMQPTestDriver driver;
 
@@ -47,70 +51,80 @@ public class FrameEncoder {
         this.driver = driver;
     }
 
-    public Buffer handleWrite(DescribedType performative, int channel, Buffer payload, Runnable payloadToLarge) {
+    public ByteBuffer handleWrite(DescribedType performative, int channel, ByteBuffer payload, Runnable payloadToLarge) {
         return writeFrame(performative, payload, AMQP_FRAME_TYPE, channel, driver.getOutboundMaxFrameSize(), payloadToLarge);
     }
 
-    public Buffer handleWrite(DescribedType performative, int channel) {
+    public ByteBuffer handleWrite(DescribedType performative, int channel) {
         return writeFrame(performative, null, SASL_FRAME_TYPE, (short) 0, driver.getOutboundMaxFrameSize(), null);
     }
 
-    private Buffer writeFrame(DescribedType performative, Buffer payload, byte frameType, int channel, int maxFrameSize, Runnable onPayloadTooLarge) {
-        final int outputBufferSize = AMQP_PERFORMATIVE_PAD + (payload != null ? payload.readableBytes() : 0);
-        final Buffer output = BufferAllocator.onHeapUnpooled().allocate(outputBufferSize);
+    private ByteBuffer writeFrame(DescribedType performative, ByteBuffer payload, byte frameType, int channel, int maxFrameSize, Runnable onPayloadTooLarge) {
+        final int outputBufferSize = AMQP_PERFORMATIVE_PAD + (payload != null ? payload.remaining() : 0);
 
-        final int performativeSize = writePerformative(performative, payload, maxFrameSize, output, onPayloadTooLarge);
-        final int capacity = maxFrameSize > 0 ? maxFrameSize - performativeSize : Integer.MAX_VALUE;
-        final int payloadSize = Math.min(payload == null ? 0 : payload.readableBytes(), capacity);
+        try (ByteArrayOutputStream baos = new ByteArrayOutputStream(outputBufferSize)) {
 
-        if (payloadSize > 0) {
-            payload.copyInto(payload.readerOffset(), output, output.writerOffset(), payloadSize);
-            payload.skipReadableBytes(payloadSize);
-            output.skipWritableBytes(payloadSize);
-        }
+            final int performativeSize = writePerformative(performative, payload, maxFrameSize, baos, onPayloadTooLarge);
+            final int capacity = maxFrameSize > 0 ? maxFrameSize - performativeSize : Integer.MAX_VALUE;
+            final int payloadSize = Math.min(payload == null ? 0 : payload.remaining(), capacity);
+
+            if (payloadSize > 0) {
+                byte[] payloadArray = new byte[payloadSize];
+                payload.get(payloadArray);
+                baos.write(payloadArray);
+            }
 
-        endFrame(output, frameType, channel);
+            final ByteBuffer output = ByteBuffer.wrap(baos.toByteArray());
 
-        return output;
+            endFrame(output, frameType, channel);
+
+            return output.asReadOnlyBuffer();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
     }
 
-    private int writePerformative(DescribedType performative, Buffer payload, int maxFrameSize, Buffer output, Runnable onPayloadTooLarge) {
-        output.writerOffset(FRAME_HEADER_SIZE);
+    private int writePerformative(DescribedType performative, ByteBuffer payload, int maxFrameSize, ByteArrayOutputStream output, Runnable onPayloadTooLarge) {
+        try {
+            output.write(FRAME_HEADER_RESERVED); // Reserve space for later Frame preamble
 
-        long encodedSize = 0;
-        final int startIndex = output.writerOffset();
+            long encodedSize = 0;
 
-        if (performative != null) {
-            try {
-                codec.putDescribedType(performative);
-                encodedSize = codec.encode(output);
-            } finally {
-                codec.clear();
+            if (performative != null) {
+                try {
+                    codec.putDescribedType(performative);
+                    encodedSize = codec.encode(output);
+                } finally {
+                    codec.clear();
+                }
             }
-        }
 
-        int performativeSize = output.writerOffset() - startIndex;
+            int performativeSize = output.size() - FRAME_HEADER_SIZE;
 
-        if (performativeSize != encodedSize) {
-            throw new IllegalStateException(String.format(
-                "Unable to encode performative %s of %d bytes into provided proton buffer, only wrote %d bytes",
-                performative, performativeSize, encodedSize));
-        }
+            if (performativeSize != encodedSize) {
+                throw new IllegalStateException(String.format(
+                    "Unable to encode performative %s of %d bytes into provided proton buffer, only wrote %d bytes",
+                    performative, performativeSize, encodedSize));
+            }
 
-        if (onPayloadTooLarge != null && maxFrameSize > 0 && payload != null && (payload.readableBytes() + performativeSize) > maxFrameSize) {
-            // Next iteration will re-encode the frame body again with updates from the <payload-to-large>
-            // handler and then we can move onto the body portion.
-            onPayloadTooLarge.run();
-            performativeSize = writePerformative(performative, payload, maxFrameSize, output, null);
-        }
+            if (onPayloadTooLarge != null && maxFrameSize > 0 && payload != null && (payload.remaining() + performativeSize) > maxFrameSize) {
+                // Next iteration will re-encode the frame body again with updates from the <payload-to-large>
+                // handler and then we can move onto the body portion.
+                onPayloadTooLarge.run();
+                output.reset();
+                performativeSize = writePerformative(performative, payload, maxFrameSize, output, null);
+            }
 
-        return performativeSize;
+            return performativeSize;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
     }
 
-    private static void endFrame(Buffer output, byte frameType, int channel) {
-        output.setInt(FRAME_START_BYTE, output.readableBytes());
-        output.setByte(FRAME_DOFF_BYTE, (byte) FRAME_DOFF_SIZE);
-        output.setByte(FRAME_TYPE_BYTE, frameType);
-        output.setShort(FRAME_CHANNEL_BYTE, (short) channel);
+    private static void endFrame(ByteBuffer output, byte frameType, int channel) {
+        output.putInt(FRAME_START_BYTE, output.remaining());
+        output.put(FRAME_DOFF_BYTE, (byte) FRAME_DOFF_SIZE);
+        output.put(FRAME_TYPE_BYTE, frameType);
+        output.putShort(FRAME_CHANNEL_BYTE, (short) channel);
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/LinkTracker.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/LinkTracker.java
index 04e764b9..4a495c1d 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/LinkTracker.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/LinkTracker.java
@@ -16,6 +16,8 @@
  */
 package org.apache.qpid.protonj2.test.driver;
 
+import java.nio.ByteBuffer;
+
 import org.apache.qpid.protonj2.test.driver.codec.messaging.Source;
 import org.apache.qpid.protonj2.test.driver.codec.messaging.Target;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedInteger;
@@ -28,8 +30,6 @@ import org.apache.qpid.protonj2.test.driver.codec.transport.Role;
 import org.apache.qpid.protonj2.test.driver.codec.transport.SenderSettleMode;
 import org.apache.qpid.protonj2.test.driver.codec.transport.Transfer;
 
-import io.netty5.buffer.Buffer;
-
 /**
  * Tracks information about links that are opened be the client under test.
  */
@@ -149,7 +149,7 @@ public abstract class LinkTracker {
         return this;
     }
 
-    protected abstract void handleTransfer(Transfer transfer, Buffer payload);
+    protected abstract void handleTransfer(Transfer transfer, ByteBuffer payload);
 
     protected abstract void handleFlow(Flow flow);
 
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/ProtonTestClient.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/ProtonTestClient.java
index c940d42c..b9eade1d 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/ProtonTestClient.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/ProtonTestClient.java
@@ -23,16 +23,12 @@ import java.util.function.Supplier;
 
 import org.apache.qpid.protonj2.test.driver.codec.primitives.DescribedType;
 import org.apache.qpid.protonj2.test.driver.codec.transport.AMQPHeader;
+import org.apache.qpid.protonj2.test.driver.netty.NettyIOBuilder;
 import org.apache.qpid.protonj2.test.driver.netty.NettyClient;
+import org.apache.qpid.protonj2.test.driver.netty.NettyEventLoop;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import io.netty5.buffer.Buffer;
-import io.netty5.channel.ChannelHandler;
-import io.netty5.channel.ChannelHandlerContext;
-import io.netty5.channel.EventLoop;
-import io.netty5.channel.SimpleChannelInboundHandler;
-
 /**
  * Test Client for AMQP server testing, allows for scripting the expected inputs from
  * the server and outputs from the client back to the server.
@@ -42,7 +38,7 @@ public class ProtonTestClient extends ProtonTestPeer implements AutoCloseable {
     private static final Logger LOG = LoggerFactory.getLogger(ProtonTestClient.class);
 
     private final AMQPTestDriver driver;
-    private final NettyTestDriverClient client;
+    private final NettyClient client;
 
     /**
      * Creates a Socket Test Peer using all default Server options.
@@ -63,8 +59,12 @@ public class ProtonTestClient extends ProtonTestPeer implements AutoCloseable {
      *      The options that control the behavior of the client connection.
      */
     public ProtonTestClient(ProtonTestClientOptions options) {
-        this.driver = new NettyAwareAMQPTestDriver(this::processDriverOutput, this::processDriverAssertion, this::eventLoop);
-        this.client = new NettyTestDriverClient(options);
+        this.driver = new NettyAwareAMQPTestDriver(this::processDriverOutput,
+                                                   this::processDriverAssertion,
+                                                   this::eventLoop);
+        this.client = NettyIOBuilder.createClient(options,
+                                             this::processConnectionEstablished,
+                                             this::processChannelInput);
     }
 
     public void connect(String hostname, int port) throws IOException {
@@ -97,7 +97,7 @@ public class ProtonTestClient extends ProtonTestPeer implements AutoCloseable {
         client.write(frame);
     }
 
-    protected void processChannelInput(Buffer input) {
+    protected void processChannelInput(ByteBuffer input) {
         LOG.trace("AMQP Test Client Channel processing: {}", input);
         driver.accept(input);
     }
@@ -107,7 +107,7 @@ public class ProtonTestClient extends ProtonTestPeer implements AutoCloseable {
         close();
     }
 
-    protected EventLoop eventLoop() {
+    protected NettyEventLoop eventLoop() {
         return client.eventLoop();
     }
 
@@ -115,7 +115,9 @@ public class ProtonTestClient extends ProtonTestPeer implements AutoCloseable {
 
     private final class NettyAwareAMQPTestDriver extends AMQPTestDriver {
 
-        public NettyAwareAMQPTestDriver(Consumer<ByteBuffer> frameConsumer, Consumer<AssertionError> assertionConsumer, Supplier<EventLoop> scheduler) {
+        public NettyAwareAMQPTestDriver(Consumer<ByteBuffer> frameConsumer,
+                                        Consumer<AssertionError> assertionConsumer,
+                                        Supplier<NettyEventLoop> scheduler) {
             super(getPeerName(), frameConsumer, assertionConsumer, scheduler);
         }
 
@@ -126,8 +128,8 @@ public class ProtonTestClient extends ProtonTestPeer implements AutoCloseable {
         // other driver resources being used on two different threads.
 
         @Override
-        public void deferAMQPFrame(int channel, DescribedType performative, Buffer payload, boolean splitWrite) {
-            EventLoop loop = client.eventLoop();
+        public void deferAMQPFrame(int channel, DescribedType performative, ByteBuffer payload, boolean splitWrite) {
+            NettyEventLoop loop = client.eventLoop();
             if (loop.inEventLoop()) {
                 super.deferAMQPFrame(channel, performative, payload, splitWrite);
             } else {
@@ -139,7 +141,7 @@ public class ProtonTestClient extends ProtonTestPeer implements AutoCloseable {
 
         @Override
         public void deferSaslFrame(int channel, DescribedType performative) {
-            EventLoop loop = client.eventLoop();
+            NettyEventLoop loop = client.eventLoop();
             if (loop.inEventLoop()) {
                 super.deferSaslFrame(channel, performative);
             } else {
@@ -151,7 +153,7 @@ public class ProtonTestClient extends ProtonTestPeer implements AutoCloseable {
 
         @Override
         public void deferHeader(AMQPHeader header) {
-            EventLoop loop = client.eventLoop();
+            NettyEventLoop loop = client.eventLoop();
             if (loop.inEventLoop()) {
                 super.deferHeader(header);
             } else {
@@ -162,8 +164,8 @@ public class ProtonTestClient extends ProtonTestPeer implements AutoCloseable {
         }
 
         @Override
-        public void sendAMQPFrame(int channel, DescribedType performative, Buffer payload, boolean splitWrite) {
-            EventLoop loop = client.eventLoop();
+        public void sendAMQPFrame(int channel, DescribedType performative, ByteBuffer payload, boolean splitWrite) {
+            NettyEventLoop loop = client.eventLoop();
             if (loop.inEventLoop()) {
                 super.sendAMQPFrame(channel, performative, payload, splitWrite);
             } else {
@@ -175,7 +177,7 @@ public class ProtonTestClient extends ProtonTestPeer implements AutoCloseable {
 
         @Override
         public void sendSaslFrame(int channel, DescribedType performative) {
-            EventLoop loop = client.eventLoop();
+            NettyEventLoop loop = client.eventLoop();
             if (loop.inEventLoop()) {
                 super.sendSaslFrame(channel, performative);
             } else {
@@ -187,7 +189,7 @@ public class ProtonTestClient extends ProtonTestPeer implements AutoCloseable {
 
         @Override
         public void sendHeader(AMQPHeader header) {
-            EventLoop loop = client.eventLoop();
+            NettyEventLoop loop = client.eventLoop();
             if (loop.inEventLoop()) {
                 super.sendHeader(header);
             } else {
@@ -199,7 +201,7 @@ public class ProtonTestClient extends ProtonTestPeer implements AutoCloseable {
 
         @Override
         public void sendEmptyFrame(int channel) {
-            EventLoop loop = client.eventLoop();
+            NettyEventLoop loop = client.eventLoop();
             if (loop.inEventLoop()) {
                 super.sendEmptyFrame(channel);
             } else {
@@ -209,38 +211,4 @@ public class ProtonTestClient extends ProtonTestPeer implements AutoCloseable {
             }
         }
     }
-
-    //----- Channel handler that drives IO for the test driver
-
-    private final class NettyTestDriverClient extends NettyClient {
-
-        public NettyTestDriverClient(ProtonTestClientOptions options) {
-            super(options);
-        }
-
-        @Override
-        protected ChannelHandler getClientHandler() {
-            return new SimpleChannelInboundHandler<Buffer>() {
-
-                @Override
-                public void channelActive(ChannelHandlerContext ctx) throws Exception {
-                    processConnectionEstablished();
-                    ctx.fireChannelActive();
-                }
-
-                @Override
-                protected void messageReceived(ChannelHandlerContext ctx, Buffer input) throws Exception {
-                    LOG.trace("AMQP Test Client Channel read: {}", input);
-
-                    // Driver processes new data and may produce output based on this.
-                    try (Buffer copy = input.copy(true)) {
-                        processChannelInput(copy);
-                    } catch (Throwable e) {
-                        LOG.error("Closed AMQP Test client channel due to error: ", e);
-                        ctx.channel().close();
-                    }
-                }
-            };
-        }
-    }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/ProtonTestServer.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/ProtonTestServer.java
index 06b6eb40..ad2c2884 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/ProtonTestServer.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/ProtonTestServer.java
@@ -25,16 +25,12 @@ import javax.net.ssl.SSLEngine;
 
 import org.apache.qpid.protonj2.test.driver.codec.primitives.DescribedType;
 import org.apache.qpid.protonj2.test.driver.codec.transport.AMQPHeader;
+import org.apache.qpid.protonj2.test.driver.netty.NettyIOBuilder;
+import org.apache.qpid.protonj2.test.driver.netty.NettyEventLoop;
 import org.apache.qpid.protonj2.test.driver.netty.NettyServer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import io.netty5.buffer.Buffer;
-import io.netty5.channel.ChannelHandler;
-import io.netty5.channel.ChannelHandlerContext;
-import io.netty5.channel.EventLoop;
-import io.netty5.channel.SimpleChannelInboundHandler;
-
 /**
  * Netty based AMQP Test Server implementation that can handle inbound connections
  * and script the expected AMQP frame interchange that should occur for a given test.
@@ -44,7 +40,7 @@ public class ProtonTestServer extends ProtonTestPeer {
     private static final Logger LOG = LoggerFactory.getLogger(ProtonTestServer.class);
 
     private final AMQPTestDriver driver;
-    private final NettyTestDriverServer server;
+    private final NettyServer server;
 
     /**
      * Creates a Socket Test Peer using all default Server options.
@@ -66,7 +62,9 @@ public class ProtonTestServer extends ProtonTestPeer {
      */
     public ProtonTestServer(ProtonTestServerOptions options) {
         this.driver = new NettyAwareAMQPTestDriver(this::processDriverOutput, this::processDriverAssertion, this::eventLoop);
-        this.server = new NettyTestDriverServer(options);
+        this.server = NettyIOBuilder.createServer(options,
+                                             this::processConnectionEstablished,
+                                             this::processChannelInput);
     }
 
     /**
@@ -150,45 +148,11 @@ public class ProtonTestServer extends ProtonTestPeer {
         return driver;
     }
 
-    //----- Channel handler that drives IO for the test driver
-
-    private final class NettyTestDriverServer extends NettyServer {
-
-        public NettyTestDriverServer(ProtonTestServerOptions options) {
-            super(options);
-        }
-
-        @Override
-        protected ChannelHandler getServerHandler() {
-            return new SimpleChannelInboundHandler<Buffer>() {
-
-                @Override
-                public void channelActive(ChannelHandlerContext ctx) throws Exception {
-                    processConnectionEstablished();
-                    ctx.fireChannelActive();
-                }
-
-                @Override
-                protected void messageReceived(ChannelHandlerContext ctx, Buffer input) throws Exception {
-                    LOG.trace("AMQP Test Server Channel read: {}", input);
-
-                    // Driver processes new data and may produce output based on this.
-                    try (Buffer copy = input.copy(true)) {
-                        processChannelInput(copy);
-                    } catch (Throwable e) {
-                        LOG.error("Closed AMQP Test server channel due to error: ", e);
-                        ctx.channel().close();
-                    }
-                }
-            };
-        }
-    }
-
     //----- Test driver Wrapper to ensure actions occur on the event loop
 
     private final class NettyAwareAMQPTestDriver extends AMQPTestDriver {
 
-        public NettyAwareAMQPTestDriver(Consumer<ByteBuffer> frameConsumer, Consumer<AssertionError> assertionConsumer, Supplier<EventLoop> scheduler) {
+        public NettyAwareAMQPTestDriver(Consumer<ByteBuffer> frameConsumer, Consumer<AssertionError> assertionConsumer, Supplier<NettyEventLoop> scheduler) {
             super(getPeerName(), frameConsumer, assertionConsumer, scheduler);
         }
 
@@ -199,8 +163,8 @@ public class ProtonTestServer extends ProtonTestPeer {
         // other driver resources being used on two different threads.
 
         @Override
-        public void deferAMQPFrame(int channel, DescribedType performative, Buffer payload, boolean splitWrite) {
-            EventLoop loop = server.eventLoop();
+        public void deferAMQPFrame(int channel, DescribedType performative, ByteBuffer payload, boolean splitWrite) {
+            NettyEventLoop loop = server.eventLoop();
             if (loop.inEventLoop()) {
                 super.deferAMQPFrame(channel, performative, payload, splitWrite);
             } else {
@@ -212,7 +176,7 @@ public class ProtonTestServer extends ProtonTestPeer {
 
         @Override
         public void deferSaslFrame(int channel, DescribedType performative) {
-            EventLoop loop = server.eventLoop();
+            NettyEventLoop loop = server.eventLoop();
             if (loop.inEventLoop()) {
                 super.deferSaslFrame(channel, performative);
             } else {
@@ -224,7 +188,7 @@ public class ProtonTestServer extends ProtonTestPeer {
 
         @Override
         public void deferHeader(AMQPHeader header) {
-            EventLoop loop = server.eventLoop();
+            NettyEventLoop loop = server.eventLoop();
             if (loop.inEventLoop()) {
                 super.deferHeader(header);
             } else {
@@ -235,8 +199,8 @@ public class ProtonTestServer extends ProtonTestPeer {
         }
 
         @Override
-        public void sendAMQPFrame(int channel, DescribedType performative, Buffer payload, boolean splitWrite) {
-            EventLoop loop = server.eventLoop();
+        public void sendAMQPFrame(int channel, DescribedType performative, ByteBuffer payload, boolean splitWrite) {
+            NettyEventLoop loop = server.eventLoop();
             if (loop.inEventLoop()) {
                 super.sendAMQPFrame(channel, performative, payload, splitWrite);
             } else {
@@ -248,7 +212,7 @@ public class ProtonTestServer extends ProtonTestPeer {
 
         @Override
         public void sendSaslFrame(int channel, DescribedType performative) {
-            EventLoop loop = server.eventLoop();
+            NettyEventLoop loop = server.eventLoop();
             if (loop.inEventLoop()) {
                 super.sendSaslFrame(channel, performative);
             } else {
@@ -260,7 +224,7 @@ public class ProtonTestServer extends ProtonTestPeer {
 
         @Override
         public void sendHeader(AMQPHeader header) {
-            EventLoop loop = server.eventLoop();
+            NettyEventLoop loop = server.eventLoop();
             if (loop.inEventLoop()) {
                 super.sendHeader(header);
             } else {
@@ -272,7 +236,7 @@ public class ProtonTestServer extends ProtonTestPeer {
 
         @Override
         public void sendEmptyFrame(int channel) {
-            EventLoop loop = server.eventLoop();
+            NettyEventLoop loop = server.eventLoop();
             if (loop.inEventLoop()) {
                 super.sendEmptyFrame(channel);
             } else {
@@ -311,12 +275,12 @@ public class ProtonTestServer extends ProtonTestPeer {
         close();
     }
 
-    protected void processChannelInput(Buffer input) {
+    protected void processChannelInput(ByteBuffer input) {
         LOG.trace("AMQP Server Channel processing: {}", input);
         driver.accept(input);
     }
 
-    protected EventLoop eventLoop() {
+    protected NettyEventLoop eventLoop() {
         return server.eventLoop();
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/ReceiverTracker.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/ReceiverTracker.java
index a9462136..57b32365 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/ReceiverTracker.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/ReceiverTracker.java
@@ -16,11 +16,11 @@
  */
 package org.apache.qpid.protonj2.test.driver;
 
+import java.nio.ByteBuffer;
+
 import org.apache.qpid.protonj2.test.driver.codec.transport.Flow;
 import org.apache.qpid.protonj2.test.driver.codec.transport.Transfer;
 
-import io.netty5.buffer.Buffer;
-
 /**
  * Link Tracker that manages tracking of the peer Receiver link which will
  * handle flows and receive transfers to a remote Sender link.
@@ -32,7 +32,7 @@ public class ReceiverTracker extends LinkTracker {
     }
 
     @Override
-    protected void handleTransfer(Transfer transfer, Buffer payload) {
+    protected void handleTransfer(Transfer transfer, ByteBuffer payload) {
         // TODO: Update internal state
     }
 
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/ScriptedAction.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/ScriptedAction.java
index e9888530..dcf6564c 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/ScriptedAction.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/ScriptedAction.java
@@ -16,6 +16,7 @@
  */
 package org.apache.qpid.protonj2.test.driver;
 
+import java.nio.ByteBuffer;
 import java.util.function.Consumer;
 
 import org.apache.qpid.protonj2.test.driver.codec.security.SaslChallenge;
@@ -34,8 +35,6 @@ import org.apache.qpid.protonj2.test.driver.codec.transport.Flow;
 import org.apache.qpid.protonj2.test.driver.codec.transport.Open;
 import org.apache.qpid.protonj2.test.driver.codec.transport.Transfer;
 
-import io.netty5.buffer.Buffer;
-
 /**
  * Entry in the test script that produces some output to be sent to the AMQP
  * peer under test.
@@ -100,47 +99,47 @@ public interface ScriptedAction extends ScriptedElement {
     }
 
     @Override
-    default void handleOpen(int frameSize, Open open, Buffer payload, int channel, AMQPTestDriver context) {
+    default void handleOpen(int frameSize, Open open, ByteBuffer payload, int channel, AMQPTestDriver context) {
         throw new AssertionError("Open arrived when expecting to perform an action");
     }
 
     @Override
-    default void handleBegin(int frameSize, Begin begin, Buffer payload, int channel, AMQPTestDriver context) {
+    default void handleBegin(int frameSize, Begin begin, ByteBuffer payload, int channel, AMQPTestDriver context) {
         throw new AssertionError("Begin arrived when expecting to perform an action");
     }
 
     @Override
-    default void handleAttach(int frameSize, Attach attach, Buffer payload, int channel, AMQPTestDriver context) {
+    default void handleAttach(int frameSize, Attach attach, ByteBuffer payload, int channel, AMQPTestDriver context) {
         throw new AssertionError("Attach arrived when expecting to perform an action");
     }
 
     @Override
-    default void handleFlow(int frameSize, Flow flow, Buffer payload, int channel, AMQPTestDriver context) {
+    default void handleFlow(int frameSize, Flow flow, ByteBuffer payload, int channel, AMQPTestDriver context) {
         throw new AssertionError("Flow arrived when expecting to perform an action");
     }
 
     @Override
-    default void handleTransfer(int frameSize, Transfer transfer, Buffer payload, int channel, AMQPTestDriver context) {
+    default void handleTransfer(int frameSize, Transfer transfer, ByteBuffer payload, int channel, AMQPTestDriver context) {
         throw new AssertionError("Transfer arrived when expecting to perform an action");
     }
 
     @Override
-    default void handleDisposition(int frameSize, Disposition disposition, Buffer payload, int channel, AMQPTestDriver context) {
+    default void handleDisposition(int frameSize, Disposition disposition, ByteBuffer payload, int channel, AMQPTestDriver context) {
         throw new AssertionError("Disposition arrived when expecting to perform an action");
     }
 
     @Override
-    default void handleDetach(int frameSize, Detach detach, Buffer payload, int channel, AMQPTestDriver context) {
+    default void handleDetach(int frameSize, Detach detach, ByteBuffer payload, int channel, AMQPTestDriver context) {
         throw new AssertionError("Detach arrived when expecting to perform an action");
     }
 
     @Override
-    default void handleEnd(int frameSize, End end, Buffer payload, int channel, AMQPTestDriver context) {
+    default void handleEnd(int frameSize, End end, ByteBuffer payload, int channel, AMQPTestDriver context) {
         throw new AssertionError("End arrived when expecting to perform an action");
     }
 
     @Override
-    default void handleClose(int frameSize, Close close, Buffer payload, int channel, AMQPTestDriver context) {
+    default void handleClose(int frameSize, Close close, ByteBuffer payload, int channel, AMQPTestDriver context) {
         throw new AssertionError("Close arrived when expecting to perform an action");
     }
 
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/SenderTracker.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/SenderTracker.java
index 82686c15..a0678d6b 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/SenderTracker.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/SenderTracker.java
@@ -16,11 +16,11 @@
  */
 package org.apache.qpid.protonj2.test.driver;
 
+import java.nio.ByteBuffer;
+
 import org.apache.qpid.protonj2.test.driver.codec.transport.Flow;
 import org.apache.qpid.protonj2.test.driver.codec.transport.Transfer;
 
-import io.netty5.buffer.Buffer;
-
 /**
  * Link Tracker that manages tracking of the peer Sender link which will
  * handle flows and initiate transfers to a remote Receiver link.
@@ -32,7 +32,7 @@ public class SenderTracker extends LinkTracker {
     }
 
     @Override
-    protected void handleTransfer(Transfer transfer, Buffer payload) {
+    protected void handleTransfer(Transfer transfer, ByteBuffer payload) {
         // TODO Handle sender scripted transfer by updating local state.
     }
 
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/SessionTracker.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/SessionTracker.java
index 8ccaef8f..d5f675f4 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/SessionTracker.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/SessionTracker.java
@@ -16,6 +16,7 @@
  */
 package org.apache.qpid.protonj2.test.driver;
 
+import java.nio.ByteBuffer;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicReference;
@@ -31,8 +32,6 @@ import org.apache.qpid.protonj2.test.driver.codec.transport.Flow;
 import org.apache.qpid.protonj2.test.driver.codec.transport.Role;
 import org.apache.qpid.protonj2.test.driver.codec.transport.Transfer;
 
-import io.netty5.buffer.Buffer;
-
 /**
  * Tracks information related to an opened Session and its various links
  */
@@ -319,7 +318,7 @@ public class SessionTracker {
         return tracker;
     }
 
-    public LinkTracker handleTransfer(Transfer transfer, Buffer payload) {
+    public LinkTracker handleTransfer(Transfer transfer, ByteBuffer payload) {
         LinkTracker tracker = remoteLinks.get(transfer.getHandle());
 
         if (tracker.isSender()) {
@@ -332,7 +331,7 @@ public class SessionTracker {
         return tracker;
     }
 
-    public void handleLocalTransfer(Transfer transfer, Buffer payload) {
+    public void handleLocalTransfer(Transfer transfer, ByteBuffer payload) {
         LinkTracker tracker = localLinks.get(transfer.getHandle());
 
         // Pass along to local sender for processing before sending and ignore if
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/actions/AbstractPerformativeInjectAction.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/actions/AbstractPerformativeInjectAction.java
index 25c76487..a12e9f0d 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/actions/AbstractPerformativeInjectAction.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/actions/AbstractPerformativeInjectAction.java
@@ -16,14 +16,14 @@
  */
 package org.apache.qpid.protonj2.test.driver.actions;
 
+import java.nio.ByteBuffer;
+
 import org.apache.qpid.protonj2.test.driver.AMQPTestDriver;
 import org.apache.qpid.protonj2.test.driver.DeferrableScriptedAction;
 import org.apache.qpid.protonj2.test.driver.ScriptedAction;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.DescribedType;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedShort;
 
-import io.netty5.buffer.Buffer;
-
 /**
  * Abstract base used by inject actions of AMQP Performatives
  *
@@ -156,7 +156,7 @@ public abstract class AbstractPerformativeInjectAction<P extends DescribedType>
     /**
      * @return the buffer containing the payload that should be sent as part of this action.
      */
-    public Buffer getPayload() {
+    public ByteBuffer getPayload() {
         return null;
     }
 
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/actions/ByteBufferInjectAction.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/actions/ByteBufferInjectAction.java
index 84663225..abdd5274 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/actions/ByteBufferInjectAction.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/actions/ByteBufferInjectAction.java
@@ -16,20 +16,20 @@
  */
 package org.apache.qpid.protonj2.test.driver.actions;
 
+import java.nio.ByteBuffer;
+
 import org.apache.qpid.protonj2.test.driver.AMQPTestDriver;
 import org.apache.qpid.protonj2.test.driver.ScriptedAction;
 
-import io.netty5.buffer.Buffer;
-
 /**
  * Scripted action that will write the contents of a given buffer out through the driver.
  */
 public class ByteBufferInjectAction implements ScriptedAction {
 
-    private final Buffer buffer;
+    private final ByteBuffer buffer;
     private final AMQPTestDriver driver;
 
-    public ByteBufferInjectAction(AMQPTestDriver driver, Buffer buffer) {
+    public ByteBufferInjectAction(AMQPTestDriver driver, ByteBuffer buffer) {
         this.buffer = buffer;
         this.driver = driver;
     }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/actions/RawBytesInjectAction.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/actions/RawBytesInjectAction.java
index d00cbc05..502293f7 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/actions/RawBytesInjectAction.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/actions/RawBytesInjectAction.java
@@ -16,12 +16,11 @@
  */
 package org.apache.qpid.protonj2.test.driver.actions;
 
+import java.nio.ByteBuffer;
+
 import org.apache.qpid.protonj2.test.driver.AMQPTestDriver;
 import org.apache.qpid.protonj2.test.driver.ScriptedAction;
 
-import io.netty5.buffer.Buffer;
-import io.netty5.buffer.BufferAllocator;
-
 /**
  * AMQP Empty Frame injection action which can be added to a driver for write at a specific time or
  * following on from some other action in the test script.
@@ -29,7 +28,7 @@ import io.netty5.buffer.BufferAllocator;
 public class RawBytesInjectAction implements ScriptedAction {
 
     private final AMQPTestDriver driver;
-    private Buffer buffer;
+    private ByteBuffer buffer;
 
     public RawBytesInjectAction(AMQPTestDriver driver) {
         this.driver = driver;
@@ -63,7 +62,10 @@ public class RawBytesInjectAction implements ScriptedAction {
     }
 
     public RawBytesInjectAction withBytes(byte[] bytes) {
-        this.buffer = BufferAllocator.onHeapUnpooled().copyOf(bytes);
+        this.buffer = ByteBuffer.allocate(bytes.length);
+        this.buffer.put(bytes);
+        this.buffer.flip().asReadOnlyBuffer();
+
         return this;
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/actions/TransferInjectAction.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/actions/TransferInjectAction.java
index 24c0d562..fa716663 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/actions/TransferInjectAction.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/actions/TransferInjectAction.java
@@ -16,6 +16,11 @@
  */
 package org.apache.qpid.protonj2.test.driver.actions;
 
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.ByteBuffer;
 import java.util.Date;
 import java.util.List;
 import java.util.UUID;
@@ -47,9 +52,6 @@ import org.apache.qpid.protonj2.test.driver.codec.transport.ErrorCondition;
 import org.apache.qpid.protonj2.test.driver.codec.transport.ReceiverSettleMode;
 import org.apache.qpid.protonj2.test.driver.codec.transport.Transfer;
 
-import io.netty5.buffer.Buffer;
-import io.netty5.buffer.BufferAllocator;
-
 /**
  * AMQP Close injection action which can be added to a driver for write at a specific time or
  * following on from some other action in the test script.
@@ -59,7 +61,7 @@ public class TransferInjectAction extends AbstractPerformativeInjectAction<Trans
     private final Transfer transfer = new Transfer();
     private final DeliveryStateBuilder stateBuilder = new DeliveryStateBuilder();
 
-    private Buffer payload;
+    private ByteBuffer payload;
 
     private Header header;
     private DeliveryAnnotations deliveryAnnotations;
@@ -81,7 +83,7 @@ public class TransferInjectAction extends AbstractPerformativeInjectAction<Trans
     }
 
     @Override
-    public Buffer getPayload() {
+    public ByteBuffer getPayload() {
         if (payload == null) {
             payload = encodePayload();
         }
@@ -212,11 +214,14 @@ public class TransferInjectAction extends AbstractPerformativeInjectAction<Trans
     }
 
     public TransferInjectAction withPayload(byte[] payload) {
-        this.payload = BufferAllocator.onHeapUnpooled().copyOf(payload);
+        this.payload = ByteBuffer.allocate(payload.length);
+        this.payload.put(payload);
+        this.payload.flip().asReadOnlyBuffer();
+
         return this;
     }
 
-    public TransferInjectAction withPayload(Buffer payload) {
+    public TransferInjectAction withPayload(ByteBuffer payload) {
         this.payload = payload;
         return this;
     }
@@ -293,36 +298,43 @@ public class TransferInjectAction extends AbstractPerformativeInjectAction<Trans
         return footer;
     }
 
-    private Buffer encodePayload() {
+    private ByteBuffer encodePayload() {
         org.apache.qpid.protonj2.test.driver.codec.Codec codec =
             org.apache.qpid.protonj2.test.driver.codec.Codec.Factory.create();
-        final Buffer buffer = BufferAllocator.onHeapUnpooled().allocate(128);
 
-        if (header != null) {
-            codec.putDescribedType(header);
-        }
-        if (deliveryAnnotations != null) {
-            codec.putDescribedType(deliveryAnnotations);
-        }
-        if (messageAnnotations != null) {
-            codec.putDescribedType(messageAnnotations);
-        }
-        if (properties != null) {
-            codec.putDescribedType(properties);
+        try (ByteArrayOutputStream baOS = new ByteArrayOutputStream(128);
+             DataOutputStream output = new DataOutputStream(baOS)) {
+
+            if (header != null) {
+                codec.putDescribedType(header);
+            }
+            if (deliveryAnnotations != null) {
+                codec.putDescribedType(deliveryAnnotations);
+            }
+            if (messageAnnotations != null) {
+                codec.putDescribedType(messageAnnotations);
+            }
+            if (properties != null) {
+                codec.putDescribedType(properties);
+            }
+            if (applicationProperties != null) {
+                codec.putDescribedType(applicationProperties);
+            }
+            if (body != null) {
+                codec.putDescribedType(body);
+            }
+            if (footer != null) {
+                codec.putDescribedType(footer);
+            }
+
+            codec.encode(output);
+
+            final byte[] encodedBytes = baOS.toByteArray();
+
+            return ByteBuffer.wrap(encodedBytes);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
         }
-        if (applicationProperties != null) {
-            codec.putDescribedType(applicationProperties);
-        }
-        if (body != null) {
-            codec.putDescribedType(body);
-        }
-        if (footer != null) {
-            codec.putDescribedType(footer);
-        }
-
-        codec.encode(buffer);
-
-        return buffer.makeReadOnly();
     }
 
     protected abstract class SectionBuilder {
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/ArrayElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/ArrayElement.java
index c705fdda..6a761ecf 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/ArrayElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/ArrayElement.java
@@ -16,11 +16,13 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+
 import org.apache.qpid.protonj2.test.driver.codec.primitives.DescribedType;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.Symbol;
 
-import io.netty5.buffer.Buffer;
-
 class ArrayElement extends AbstractElement<Object[]> {
 
     private final boolean described;
@@ -151,134 +153,134 @@ class ArrayElement extends AbstractElement<Object[]> {
     }
 
     @Override
-    public int encode(Buffer buffer) {
+    public int encode(DataOutput output) {
         int size = size();
 
         final int count = (int) count();
 
-        if (buffer.implicitCapacityLimit() - buffer.capacity() >= size) {
+        try {
             if (!isElementOfArray()) {
                 if (size > 257 || count > 255) {
-                    buffer.writeByte((byte) 0xf0);
-                    buffer.writeInt(size - 5);
-                    buffer.writeInt(count);
+                    output.writeByte((byte) 0xf0);
+                    output.writeInt(size - 5);
+                    output.writeInt(count);
                 } else {
-                    buffer.writeByte((byte) 0xe0);
-                    buffer.writeByte((byte) (size - 2));
-                    buffer.writeByte((byte) count);
+                    output.writeByte((byte) 0xe0);
+                    output.writeByte((byte) (size - 2));
+                    output.writeByte((byte) count);
                 }
             } else {
                 ArrayElement parent = (ArrayElement) parent();
                 if (parent.constructorType() == SMALL) {
-                    buffer.writeByte((byte) (size - 1));
-                    buffer.writeByte((byte) count);
+                    output.writeByte((byte) (size - 1));
+                    output.writeByte((byte) count);
                 } else {
-                    buffer.writeInt(size - 4);
-                    buffer.writeInt(count);
+                    output.writeInt(size - 4);
+                    output.writeInt(count);
                 }
             }
             Element<?> element = first;
             if (isDescribed()) {
-                buffer.writeByte((byte) 0);
+                output.writeByte((byte) 0);
                 if (element == null) {
-                    buffer.writeByte((byte) 0x40);
+                    output.writeByte((byte) 0x40);
                 } else {
-                    element.encode(buffer);
+                    element.encode(output);
                     element = element.next();
                 }
             }
             switch (arrayType) {
                 case NULL:
-                    buffer.writeByte((byte) 0x40);
+                    output.writeByte((byte) 0x40);
                     break;
                 case BOOL:
-                    buffer.writeByte((byte) 0x56);
+                    output.writeByte((byte) 0x56);
                     break;
                 case UBYTE:
-                    buffer.writeByte((byte) 0x50);
+                    output.writeByte((byte) 0x50);
                     break;
                 case BYTE:
-                    buffer.writeByte((byte) 0x51);
+                    output.writeByte((byte) 0x51);
                     break;
                 case USHORT:
-                    buffer.writeByte((byte) 0x60);
+                    output.writeByte((byte) 0x60);
                     break;
                 case SHORT:
-                    buffer.writeByte((byte) 0x61);
+                    output.writeByte((byte) 0x61);
                     break;
                 case UINT:
                     switch (constructorType()) {
                         case TINY:
-                            buffer.writeByte((byte) 0x43);
+                            output.writeByte((byte) 0x43);
                             break;
                         case SMALL:
-                            buffer.writeByte((byte) 0x52);
+                            output.writeByte((byte) 0x52);
                             break;
                         case LARGE:
-                            buffer.writeByte((byte) 0x70);
+                            output.writeByte((byte) 0x70);
                             break;
                     }
                     break;
                 case INT:
-                    buffer.writeByte(constructorType == SMALL ? (byte) 0x54 : (byte) 0x71);
+                    output.writeByte(constructorType == SMALL ? (byte) 0x54 : (byte) 0x71);
                     break;
                 case CHAR:
-                    buffer.writeByte((byte) 0x73);
+                    output.writeByte((byte) 0x73);
                     break;
                 case ULONG:
                     switch (constructorType()) {
                         case TINY:
-                            buffer.writeByte((byte) 0x44);
+                            output.writeByte((byte) 0x44);
                             break;
                         case SMALL:
-                            buffer.writeByte((byte) 0x53);
+                            output.writeByte((byte) 0x53);
                             break;
                         case LARGE:
-                            buffer.writeByte((byte) 0x80);
+                            output.writeByte((byte) 0x80);
                             break;
                     }
                     break;
                 case LONG:
-                    buffer.writeByte(constructorType == SMALL ? (byte) 0x55 : (byte) 0x81);
+                    output.writeByte(constructorType == SMALL ? (byte) 0x55 : (byte) 0x81);
                     break;
                 case TIMESTAMP:
-                    buffer.writeByte((byte) 0x83);
+                    output.writeByte((byte) 0x83);
                     break;
                 case FLOAT:
-                    buffer.writeByte((byte) 0x72);
+                    output.writeByte((byte) 0x72);
                     break;
                 case DOUBLE:
-                    buffer.writeByte((byte) 0x82);
+                    output.writeByte((byte) 0x82);
                     break;
                 case DECIMAL32:
-                    buffer.writeByte((byte) 0x74);
+                    output.writeByte((byte) 0x74);
                     break;
                 case DECIMAL64:
-                    buffer.writeByte((byte) 0x84);
+                    output.writeByte((byte) 0x84);
                     break;
                 case DECIMAL128:
-                    buffer.writeByte((byte) 0x94);
+                    output.writeByte((byte) 0x94);
                     break;
                 case UUID:
-                    buffer.writeByte((byte) 0x98);
+                    output.writeByte((byte) 0x98);
                     break;
                 case BINARY:
-                    buffer.writeByte(constructorType == SMALL ? (byte) 0xa0 : (byte) 0xb0);
+                    output.writeByte(constructorType == SMALL ? (byte) 0xa0 : (byte) 0xb0);
                     break;
                 case STRING:
-                    buffer.writeByte(constructorType == SMALL ? (byte) 0xa1 : (byte) 0xb1);
+                    output.writeByte(constructorType == SMALL ? (byte) 0xa1 : (byte) 0xb1);
                     break;
                 case SYMBOL:
-                    buffer.writeByte(constructorType == SMALL ? (byte) 0xa3 : (byte) 0xb3);
+                    output.writeByte(constructorType == SMALL ? (byte) 0xa3 : (byte) 0xb3);
                     break;
                 case ARRAY:
-                    buffer.writeByte(constructorType == SMALL ? (byte) 0xe0 : (byte) 0xf0);
+                    output.writeByte(constructorType == SMALL ? (byte) 0xe0 : (byte) 0xf0);
                     break;
                 case LIST:
-                    buffer.writeByte(constructorType == TINY ? (byte) 0x45 : constructorType == SMALL ? (byte) 0xc0 : (byte) 0xd0);
+                    output.writeByte(constructorType == TINY ? (byte) 0x45 : constructorType == SMALL ? (byte) 0xc0 : (byte) 0xd0);
                     break;
                 case MAP:
-                    buffer.writeByte(constructorType == SMALL ? (byte) 0xc1 : (byte) 0xd1);
+                    output.writeByte(constructorType == SMALL ? (byte) 0xc1 : (byte) 0xd1);
                     break;
                 case DESCRIBED:
                     break;
@@ -286,12 +288,12 @@ class ArrayElement extends AbstractElement<Object[]> {
                     break;
             }
             while (element != null) {
-                element.encode(buffer);
+                element.encode(output);
                 element = element.next();
             }
             return size;
-        } else {
-            return 0;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
         }
     }
 
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/BinaryElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/BinaryElement.java
index 1d883227..aac144c2 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/BinaryElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/BinaryElement.java
@@ -16,9 +16,11 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
-import org.apache.qpid.protonj2.test.driver.codec.primitives.Binary;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 
-import io.netty5.buffer.Buffer;
+import org.apache.qpid.protonj2.test.driver.codec.primitives.Binary;
 
 class BinaryElement extends AtomicElement<Binary> {
 
@@ -68,29 +70,30 @@ class BinaryElement extends AtomicElement<Binary> {
     }
 
     @Override
-    public int encode(Buffer buffer) {
-        int size = size();
-        if (buffer.implicitCapacityLimit() - buffer.capacity() < size) {
-            return 0;
-        }
+    public int encode(DataOutput output) {
+        try {
+            int size = size();
 
-        if (isElementOfArray()) {
-            final ArrayElement parent = (ArrayElement) parent();
+            if (isElementOfArray()) {
+                final ArrayElement parent = (ArrayElement) parent();
 
-            if (parent.constructorType() == ArrayElement.SMALL) {
-                buffer.writeByte((byte) value.getLength());
+                if (parent.constructorType() == ArrayElement.SMALL) {
+                    output.writeByte((byte) value.getLength());
+                } else {
+                    output.writeInt(value.getLength());
+                }
+            } else if (value.getLength() <= 255) {
+                output.writeByte((byte) 0xa0);
+                output.writeByte((byte) value.getLength());
             } else {
-                buffer.writeInt(value.getLength());
+                output.writeByte((byte) 0xb0);
+                output.writeInt(value.getLength());
             }
-        } else if (value.getLength() <= 255) {
-            buffer.writeByte((byte) 0xa0);
-            buffer.writeByte((byte) value.getLength());
-        } else {
-            buffer.writeByte((byte) 0xb0);
-            buffer.writeInt(value.getLength());
-        }
 
-        buffer.writeBytes(value.getArray(), 0, value.getLength());
-        return size;
+            output.write(value.getArray(), 0, value.getLength());
+            return size;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/BooleanElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/BooleanElement.java
index 93adb3dd..53c0fcde 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/BooleanElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/BooleanElement.java
@@ -16,7 +16,9 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
-import io.netty5.buffer.Buffer;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 
 class BooleanElement extends AtomicElement<Boolean> {
 
@@ -46,15 +48,16 @@ class BooleanElement extends AtomicElement<Boolean> {
     }
 
     @Override
-    public int encode(Buffer buffer) {
-        if (buffer.writableBytes() > 0) {
+    public int encode(DataOutput output) {
+        try {
             if (isElementOfArray()) {
-                buffer.writeByte(value ? (byte) 1 : (byte) 0);
+                output.writeByte(value ? (byte) 1 : (byte) 0);
             } else {
-                buffer.writeByte(value ? (byte) 0x41 : (byte) 0x42);
+                output.writeByte(value ? (byte) 0x41 : (byte) 0x42);
             }
             return 1;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
         }
-        return 0;
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/ByteElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/ByteElement.java
index 2cfe9b9d..24f616b5 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/ByteElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/ByteElement.java
@@ -16,7 +16,9 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
-import io.netty5.buffer.Buffer;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 
 class ByteElement extends AtomicElement<Byte> {
 
@@ -43,19 +45,18 @@ class ByteElement extends AtomicElement<Byte> {
     }
 
     @Override
-    public int encode(Buffer buffer) {
-        if (isElementOfArray()) {
-            if (buffer.writableBytes() > 0) {
-                buffer.writeByte(value);
+    public int encode(DataOutput output) {
+        try {
+            if (isElementOfArray()) {
+                output.writeByte(value);
                 return 1;
-            }
-        } else {
-            if ((buffer.implicitCapacityLimit() - buffer.capacity()) >= 2) {
-                buffer.writeByte((byte) 0x51);
-                buffer.writeByte(value);
+            } else {
+                output.writeByte((byte) 0x51);
+                output.writeByte(value);
                 return 2;
             }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
         }
-        return 0;
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/CharElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/CharElement.java
index 51e0a1c4..7597382d 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/CharElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/CharElement.java
@@ -16,7 +16,9 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
-import io.netty5.buffer.Buffer;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 
 class CharElement extends AtomicElement<Integer> {
 
@@ -43,14 +45,17 @@ class CharElement extends AtomicElement<Integer> {
     }
 
     @Override
-    public int encode(Buffer buffer) {
+    public int encode(DataOutput output) {
         final int size = size();
-        if (size <= buffer.implicitCapacityLimit() - buffer.capacity()) {
+
+        try {
             if (size == 5) {
-                buffer.writeByte((byte) 0x73);
+                output.writeByte((byte) 0x73);
             }
-            buffer.writeInt(value);
+            output.writeInt(value);
+            return size;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
         }
-        return 0;
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/Codec.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/Codec.java
index c6055302..b1426bf0 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/Codec.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/Codec.java
@@ -16,6 +16,8 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
@@ -32,8 +34,6 @@ import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedInteger;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedLong;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedShort;
 
-import io.netty5.buffer.Buffer;
-
 public interface Codec {
 
     public static final class Factory {
@@ -91,9 +91,9 @@ public interface Codec {
 
     long encodedSize();
 
-    long encode(Buffer buffer);
+    long encode(OutputStream output);
 
-    long decode(Buffer buffer);
+    long decode(ByteBuffer input);
 
     void putList();
 
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/CodecImpl.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/CodecImpl.java
index 0eabbc03..59f17dbd 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/CodecImpl.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/CodecImpl.java
@@ -16,6 +16,11 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.nio.ByteBuffer;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
@@ -32,8 +37,6 @@ import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedInteger;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedLong;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedShort;
 
-import io.netty5.buffer.Buffer;
-
 public class CodecImpl implements Codec {
 
     private Element<?> first;
@@ -124,24 +127,25 @@ public class CodecImpl implements Codec {
     }
 
     @Override
-    public long encode(Buffer buffer) {
-        Element<?> elt = first;
-        int size = 0;
-        while (elt != null) {
-            final int eltSize = elt.size();
-            if (eltSize <= buffer.implicitCapacityLimit()) {
-                size += elt.encode(buffer);
-            } else {
-                size += eltSize;
+    public long encode(OutputStream output) {
+        try (DataOutputStream daos = new DataOutputStream(output)) {
+            Element<?> element = first;
+            int size = 0;
+
+            while (element != null) {
+                size += element.encode(daos);
+                element = element.next();
             }
-            elt = elt.next();
+
+            return size;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
         }
-        return size;
     }
 
     @Override
-    public long decode(Buffer buffer) {
-        return TypeDecoder.decode(buffer, this);
+    public long decode(ByteBuffer input) {
+        return TypeDecoder.decode(input, this);
     }
 
     private void putElement(Element<?> element) {
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/Decimal128Element.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/Decimal128Element.java
index 608a0adf..4ff5e71c 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/Decimal128Element.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/Decimal128Element.java
@@ -16,9 +16,11 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
-import org.apache.qpid.protonj2.test.driver.codec.primitives.Decimal128;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 
-import io.netty5.buffer.Buffer;
+import org.apache.qpid.protonj2.test.driver.codec.primitives.Decimal128;
 
 class Decimal128Element extends AtomicElement<Decimal128> {
 
@@ -45,17 +47,18 @@ class Decimal128Element extends AtomicElement<Decimal128> {
     }
 
     @Override
-    public int encode(Buffer buffer) {
+    public int encode(DataOutput output) {
         int size = size();
-        if (buffer.implicitCapacityLimit() - buffer.capacity() >= size) {
+
+        try {
             if (size == 17) {
-                buffer.writeByte((byte) 0x94);
+                output.writeByte((byte) 0x94);
             }
-            buffer.writeLong(value.getMostSignificantBits());
-            buffer.writeLong(value.getLeastSignificantBits());
+            output.writeLong(value.getMostSignificantBits());
+            output.writeLong(value.getLeastSignificantBits());
             return size;
-        } else {
-            return 0;
+        } catch(IOException e) {
+            throw new UncheckedIOException(e);
         }
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/Decimal32Element.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/Decimal32Element.java
index 887d0c6f..a08e6d7a 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/Decimal32Element.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/Decimal32Element.java
@@ -16,9 +16,11 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
-import org.apache.qpid.protonj2.test.driver.codec.primitives.Decimal32;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 
-import io.netty5.buffer.Buffer;
+import org.apache.qpid.protonj2.test.driver.codec.primitives.Decimal32;
 
 class Decimal32Element extends AtomicElement<Decimal32> {
 
@@ -45,16 +47,17 @@ class Decimal32Element extends AtomicElement<Decimal32> {
     }
 
     @Override
-    public int encode(Buffer buffer) {
+    public int encode(DataOutput output) {
         int size = size();
-        if (buffer.implicitCapacityLimit() - buffer.capacity() >= size) {
+
+        try {
             if (size == 5) {
-                buffer.writeByte((byte) 0x74);
+                output.writeByte((byte) 0x74);
             }
-            buffer.writeInt(value.getBits());
+            output.writeInt(value.getBits());
             return size;
-        } else {
-            return 0;
+        } catch(IOException e) {
+            throw new UncheckedIOException(e);
         }
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/Decimal64Element.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/Decimal64Element.java
index a0eda734..16775a7d 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/Decimal64Element.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/Decimal64Element.java
@@ -16,9 +16,11 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
-import org.apache.qpid.protonj2.test.driver.codec.primitives.Decimal64;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 
-import io.netty5.buffer.Buffer;
+import org.apache.qpid.protonj2.test.driver.codec.primitives.Decimal64;
 
 class Decimal64Element extends AtomicElement<Decimal64> {
 
@@ -45,16 +47,17 @@ class Decimal64Element extends AtomicElement<Decimal64> {
     }
 
     @Override
-    public int encode(Buffer buffer) {
-        int size = size();
-        if (buffer.implicitCapacityLimit() - buffer.capacity() >= size) {
+    public int encode(DataOutput output) {
+        final int size = size();
+
+        try {
             if (size == 9) {
-                buffer.writeByte((byte) 0x84);
+                output.writeByte((byte) 0x84);
             }
-            buffer.writeLong(value.getBits());
+            output.writeLong(value.getBits());
             return size;
-        } else {
-            return 0;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
         }
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/DescribedTypeElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/DescribedTypeElement.java
index a94c4364..be1ac8a9 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/DescribedTypeElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/DescribedTypeElement.java
@@ -16,9 +16,11 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
-import org.apache.qpid.protonj2.test.driver.codec.primitives.DescribedType;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 
-import io.netty5.buffer.Buffer;
+import org.apache.qpid.protonj2.test.driver.codec.primitives.DescribedType;
 
 class DescribedTypeElement extends AbstractElement<DescribedType> {
 
@@ -68,25 +70,26 @@ class DescribedTypeElement extends AbstractElement<DescribedType> {
     }
 
     @Override
-    public int encode(Buffer buffer) {
+    public int encode(DataOutput output) {
         int encodedSize = size();
 
-        if (encodedSize > buffer.implicitCapacityLimit() - buffer.capacity()) {
-            return 0;
-        } else {
-            buffer.writeByte((byte) 0);
+        try {
+            output.writeByte((byte) 0);
             if (first == null) {
-                buffer.writeByte((byte) 0x40);
-                buffer.writeByte((byte) 0x40);
+                output.writeByte((byte) 0x40);
+                output.writeByte((byte) 0x40);
             } else {
-                first.encode(buffer);
+                first.encode(output);
                 if (first.next() == null) {
-                    buffer.writeByte((byte) 0x40);
+                    output.writeByte((byte) 0x40);
                 } else {
-                    first.next().encode(buffer);
+                    first.next().encode(output);
                 }
             }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
         }
+
         return encodedSize;
     }
 
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/DoubleElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/DoubleElement.java
index 3ae02139..ea17351c 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/DoubleElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/DoubleElement.java
@@ -16,7 +16,9 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
-import io.netty5.buffer.Buffer;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 
 class DoubleElement extends AtomicElement<Double> {
 
@@ -43,16 +45,17 @@ class DoubleElement extends AtomicElement<Double> {
     }
 
     @Override
-    public int encode(Buffer buffer) {
+    public int encode(DataOutput output) {
         int size = size();
-        if (buffer.implicitCapacityLimit() - buffer.capacity() >= size) {
+
+        try {
             if (size == 9) {
-                buffer.writeByte((byte) 0x82);
+                output.writeByte((byte) 0x82);
             }
-            buffer.writeDouble(value);
+            output.writeDouble(value);
             return size;
-        } else {
-            return 0;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
         }
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/Element.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/Element.java
index c94c3489..97af43b9 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/Element.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/Element.java
@@ -16,7 +16,7 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
-import io.netty5.buffer.Buffer;
+import java.io.DataOutput;
 
 interface Element<T> {
 
@@ -26,7 +26,7 @@ interface Element<T> {
 
     Codec.DataType getDataType();
 
-    int encode(Buffer buffer);
+    int encode(DataOutput output);
 
     Element<?> next();
 
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/FloatElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/FloatElement.java
index 2ca919af..0f0af418 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/FloatElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/FloatElement.java
@@ -16,7 +16,9 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
-import io.netty5.buffer.Buffer;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 
 class FloatElement extends AtomicElement<Float> {
 
@@ -43,16 +45,17 @@ class FloatElement extends AtomicElement<Float> {
     }
 
     @Override
-    public int encode(Buffer buffer) {
+    public int encode(DataOutput output) {
         int size = size();
-        if (buffer.implicitCapacityLimit() >= size) {
+
+        try {
             if (size == 5) {
-                buffer.writeByte((byte) 0x72);
+                output.writeByte((byte) 0x72);
             }
-            buffer.writeFloat(value);
+            output.writeFloat(value);
             return size;
-        } else {
-            return 0;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
         }
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/IntegerElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/IntegerElement.java
index ae657946..815738af 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/IntegerElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/IntegerElement.java
@@ -16,7 +16,9 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
-import io.netty5.buffer.Buffer;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 
 class IntegerElement extends AtomicElement<Integer> {
 
@@ -57,25 +59,25 @@ class IntegerElement extends AtomicElement<Integer> {
     }
 
     @Override
-    public int encode(Buffer buffer) {
+    public int encode(DataOutput output) {
         int size = size();
-        if (size <= buffer.implicitCapacityLimit() - buffer.capacity()) {
+
+        try {
             switch (size) {
                 case 2:
-                    buffer.writeByte((byte) 0x54);
+                    output.writeByte((byte) 0x54);
                 case 1:
-                    buffer.writeByte((byte) value);
+                    output.writeByte((byte) value);
                     break;
-
                 case 5:
-                    buffer.writeByte((byte) 0x71);
+                    output.writeByte((byte) 0x71);
                 case 4:
-                    buffer.writeInt(value);
-
+                    output.writeInt(value);
             }
 
             return size;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
         }
-        return 0;
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/ListElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/ListElement.java
index 2e1a4870..d9aae25e 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/ListElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/ListElement.java
@@ -16,12 +16,13 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
-import io.netty5.buffer.Buffer;
-
 class ListElement extends AbstractElement<List<Object>> {
 
     private Element<?> first;
@@ -99,54 +100,54 @@ class ListElement extends AbstractElement<List<Object>> {
     }
 
     @Override
-    public int encode(Buffer buffer) {
-        int encodedSize = size();
+    public int encode(DataOutput output) {
+        try {
+            int encodedSize = size();
 
-        int count = 0;
-        int size = 0;
-        Element<?> elt = first;
-        while (elt != null) {
-            count++;
-            size += elt.size();
-            elt = elt.next();
-        }
+            int count = 0;
+            int size = 0;
+            Element<?> elt = first;
+            while (elt != null) {
+                count++;
+                size += elt.size();
+                elt = elt.next();
+            }
 
-        if (encodedSize > buffer.implicitCapacityLimit() - buffer.capacity()) {
-            return 0;
-        } else {
             if (isElementOfArray()) {
                 switch (((ArrayElement) parent()).constructorType()) {
                     case TINY:
                         break;
                     case SMALL:
-                        buffer.writeByte((byte) (size + 1));
-                        buffer.writeByte((byte) count);
+                        output.writeByte((byte) (size + 1));
+                        output.writeByte((byte) count);
                         break;
                     case LARGE:
-                        buffer.writeInt((size + 4));
-                        buffer.writeInt(count);
+                        output.writeInt((size + 4));
+                        output.writeInt(count);
                 }
             } else {
                 if (count == 0) {
-                    buffer.writeByte((byte) 0x45);
+                    output.writeByte((byte) 0x45);
                 } else if (size <= 254 && count <= 255) {
-                    buffer.writeByte((byte) 0xc0);
-                    buffer.writeByte((byte) (size + 1));
-                    buffer.writeByte((byte) count);
+                    output.writeByte((byte) 0xc0);
+                    output.writeByte((byte) (size + 1));
+                    output.writeByte((byte) count);
                 } else {
-                    buffer.writeByte((byte) 0xd0);
-                    buffer.writeInt((size + 4));
-                    buffer.writeInt(count);
+                    output.writeByte((byte) 0xd0);
+                    output.writeInt((size + 4));
+                    output.writeInt(count);
                 }
             }
 
             elt = first;
             while (elt != null) {
-                elt.encode(buffer);
+                elt.encode(output);
                 elt = elt.next();
             }
 
             return encodedSize;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
         }
     }
 
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/LongElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/LongElement.java
index 6349c820..85bbdf0f 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/LongElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/LongElement.java
@@ -16,7 +16,9 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
-import io.netty5.buffer.Buffer;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 
 class LongElement extends AtomicElement<Long> {
 
@@ -59,23 +61,25 @@ class LongElement extends AtomicElement<Long> {
     }
 
     @Override
-    public int encode(Buffer buffer) {
-        int size = size();
-        if (size > buffer.implicitCapacityLimit() - buffer.capacity()) {
-            return 0;
-        }
-        switch (size) {
-            case 2:
-                buffer.writeByte((byte) 0x55);
-            case 1:
-                buffer.writeByte((byte) value);
-                break;
-            case 9:
-                buffer.writeByte((byte) 0x81);
-            case 8:
-                buffer.writeLong(value);
+    public int encode(DataOutput output) {
+        try {
+            int size = size();
+
+            switch (size) {
+                case 2:
+                    output.writeByte((byte) 0x55);
+                case 1:
+                    output.writeByte((byte) value);
+                    break;
+                case 9:
+                    output.writeByte((byte) 0x81);
+                case 8:
+                    output.writeLong(value);
+            }
 
+            return size;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
         }
-        return size;
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/MapElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/MapElement.java
index e3f4fbe8..912145f5 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/MapElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/MapElement.java
@@ -16,12 +16,13 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
-import io.netty5.buffer.Buffer;
-
 class MapElement extends AbstractElement<Map<Object, Object>> {
 
     private Element<?> first;
@@ -100,30 +101,28 @@ class MapElement extends AbstractElement<Map<Object, Object>> {
     }
 
     @Override
-    public int encode(Buffer buffer) {
-        int encodedSize = size();
+    public int encode(DataOutput output) {
+        try {
+            int encodedSize = size();
 
-        int count = 0;
-        int size = 0;
-        Element<?> elt = first;
-        while (elt != null) {
-            count++;
-            size += elt.size();
-            elt = elt.next();
-        }
+            int count = 0;
+            int size = 0;
+            Element<?> elt = first;
+            while (elt != null) {
+                count++;
+                size += elt.size();
+                elt = elt.next();
+            }
 
-        if (encodedSize > buffer.implicitCapacityLimit() - buffer.capacity()) {
-            return 0;
-        } else {
             if (isElementOfArray()) {
                 switch (((ArrayElement) parent()).constructorType()) {
                     case SMALL:
-                        buffer.writeByte((byte) (size + 1));
-                        buffer.writeByte((byte) count);
+                        output.writeByte((byte) (size + 1));
+                        output.writeByte((byte) count);
                         break;
                     case LARGE:
-                        buffer.writeInt((size + 4));
-                        buffer.writeInt(count);
+                        output.writeInt((size + 4));
+                        output.writeInt(count);
                     case TINY:
                         break;
                     default:
@@ -131,23 +130,25 @@ class MapElement extends AbstractElement<Map<Object, Object>> {
                 }
             } else {
                 if (size <= 254 && count <= 255) {
-                    buffer.writeByte((byte) 0xc1);
-                    buffer.writeByte((byte) (size + 1));
-                    buffer.writeByte((byte) count);
+                    output.writeByte((byte) 0xc1);
+                    output.writeByte((byte) (size + 1));
+                    output.writeByte((byte) count);
                 } else {
-                    buffer.writeByte((byte) 0xd1);
-                    buffer.writeInt((size + 4));
-                    buffer.writeInt(count);
+                    output.writeByte((byte) 0xd1);
+                    output.writeInt((size + 4));
+                    output.writeInt(count);
                 }
             }
 
             elt = first;
             while (elt != null) {
-                elt.encode(buffer);
+                elt.encode(output);
                 elt = elt.next();
             }
 
             return encodedSize;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
         }
     }
 
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/NullElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/NullElement.java
index 675c9da4..e6ce5c48 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/NullElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/NullElement.java
@@ -16,7 +16,9 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
-import io.netty5.buffer.Buffer;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 
 class NullElement extends AtomicElement<Void> {
 
@@ -40,11 +42,12 @@ class NullElement extends AtomicElement<Void> {
     }
 
     @Override
-    public int encode(Buffer buffer) {
-        if (buffer.writableBytes() > 0 && !isElementOfArray()) {
-            buffer.writeByte((byte) 0x40);
+    public int encode(DataOutput output) {
+        try {
+            output.writeByte((byte) 0x40);
             return 1;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
         }
-        return 0;
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/ShortElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/ShortElement.java
index 58680420..2946f087 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/ShortElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/ShortElement.java
@@ -16,7 +16,9 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
-import io.netty5.buffer.Buffer;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 
 class ShortElement extends AtomicElement<Short> {
 
@@ -43,19 +45,18 @@ class ShortElement extends AtomicElement<Short> {
     }
 
     @Override
-    public int encode(Buffer buffer) {
-        if (isElementOfArray()) {
-            if (buffer.implicitCapacityLimit() - buffer.capacity() >= 2) {
-                buffer.writeShort(value);
+    public int encode(DataOutput output) {
+        try {
+            if (isElementOfArray()) {
+                output.writeShort(value);
                 return 2;
-            }
-        } else {
-            if (buffer.implicitCapacityLimit() - buffer.capacity() >= 3) {
-                buffer.writeByte((byte) 0x61);
-                buffer.writeShort(value);
+            } else {
+                output.writeByte((byte) 0x61);
+                output.writeShort(value);
                 return 3;
             }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
         }
-        return 0;
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/StringElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/StringElement.java
index 895b279e..198ba443 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/StringElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/StringElement.java
@@ -16,10 +16,11 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 import java.nio.charset.Charset;
 
-import io.netty5.buffer.Buffer;
-
 class StringElement extends AtomicElement<String> {
 
     private static final Charset UTF_8 = Charset.forName("UTF-8");
@@ -71,30 +72,32 @@ class StringElement extends AtomicElement<String> {
     }
 
     @Override
-    public int encode(Buffer buffer) {
-        final byte[] bytes = value.getBytes(UTF_8);
-        final int length = bytes.length;
+    public int encode(DataOutput output) {
+        try {
+            final byte[] bytes = value.getBytes(UTF_8);
+            final int length = bytes.length;
 
-        int size = size(length);
-        if (buffer.implicitCapacityLimit() - buffer.capacity() < size) {
-            return 0;
-        }
-        if (isElementOfArray()) {
-            final ArrayElement parent = (ArrayElement) parent();
+            int size = size(length);
 
-            if (parent.constructorType() == ArrayElement.SMALL) {
-                buffer.writeByte((byte) length);
+            if (isElementOfArray()) {
+                final ArrayElement parent = (ArrayElement) parent();
+
+                if (parent.constructorType() == ArrayElement.SMALL) {
+                    output.writeByte((byte) length);
+                } else {
+                    output.writeInt(length);
+                }
+            } else if (length <= 255) {
+                output.writeByte((byte) 0xa1);
+                output.writeByte((byte) length);
             } else {
-                buffer.writeInt(length);
+                output.writeByte((byte) 0xb1);
+                output.writeInt(length);
             }
-        } else if (length <= 255) {
-            buffer.writeByte((byte) 0xa1);
-            buffer.writeByte((byte) length);
-        } else {
-            buffer.writeByte((byte) 0xb1);
-            buffer.writeInt(length);
+            output.write(bytes);
+            return size;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
         }
-        buffer.writeBytes(bytes);
-        return size;
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/SymbolElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/SymbolElement.java
index 7fb28766..8f24630a 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/SymbolElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/SymbolElement.java
@@ -16,12 +16,13 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 import java.nio.charset.Charset;
 
 import org.apache.qpid.protonj2.test.driver.codec.primitives.Symbol;
 
-import io.netty5.buffer.Buffer;
-
 class SymbolElement extends AtomicElement<Symbol> {
 
     private static final Charset ASCII = Charset.forName("US-ASCII");
@@ -69,27 +70,29 @@ class SymbolElement extends AtomicElement<Symbol> {
     }
 
     @Override
-    public int encode(Buffer buffer) {
-        int size = size();
-        if (buffer.implicitCapacityLimit() - buffer.capacity() < size) {
-            return 0;
-        }
-        if (isElementOfArray()) {
-            final ArrayElement parent = (ArrayElement) parent();
+    public int encode(DataOutput output) {
+        try {
+            int size = size();
 
-            if (parent.constructorType() == ArrayElement.SMALL) {
-                buffer.writeByte((byte) value.getLength());
+            if (isElementOfArray()) {
+                final ArrayElement parent = (ArrayElement) parent();
+
+                if (parent.constructorType() == ArrayElement.SMALL) {
+                    output.writeByte((byte) value.getLength());
+                } else {
+                    output.writeInt(value.getLength());
+                }
+            } else if (value.getLength() <= 255) {
+                output.writeByte((byte) 0xa3);
+                output.writeByte((byte) value.getLength());
             } else {
-                buffer.writeInt(value.getLength());
+                output.writeByte((byte) 0xb3);
+                output.writeByte((byte) value.getLength());
             }
-        } else if (value.getLength() <= 255) {
-            buffer.writeByte((byte) 0xa3);
-            buffer.writeByte((byte) value.getLength());
-        } else {
-            buffer.writeByte((byte) 0xb3);
-            buffer.writeByte((byte) value.getLength());
+            output.write(value.toString().getBytes(ASCII));
+            return size;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
         }
-        buffer.writeBytes(value.toString().getBytes(ASCII));
-        return size;
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/TimestampElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/TimestampElement.java
index abdd51c7..05319d79 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/TimestampElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/TimestampElement.java
@@ -16,10 +16,11 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 import java.util.Date;
 
-import io.netty5.buffer.Buffer;
-
 class TimestampElement extends AtomicElement<Date> {
 
     private final Date value;
@@ -45,16 +46,19 @@ class TimestampElement extends AtomicElement<Date> {
     }
 
     @Override
-    public int encode(Buffer buffer) {
-        int size = size();
-        if (size > buffer.implicitCapacityLimit() - buffer.capacity()) {
-            return 0;
-        }
-        if (size == 9) {
-            buffer.writeByte((byte) 0x83);
-        }
-        buffer.writeLong(value.getTime());
+    public int encode(DataOutput output) {
+        try {
+            final int size = size();
 
-        return size;
+            if (size == 9) {
+                output.writeByte((byte) 0x83);
+            }
+
+            output.writeLong(value.getTime());
+
+            return size;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/TypeDecoder.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/TypeDecoder.java
index e532ddc7..8d11011a 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/TypeDecoder.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/TypeDecoder.java
@@ -16,6 +16,7 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
+import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
 import java.util.Date;
 import java.util.UUID;
@@ -29,8 +30,6 @@ import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedInteger;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedLong;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedShort;
 
-import io.netty5.buffer.Buffer;
-
 class TypeDecoder {
 
     private static final Charset ASCII = Charset.forName("US-ASCII");
@@ -96,30 +95,23 @@ class TypeDecoder {
 
         Codec.DataType getType();
 
-        int size(Buffer buffer);
+        int size(ByteBuffer input);
 
-        void parse(Buffer buffer, Codec data);
+        void parse(ByteBuffer input, Codec data);
 
     }
 
-    static int decode(Buffer buffer, Codec data) {
-        if (buffer.readableBytes() > 0) {
-            final int position = buffer.readerOffset();
-            final TypeConstructor c = readConstructor(buffer);
-            final int size = c.size(buffer);
-            if (buffer.readableBytes() >= size) {
-                c.parse(buffer, data);
-                return 1 + size;
-            } else {
-                buffer.readerOffset(position);
-                return -4;
-            }
-        }
-        return 0;
+    static int decode(ByteBuffer input, Codec data) {
+        final TypeConstructor c = readConstructor(input);
+        final int size = c.size(input);
+
+        c.parse(input, data);
+
+        return 1 + size;
     }
 
-    private static TypeConstructor readConstructor(Buffer buffer) {
-        final int index = buffer.readByte() & 0xff;
+    private static TypeConstructor readConstructor(ByteBuffer input) {
+        final int index = input.get() & 0xff;
         final TypeConstructor tc = constructors[index];
         if (tc == null) {
             throw new IllegalArgumentException("No constructor for type " + index);
@@ -135,12 +127,12 @@ class TypeDecoder {
         }
 
         @Override
-        public int size(Buffer buffer) {
+        public int size(ByteBuffer input) {
             return 0;
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
+        public void parse(ByteBuffer input, Codec data) {
             data.putNull();
         }
     }
@@ -153,12 +145,12 @@ class TypeDecoder {
         }
 
         @Override
-        public int size(Buffer buffer) {
+        public int size(ByteBuffer input) {
             return 0;
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
+        public void parse(ByteBuffer input, Codec data) {
             data.putBoolean(true);
         }
     }
@@ -171,12 +163,12 @@ class TypeDecoder {
         }
 
         @Override
-        public int size(Buffer buffer) {
+        public int size(ByteBuffer input) {
             return 0;
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
+        public void parse(ByteBuffer input, Codec data) {
             data.putBoolean(false);
         }
     }
@@ -189,12 +181,12 @@ class TypeDecoder {
         }
 
         @Override
-        public int size(Buffer buffer) {
+        public int size(ByteBuffer input) {
             return 0;
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
+        public void parse(ByteBuffer input, Codec data) {
             data.putUnsignedInteger(UnsignedInteger.ZERO);
         }
     }
@@ -207,12 +199,12 @@ class TypeDecoder {
         }
 
         @Override
-        public int size(Buffer buffer) {
+        public int size(ByteBuffer input) {
             return 0;
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
+        public void parse(ByteBuffer input, Codec data) {
             data.putUnsignedLong(UnsignedLong.ZERO);
         }
     }
@@ -225,12 +217,12 @@ class TypeDecoder {
         }
 
         @Override
-        public int size(Buffer buffer) {
+        public int size(ByteBuffer input) {
             return 0;
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
+        public void parse(ByteBuffer input, Codec data) {
             data.putList();
         }
     }
@@ -239,7 +231,7 @@ class TypeDecoder {
     private static abstract class Fixed0SizeConstructor implements TypeConstructor {
 
         @Override
-        public final int size(Buffer buffer) {
+        public final int size(ByteBuffer input) {
             return 0;
         }
     }
@@ -247,7 +239,7 @@ class TypeDecoder {
     private static abstract class Fixed1SizeConstructor implements TypeConstructor {
 
         @Override
-        public int size(Buffer buffer) {
+        public int size(ByteBuffer input) {
             return 1;
         }
     }
@@ -255,7 +247,7 @@ class TypeDecoder {
     private static abstract class Fixed2SizeConstructor implements TypeConstructor {
 
         @Override
-        public int size(Buffer buffer) {
+        public int size(ByteBuffer input) {
             return 2;
         }
     }
@@ -263,7 +255,7 @@ class TypeDecoder {
     private static abstract class Fixed4SizeConstructor implements TypeConstructor {
 
         @Override
-        public int size(Buffer buffer) {
+        public int size(ByteBuffer input) {
             return 4;
         }
     }
@@ -271,7 +263,7 @@ class TypeDecoder {
     private static abstract class Fixed8SizeConstructor implements TypeConstructor {
 
         @Override
-        public int size(Buffer buffer) {
+        public int size(ByteBuffer input) {
             return 8;
         }
     }
@@ -279,7 +271,7 @@ class TypeDecoder {
     private static abstract class Fixed16SizeConstructor implements TypeConstructor {
 
         @Override
-        public int size(Buffer buffer) {
+        public int size(ByteBuffer input) {
             return 16;
         }
     }
@@ -292,8 +284,8 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            data.putUnsignedByte(UnsignedByte.valueOf(buffer.readByte()));
+        public void parse(ByteBuffer input, Codec data) {
+            data.putUnsignedByte(UnsignedByte.valueOf(input.get()));
         }
     }
 
@@ -305,8 +297,8 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            data.putByte(buffer.readByte());
+        public void parse(ByteBuffer input, Codec data) {
+            data.putByte(input.get());
         }
     }
 
@@ -317,9 +309,10 @@ class TypeDecoder {
             return Codec.DataType.UINT;
         }
 
+
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            data.putUnsignedInteger(UnsignedInteger.valueOf((buffer.readByte()) & 0xff));
+        public void parse(ByteBuffer input, Codec data) {
+            data.putUnsignedInteger(UnsignedInteger.valueOf((input.get()) & 0xff));
         }
     }
 
@@ -331,8 +324,8 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            data.putInt(buffer.readByte());
+        public void parse(ByteBuffer input, Codec data) {
+            data.putInt(input.get());
         }
     }
 
@@ -343,9 +336,10 @@ class TypeDecoder {
             return Codec.DataType.ULONG;
         }
 
+
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            data.putUnsignedLong(UnsignedLong.valueOf((buffer.readByte()) & 0xff));
+        public void parse(ByteBuffer input, Codec data) {
+            data.putUnsignedLong(UnsignedLong.valueOf((input.get()) & 0xff));
         }
     }
 
@@ -357,8 +351,8 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            data.putLong(buffer.readByte());
+        public void parse(ByteBuffer input, Codec data) {
+            data.putLong(input.get());
         }
     }
 
@@ -370,8 +364,8 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            final int i = buffer.readByte();
+        public void parse(ByteBuffer input, Codec data) {
+            final int i = input.get();
             if (i != 0 && i != 1) {
                 throw new IllegalArgumentException("Illegal value " + i + " for boolean");
             }
@@ -387,8 +381,8 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            data.putUnsignedShort(UnsignedShort.valueOf(buffer.readShort()));
+        public void parse(ByteBuffer input, Codec data) {
+            data.putUnsignedShort(UnsignedShort.valueOf(input.getShort()));
         }
     }
 
@@ -400,8 +394,8 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            data.putShort(buffer.readShort());
+        public void parse(ByteBuffer input, Codec data) {
+            data.putShort(input.getShort());
         }
     }
 
@@ -413,8 +407,8 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            data.putUnsignedInteger(UnsignedInteger.valueOf(buffer.readInt()));
+        public void parse(ByteBuffer input, Codec data) {
+            data.putUnsignedInteger(UnsignedInteger.valueOf(input.getInt()));
         }
     }
 
@@ -426,8 +420,8 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            data.putInt(buffer.readInt());
+        public void parse(ByteBuffer input, Codec data) {
+            data.putInt(input.getInt());
         }
     }
 
@@ -439,8 +433,8 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            data.putFloat(buffer.readFloat());
+        public void parse(ByteBuffer input, Codec data) {
+            data.putFloat(input.getFloat());
         }
     }
 
@@ -452,8 +446,8 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            data.putChar(buffer.readInt());
+        public void parse(ByteBuffer input, Codec data) {
+            data.putChar(input.getInt());
         }
     }
 
@@ -465,8 +459,8 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            data.putDecimal32(new Decimal32(buffer.readInt()));
+        public void parse(ByteBuffer input, Codec data) {
+            data.putDecimal32(new Decimal32(input.getInt()));
         }
     }
 
@@ -478,8 +472,8 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            data.putUnsignedLong(UnsignedLong.valueOf(buffer.readLong()));
+        public void parse(ByteBuffer input, Codec data) {
+            data.putUnsignedLong(UnsignedLong.valueOf(input.getLong()));
         }
     }
 
@@ -491,8 +485,8 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            data.putLong(buffer.readLong());
+        public void parse(ByteBuffer input, Codec data) {
+            data.putLong(input.getLong());
         }
     }
 
@@ -504,8 +498,8 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            data.putDouble(buffer.readDouble());
+        public void parse(ByteBuffer input, Codec data) {
+            data.putDouble(input.getDouble());
         }
     }
 
@@ -517,8 +511,8 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            data.putTimestamp(new Date(buffer.readLong()));
+        public void parse(ByteBuffer input, Codec data) {
+            data.putTimestamp(new Date(input.getLong()));
         }
     }
 
@@ -530,8 +524,8 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            data.putDecimal64(new Decimal64(buffer.readLong()));
+        public void parse(ByteBuffer input, Codec data) {
+            data.putDecimal64(new Decimal64(input.getLong()));
         }
     }
 
@@ -543,8 +537,8 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            data.putDecimal128(new Decimal128(buffer.readLong(), buffer.readLong()));
+        public void parse(ByteBuffer input, Codec data) {
+            data.putDecimal128(new Decimal128(input.getLong(), input.getLong()));
         }
     }
 
@@ -556,43 +550,39 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            data.putUUID(new UUID(buffer.readLong(), buffer.readLong()));
+        public void parse(ByteBuffer input, Codec data) {
+            data.putUUID(new UUID(input.getLong(), input.getLong()));
         }
     }
 
     private static abstract class SmallVariableConstructor implements TypeConstructor {
 
         @Override
-        public int size(Buffer buffer) {
-            final int position = buffer.readerOffset();
-            if (buffer.readableBytes() > 0) {
-                final int size = buffer.readByte() & 0xff;
-                buffer.readerOffset(position);
-
+        public int size(ByteBuffer input) {
+            final int position = input.position();
+            if (input.remaining() > 0) {
+                final int size = input.get() & 0xff;
+                input.position(position);
                 return size + 1;
             } else {
                 return 1;
             }
         }
-
     }
 
     private static abstract class VariableConstructor implements TypeConstructor {
 
         @Override
-        public int size(Buffer buffer) {
-            final int position = buffer.readerOffset();
-            if (buffer.readableBytes() >= 4) {
-                final int size = buffer.readInt();
-                buffer.readerOffset(position);
-
+        public int size(ByteBuffer input) {
+            final int position = input.position();
+            if (input.remaining() >= 4) {
+                final int size = input.getInt();
+                input.position(position);
                 return size + 4;
             } else {
                 return 4;
             }
         }
-
     }
 
     private static class SmallBinaryConstructor extends SmallVariableConstructor {
@@ -603,10 +593,10 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            final int size = buffer.readByte() & 0xff;
+        public void parse(ByteBuffer input, Codec data) {
+            final int size = input.get() & 0xff;
             final byte[] bytes = new byte[size];
-            buffer.readBytes(bytes, 0, bytes.length);
+            input.get(bytes, 0, bytes.length);
             data.putBinary(bytes);
         }
     }
@@ -619,10 +609,10 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            final int size = buffer.readByte() & 0xff;
+        public void parse(ByteBuffer input, Codec data) {
+            final int size = input.get() & 0xff;
             final byte[] bytes = new byte[size];
-            buffer.readBytes(bytes, 0, bytes.length);
+            input.get(bytes, 0, bytes.length);
             data.putSymbol(Symbol.valueOf(new String(bytes, ASCII)));
         }
     }
@@ -635,10 +625,10 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            final int size = buffer.readByte() & 0xff;
+        public void parse(ByteBuffer input, Codec data) {
+            final int size = input.get() & 0xff;
             final byte[] bytes = new byte[size];
-            buffer.readBytes(bytes, 0, bytes.length);
+            input.get(bytes, 0, bytes.length);
             data.putString(new String(bytes, UTF_8));
         }
     }
@@ -651,10 +641,10 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            final int size = buffer.readInt();
+        public void parse(ByteBuffer input, Codec data) {
+            final int size = input.getInt();
             final byte[] bytes = new byte[size];
-            buffer.readBytes(bytes, 0, bytes.length);
+            input.get(bytes, 0, bytes.length);
             data.putBinary(bytes);
         }
     }
@@ -667,10 +657,10 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            final int size = buffer.readInt();
+        public void parse(ByteBuffer input, Codec data) {
+            final int size = input.getInt();
             final byte[] bytes = new byte[size];
-            buffer.readBytes(bytes, 0, bytes.length);
+            input.get(bytes, 0, bytes.length);
             data.putSymbol(Symbol.valueOf(new String(bytes, ASCII)));
         }
     }
@@ -683,10 +673,10 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            final int size = buffer.readInt();
+        public void parse(ByteBuffer input, Codec data) {
+            final int size = input.getInt();
             final byte[] bytes = new byte[size];
-            buffer.readBytes(bytes, 0, bytes.length);
+            input.get(bytes, 0, bytes.length);
             data.putString(new String(bytes, UTF_8));
         }
     }
@@ -699,14 +689,17 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            int size = buffer.readByte() & 0xff;
-            try (Buffer buf = buffer.copy(buffer.readerOffset(), size, true)) {
-                buffer.skipReadableBytes(size);
-                final int count = buf.readByte() & 0xff;
-                data.putList();
-                parseChildren(data, buf, count);
-            }
+        public void parse(ByteBuffer input, Codec data) {
+            final int size = input.get() & 0xff;
+            final ByteBuffer duplicate = input.slice().asReadOnlyBuffer();
+            final int count = duplicate.get() & 0xff;
+
+            // Skip bytes in the source buffer
+            input.position(input.position() + size);
+
+            // Now parse the actual type encoding using the duplicate
+            data.putList();
+            parseChildren(data, duplicate, count);
         }
     }
 
@@ -718,14 +711,17 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            int size = buffer.readByte() & 0xff;
-            try (Buffer buf = buffer.copy(buffer.readerOffset(), size, true)) {
-                buffer.skipReadableBytes(size);
-                final int count = buf.readByte() & 0xff;
-                data.putMap();
-                parseChildren(data, buf, count);
-            }
+        public void parse(ByteBuffer input, Codec data) {
+            final int size = input.get() & 0xff;
+            final ByteBuffer duplicate = input.slice().asReadOnlyBuffer();
+            final int count = duplicate.get() & 0xff;
+
+            // Skip bytes in the source buffer
+            input.position(input.position() + size);
+
+            // Now parse the actual type encoding using the duplicate
+            data.putMap();
+            parseChildren(data, duplicate, count);
         }
     }
 
@@ -737,14 +733,17 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            int size = buffer.readInt();
-            try (Buffer buf = buffer.copy(buffer.readerOffset(), size, true)) {
-                buffer.skipReadableBytes(size);
-                final int count = buf.readInt();
-                data.putList();
-                parseChildren(data, buf, count);
-            }
+        public void parse(ByteBuffer input, Codec data) {
+            final int size = input.getInt();
+            final ByteBuffer duplicate = input.slice().asReadOnlyBuffer();
+            final int count = duplicate.getInt();
+
+            // Skip bytes in the source buffer
+            input.position(input.position() + size);
+
+            // Now parse the actual type encoding using the duplicate
+            data.putList();
+            parseChildren(data, duplicate, count);
         }
     }
 
@@ -756,25 +755,28 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            int size = buffer.readInt();
-            try (Buffer buf = buffer.copy(buffer.readerOffset(), size, true)) {
-                buffer.skipReadableBytes(size);
-                final int count = buf.readInt();
-                data.putMap();
-                parseChildren(data, buf, count);
-            }
+        public void parse(ByteBuffer input, Codec data) {
+            final int size = input.getInt();
+            final ByteBuffer duplicate = input.slice().asReadOnlyBuffer();
+            final int count = duplicate.getInt();
+
+            // Skip bytes in the source buffer
+            input.position(input.position() + size);
+
+            // Now parse the actual type encoding using the duplicate
+            data.putMap();
+            parseChildren(data, duplicate, count);
         }
     }
 
-    private static void parseChildren(Codec data, Buffer buf, int count) {
+    private static void parseChildren(Codec data, ByteBuffer input, int count) {
         data.enter();
         for (int i = 0; i < count; i++) {
-            final TypeConstructor c = readConstructor(buf);
-            final int size = c.size(buf);
-            final int getReadableBytes = buf.readableBytes();
+            final TypeConstructor c = readConstructor(input);
+            final int size = c.size(input);
+            final int getReadableBytes = input.remaining();
             if (size <= getReadableBytes) {
-                c.parse(buf, data);
+                c.parse(input, data);
             } else {
                 throw new IllegalArgumentException("Malformed data");
             }
@@ -791,32 +793,24 @@ class TypeDecoder {
         }
 
         @Override
-        public int size(Buffer buffer) {
-            try (Buffer buf = buffer.copy(true)) {
-                if (buf.readableBytes() > 0) {
-                    TypeConstructor c = readConstructor(buf);
-                    final int size = c.size(buf);
-                    if (buf.readableBytes() > size) {
-                        buf.readerOffset(size + 1);
-                        c = readConstructor(buf);
-                        return size + 2 + c.size(buf);
-                    } else {
-                        return size + 2;
-                    }
-                } else {
-                    return 1;
-                }
-            }
+        public int size(ByteBuffer input) {
+            final ByteBuffer buf = input.slice().asReadOnlyBuffer();
+            TypeConstructor c = readConstructor(buf);
+            final int size = c.size(buf);
+
+            buf.position(size + 1);
+            c = readConstructor(buf);
+            return size + 2 + c.size(buf);
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
+        public void parse(ByteBuffer input, Codec data) {
             data.putDescribed();
             data.enter();
-            TypeConstructor c = readConstructor(buffer);
-            c.parse(buffer, data);
-            c = readConstructor(buffer);
-            c.parse(buffer, data);
+            TypeConstructor c = readConstructor(input);
+            c.parse(input, data);
+            c = readConstructor(input);
+            c.parse(input, data);
             data.exit();
         }
     }
@@ -829,13 +823,13 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            final int size = buffer.readByte() & 0xff;
-            try (Buffer buf = buffer.copy(buffer.readerOffset(), size, true)) {
-                buffer.skipReadableBytes(size);
-                final int count = buf.readByte() & 0xff;
-                parseArray(data, buf, count);
-            }
+        public void parse(ByteBuffer input, Codec data) {
+            final int size = input.get() & 0xff;
+            final ByteBuffer buf = input.slice().asReadOnlyBuffer();
+
+            input.position(input.position() + size);
+            final int count = buf.get() & 0xff;
+            parseArray(data, buf, count);
         }
     }
 
@@ -847,42 +841,45 @@ class TypeDecoder {
         }
 
         @Override
-        public void parse(Buffer buffer, Codec data) {
-            final int size = buffer.readInt();
-            try (Buffer buf = buffer.copy(buffer.readerOffset(), size, true)) {
-                buffer.skipReadableBytes(size);
-                final int count = buf.readInt();
-                parseArray(data, buf, count);
-            }
+        public void parse(ByteBuffer input, Codec data) {
+            final int size = input.getInt();
+            final ByteBuffer buf = input.slice().asReadOnlyBuffer();
+
+            input.position(input.position() + size);
+            final int count = buf.getInt();
+            parseArray(data, buf, count);
         }
     }
 
-    private static void parseArray(Codec data, Buffer buffer, int count) {
-        byte type = buffer.readByte();
-        boolean isDescribed = type == (byte) 0x00;
-        final int descriptorPosition = buffer.readerOffset();
+    private static void parseArray(Codec data, ByteBuffer input, int count) {
+        byte type = input.get();
+
+        final boolean isDescribed = type == (byte) 0x00;
+        final int descriptorPosition = input.position();
+
         if (isDescribed) {
-            final TypeConstructor descriptorTc = readConstructor(buffer);
-            buffer.skipReadableBytes(descriptorTc.size(buffer));
-            type = buffer.readByte();
+            final TypeConstructor descriptorTc = readConstructor(input);
+            input.position(input.position() + descriptorTc.size(input));
+            type = input.get();
             if (type == (byte) 0x00) {
                 throw new IllegalArgumentException("Malformed array data");
             }
-
         }
-        TypeConstructor tc = constructors[type & 0xff];
+
+        final TypeConstructor tc = constructors[type & 0xff];
 
         data.putArray(isDescribed, tc.getType());
         data.enter();
+
         if (isDescribed) {
-            final int position = buffer.readerOffset();
-            buffer.readerOffset(descriptorPosition);
-            final TypeConstructor descriptorTc = readConstructor(buffer);
-            descriptorTc.parse(buffer, data);
-            buffer.readerOffset(position);
+            final int position = input.position();
+            input.position(descriptorPosition);
+            final TypeConstructor descriptorTc = readConstructor(input);
+            descriptorTc.parse(input, data);
+            input.position(position);
         }
         for (int i = 0; i < count; i++) {
-            tc.parse(buffer, data);
+            tc.parse(input, data);
         }
 
         data.exit();
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/UUIDElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/UUIDElement.java
index 5c86ceac..802e613d 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/UUIDElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/UUIDElement.java
@@ -16,10 +16,11 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 import java.util.UUID;
 
-import io.netty5.buffer.Buffer;
-
 class UUIDElement extends AtomicElement<UUID> {
 
     private final UUID value;
@@ -45,17 +46,18 @@ class UUIDElement extends AtomicElement<UUID> {
     }
 
     @Override
-    public int encode(Buffer buffer) {
-        int size = size();
-        if (buffer.implicitCapacityLimit() >= size) {
+    public int encode(DataOutput output) {
+        final int size = size();
+
+        try {
             if (size == 17) {
-                buffer.writeByte((byte) 0x98);
+                output.writeByte((byte) 0x98);
             }
-            buffer.writeLong(value.getMostSignificantBits());
-            buffer.writeLong(value.getLeastSignificantBits());
+            output.writeLong(value.getMostSignificantBits());
+            output.writeLong(value.getLeastSignificantBits());
             return size;
-        } else {
-            return 0;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
         }
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/UnsignedByteElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/UnsignedByteElement.java
index cad775f3..83945dc1 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/UnsignedByteElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/UnsignedByteElement.java
@@ -16,9 +16,11 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
-import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedByte;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 
-import io.netty5.buffer.Buffer;
+import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedByte;
 
 class UnsignedByteElement extends AtomicElement<UnsignedByte> {
 
@@ -45,19 +47,18 @@ class UnsignedByteElement extends AtomicElement<UnsignedByte> {
     }
 
     @Override
-    public int encode(Buffer buffer) {
-        if (isElementOfArray()) {
-            if (buffer.writableBytes() > 0) {
-                buffer.writeByte(value.byteValue());
+    public int encode(DataOutput output) {
+        try {
+            if (isElementOfArray()) {
+                output.writeByte(value.byteValue());
                 return 1;
-            }
-        } else {
-            if (buffer.implicitCapacityLimit() - buffer.capacity() >= 2) {
-                buffer.writeByte((byte) 0x50);
-                buffer.writeByte(value.byteValue());
+            } else {
+                output.writeByte((byte) 0x50);
+                output.writeByte(value.byteValue());
                 return 2;
             }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
         }
-        return 0;
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/UnsignedIntegerElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/UnsignedIntegerElement.java
index ff7750c2..b77a8ec6 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/UnsignedIntegerElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/UnsignedIntegerElement.java
@@ -16,9 +16,11 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
-import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedInteger;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 
-import io.netty5.buffer.Buffer;
+import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedInteger;
 
 class UnsignedIntegerElement extends AtomicElement<UnsignedInteger> {
 
@@ -68,30 +70,31 @@ class UnsignedIntegerElement extends AtomicElement<UnsignedInteger> {
     }
 
     @Override
-    public int encode(Buffer buffer) {
-        int size = size();
-        if (size > buffer.implicitCapacityLimit() - buffer.capacity()) {
-            return 0;
-        }
-        switch (size) {
-            case 1:
-                if (isElementOfArray()) {
-                    buffer.writeByte((byte) value.intValue());
-                } else {
-                    buffer.writeByte((byte) 0x43);
-                }
-                break;
-            case 2:
-                buffer.writeByte((byte) 0x52);
-                buffer.writeByte((byte) value.intValue());
-                break;
-            case 5:
-                buffer.writeByte((byte) 0x70);
-            case 4:
-                buffer.writeInt(value.intValue());
+    public int encode(DataOutput output) {
+        try {
+            int size = size();
 
-        }
+            switch (size) {
+                case 1:
+                    if (isElementOfArray()) {
+                        output.writeByte((byte) value.intValue());
+                    } else {
+                        output.writeByte((byte) 0x43);
+                    }
+                    break;
+                case 2:
+                    output.writeByte((byte) 0x52);
+                    output.writeByte((byte) value.intValue());
+                    break;
+                case 5:
+                    output.writeByte((byte) 0x70);
+                case 4:
+                    output.writeInt(value.intValue());
+            }
 
-        return size;
+            return size;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/UnsignedLongElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/UnsignedLongElement.java
index 89a31790..bc01af78 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/UnsignedLongElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/UnsignedLongElement.java
@@ -16,9 +16,11 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
-import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedLong;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 
-import io.netty5.buffer.Buffer;
+import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedLong;
 
 class UnsignedLongElement extends AtomicElement<UnsignedLong> {
 
@@ -68,30 +70,31 @@ class UnsignedLongElement extends AtomicElement<UnsignedLong> {
     }
 
     @Override
-    public int encode(Buffer buffer) {
-        int size = size();
-        if (size > buffer.implicitCapacityLimit() - buffer.capacity()) {
-            return 0;
-        }
-        switch (size) {
-            case 1:
-                if (isElementOfArray()) {
-                    buffer.writeByte((byte) value.longValue());
-                } else {
-                    buffer.writeByte((byte) 0x44);
-                }
-                break;
-            case 2:
-                buffer.writeByte((byte) 0x53);
-                buffer.writeByte((byte) value.longValue());
-                break;
-            case 9:
-                buffer.writeByte((byte) 0x80);
-            case 8:
-                buffer.writeLong(value.longValue());
+    public int encode(DataOutput output) {
+        try {
+            int size = size();
 
-        }
+            switch (size) {
+                case 1:
+                    if (isElementOfArray()) {
+                        output.writeByte((byte) value.longValue());
+                    } else {
+                        output.writeByte((byte) 0x44);
+                    }
+                    break;
+                case 2:
+                    output.writeByte((byte) 0x53);
+                    output.writeByte((byte) value.longValue());
+                    break;
+                case 9:
+                    output.writeByte((byte) 0x80);
+                case 8:
+                    output.writeLong(value.longValue());
+            }
 
-        return size;
+            return size;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/UnsignedShortElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/UnsignedShortElement.java
index 8fd67eca..7bcc8772 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/UnsignedShortElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/UnsignedShortElement.java
@@ -16,9 +16,11 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec;
 
-import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedShort;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 
-import io.netty5.buffer.Buffer;
+import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedShort;
 
 class UnsignedShortElement extends AtomicElement<UnsignedShort> {
 
@@ -45,19 +47,18 @@ class UnsignedShortElement extends AtomicElement<UnsignedShort> {
     }
 
     @Override
-    public int encode(Buffer buffer) {
-        if (isElementOfArray()) {
-            if (buffer.implicitCapacityLimit() - buffer.capacity() >= 2) {
-                buffer.writeShort(value.shortValue());
+    public int encode(DataOutput output) {
+        try {
+            if (isElementOfArray()) {
+                output.writeShort(value.shortValue());
                 return 2;
-            }
-        } else {
-            if (buffer.implicitCapacityLimit() - buffer.capacity() >= 3) {
-                buffer.writeByte((byte) 0x60);
-                buffer.writeShort(value.shortValue());
+            } else {
+                output.writeByte((byte) 0x60);
+                output.writeShort(value.shortValue());
                 return 3;
             }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
         }
-        return 0;
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Attach.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Attach.java
index 112ea4c0..02c61a7f 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Attach.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Attach.java
@@ -16,6 +16,7 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec.transport;
 
+import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
@@ -30,8 +31,6 @@ import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedInteger;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedLong;
 import org.apache.qpid.protonj2.test.driver.codec.transactions.Coordinator;
 
-import io.netty5.buffer.Buffer;
-
 public class Attach extends PerformativeDescribedType {
 
     public static final Symbol DESCRIPTOR_SYMBOL = Symbol.valueOf("amqp:attach:list");
@@ -252,7 +251,7 @@ public class Attach extends PerformativeDescribedType {
     }
 
     @Override
-    public <E> void invoke(PerformativeHandler<E> handler, int frameSize, Buffer payload, int channel, E context) {
+    public <E> void invoke(PerformativeHandler<E> handler, int frameSize, ByteBuffer payload, int channel, E context) {
         handler.handleAttach(frameSize, this, payload, channel, context);
     }
 
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Begin.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Begin.java
index 6ed6a274..480f80dc 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Begin.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Begin.java
@@ -16,6 +16,7 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec.transport;
 
+import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
@@ -25,8 +26,6 @@ import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedInteger;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedLong;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedShort;
 
-import io.netty5.buffer.Buffer;
-
 public class Begin extends PerformativeDescribedType {
 
     public static final Symbol DESCRIPTOR_SYMBOL = Symbol.valueOf("amqp:begin:list");
@@ -143,7 +142,7 @@ public class Begin extends PerformativeDescribedType {
     }
 
     @Override
-    public <E> void invoke(PerformativeHandler<E> handler, int frameSize, Buffer payload, int channel, E context) {
+    public <E> void invoke(PerformativeHandler<E> handler, int frameSize, ByteBuffer payload, int channel, E context) {
         handler.handleBegin(frameSize, this, payload, channel, context);
     }
 
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Close.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Close.java
index a0085163..cf104c46 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Close.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Close.java
@@ -16,13 +16,12 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec.transport;
 
+import java.nio.ByteBuffer;
 import java.util.List;
 
 import org.apache.qpid.protonj2.test.driver.codec.primitives.Symbol;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedLong;
 
-import io.netty5.buffer.Buffer;
-
 public class Close extends PerformativeDescribedType {
 
     public static final Symbol DESCRIPTOR_SYMBOL = Symbol.valueOf("amqp:close:list");
@@ -68,7 +67,7 @@ public class Close extends PerformativeDescribedType {
     }
 
     @Override
-    public <E> void invoke(PerformativeHandler<E> handler, int frameSize, Buffer payload, int channel, E context) {
+    public <E> void invoke(PerformativeHandler<E> handler, int frameSize, ByteBuffer payload, int channel, E context) {
         handler.handleClose(frameSize, this, payload, channel, context);
     }
 
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Detach.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Detach.java
index fb88391e..8749d9d4 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Detach.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Detach.java
@@ -16,14 +16,13 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec.transport;
 
+import java.nio.ByteBuffer;
 import java.util.List;
 
 import org.apache.qpid.protonj2.test.driver.codec.primitives.Symbol;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedInteger;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedLong;
 
-import io.netty5.buffer.Buffer;
-
 public class Detach extends PerformativeDescribedType {
 
     public static final Symbol DESCRIPTOR_SYMBOL = Symbol.valueOf("amqp:detach:list");
@@ -89,7 +88,7 @@ public class Detach extends PerformativeDescribedType {
     }
 
     @Override
-    public <E> void invoke(PerformativeHandler<E> handler, int frameSize, Buffer payload, int channel, E context) {
+    public <E> void invoke(PerformativeHandler<E> handler, int frameSize, ByteBuffer payload, int channel, E context) {
         handler.handleDetach(frameSize, this, payload, channel, context);
     }
 
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Disposition.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Disposition.java
index aadfa5be..be91867c 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Disposition.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Disposition.java
@@ -16,6 +16,7 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec.transport;
 
+import java.nio.ByteBuffer;
 import java.util.List;
 
 import org.apache.qpid.protonj2.test.driver.codec.primitives.DescribedType;
@@ -23,8 +24,6 @@ import org.apache.qpid.protonj2.test.driver.codec.primitives.Symbol;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedInteger;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedLong;
 
-import io.netty5.buffer.Buffer;
-
 public class Disposition extends PerformativeDescribedType {
 
     public static final Symbol DESCRIPTOR_SYMBOL = Symbol.valueOf("amqp:disposition:list");
@@ -120,7 +119,7 @@ public class Disposition extends PerformativeDescribedType {
     }
 
     @Override
-    public <E> void invoke(PerformativeHandler<E> handler, int frameSize, Buffer payload, int channel, E context) {
+    public <E> void invoke(PerformativeHandler<E> handler, int frameSize, ByteBuffer payload, int channel, E context) {
         handler.handleDisposition(frameSize, this, payload, channel, context);
     }
 
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/End.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/End.java
index 82712f56..a5c35c1d 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/End.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/End.java
@@ -16,13 +16,12 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec.transport;
 
+import java.nio.ByteBuffer;
 import java.util.List;
 
 import org.apache.qpid.protonj2.test.driver.codec.primitives.Symbol;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedLong;
 
-import io.netty5.buffer.Buffer;
-
 public class End extends PerformativeDescribedType {
 
     public static final Symbol DESCRIPTOR_SYMBOL = Symbol.valueOf("amqp:end:list");
@@ -68,7 +67,7 @@ public class End extends PerformativeDescribedType {
     }
 
     @Override
-    public <E> void invoke(PerformativeHandler<E> handler, int frameSize, Buffer payload, int channel, E context) {
+    public <E> void invoke(PerformativeHandler<E> handler, int frameSize, ByteBuffer payload, int channel, E context) {
         handler.handleEnd(frameSize, this, payload, channel, context);
     }
 
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Flow.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Flow.java
index 11f8e71f..adeef7f9 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Flow.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Flow.java
@@ -16,6 +16,7 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec.transport;
 
+import java.nio.ByteBuffer;
 import java.util.List;
 import java.util.Map;
 
@@ -23,8 +24,6 @@ import org.apache.qpid.protonj2.test.driver.codec.primitives.Symbol;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedInteger;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedLong;
 
-import io.netty5.buffer.Buffer;
-
 public class Flow extends PerformativeDescribedType {
 
     public static final Symbol DESCRIPTOR_SYMBOL = Symbol.valueOf("amqp:flow:list");
@@ -171,7 +170,7 @@ public class Flow extends PerformativeDescribedType {
     }
 
     @Override
-    public <E> void invoke(PerformativeHandler<E> handler, int frameSize, Buffer payload, int channel, E context) {
+    public <E> void invoke(PerformativeHandler<E> handler, int frameSize, ByteBuffer payload, int channel, E context) {
         handler.handleFlow(frameSize, this, payload, channel, context);
     }
 
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/HeartBeat.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/HeartBeat.java
index 1d024f62..afa4a37f 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/HeartBeat.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/HeartBeat.java
@@ -16,7 +16,7 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec.transport;
 
-import io.netty5.buffer.Buffer;
+import java.nio.ByteBuffer;
 
 /**
  * Dummy Performative that is fired whenever an Empty frame is received
@@ -40,7 +40,7 @@ public class HeartBeat extends PerformativeDescribedType {
     }
 
     @Override
-    public <E> void invoke(PerformativeHandler<E> handler, int frameSize, Buffer payload, int channel, E context) {
+    public <E> void invoke(PerformativeHandler<E> handler, int frameSize, ByteBuffer payload, int channel, E context) {
         handler.handleHeartBeat(frameSize, INSTANCE, payload, channel, context);
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Open.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Open.java
index 82c28d57..95d5ba8f 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Open.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Open.java
@@ -16,6 +16,7 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec.transport;
 
+import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
@@ -25,8 +26,6 @@ import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedInteger;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedLong;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedShort;
 
-import io.netty5.buffer.Buffer;
-
 public class Open extends PerformativeDescribedType {
 
     public static final Symbol DESCRIPTOR_SYMBOL = Symbol.valueOf("amqp:open:list");
@@ -163,7 +162,7 @@ public class Open extends PerformativeDescribedType {
     }
 
     @Override
-    public <E> void invoke(PerformativeHandler<E> handler, int frameSize, Buffer payload, int channel, E context) {
+    public <E> void invoke(PerformativeHandler<E> handler, int frameSize, ByteBuffer payload, int channel, E context) {
         handler.handleOpen(frameSize, this, payload, channel, context);
     }
 
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/PerformativeDescribedType.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/PerformativeDescribedType.java
index 48a7cb73..d57bd265 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/PerformativeDescribedType.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/PerformativeDescribedType.java
@@ -16,12 +16,11 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec.transport;
 
+import java.nio.ByteBuffer;
 import java.util.List;
 
 import org.apache.qpid.protonj2.test.driver.codec.ListDescribedType;
 
-import io.netty5.buffer.Buffer;
-
 /**
  * AMQP Performative marker class for DescribedType elements in this codec.
  */
@@ -52,34 +51,34 @@ public abstract class PerformativeDescribedType extends ListDescribedType {
 
     public interface PerformativeHandler<E> {
 
-        default void handleOpen(int frameSize, Open open, Buffer payload, int channel, E context) {
+        default void handleOpen(int frameSize, Open open, ByteBuffer payload, int channel, E context) {
             throw new AssertionError("AMQP Open was not handled");
         }
-        default void handleBegin(int frameSize, Begin begin, Buffer payload, int channel, E context) {
+        default void handleBegin(int frameSize, Begin begin, ByteBuffer payload, int channel, E context) {
             throw new AssertionError("AMQP Begin was not handled");
         }
-        default void handleAttach(int frameSize, Attach attach, Buffer payload, int channel, E context) {
+        default void handleAttach(int frameSize, Attach attach, ByteBuffer payload, int channel, E context) {
             throw new AssertionError("AMQP Attach was not handled");
         }
-        default void handleFlow(int frameSize, Flow flow, Buffer payload, int channel, E context) {
+        default void handleFlow(int frameSize, Flow flow, ByteBuffer payload, int channel, E context) {
             throw new AssertionError("AMQP Flow was not handled");
         }
-        default void handleTransfer(int frameSize, Transfer transfer, Buffer payload, int channel, E context) {
+        default void handleTransfer(int frameSize, Transfer transfer, ByteBuffer payload, int channel, E context) {
             throw new AssertionError("AMQP Transfer was not handled");
         }
-        default void handleDisposition(int frameSize, Disposition disposition, Buffer payload, int channel, E context) {
+        default void handleDisposition(int frameSize, Disposition disposition, ByteBuffer payload, int channel, E context) {
             throw new AssertionError("AMQP Disposition was not handled");
         }
-        default void handleDetach(int frameSize, Detach detach, Buffer payload, int channel, E context) {
+        default void handleDetach(int frameSize, Detach detach, ByteBuffer payload, int channel, E context) {
             throw new AssertionError("AMQP Detach was not handled");
         }
-        default void handleEnd(int frameSize, End end, Buffer payload, int channel, E context) {
+        default void handleEnd(int frameSize, End end, ByteBuffer payload, int channel, E context) {
             throw new AssertionError("AMQP End was not handled");
         }
-        default void handleClose(int frameSize, Close close, Buffer payload, int channel, E context) {
+        default void handleClose(int frameSize, Close close, ByteBuffer payload, int channel, E context) {
             throw new AssertionError("AMQP Close was not handled");
         }
-        default void handleHeartBeat(int frameSize, HeartBeat thump, Buffer payload, int channel, E context) {
+        default void handleHeartBeat(int frameSize, HeartBeat thump, ByteBuffer payload, int channel, E context) {
             throw new AssertionError("AMQP Heart Beat frame was not handled");
         }
     }
@@ -88,7 +87,7 @@ public abstract class PerformativeDescribedType extends ListDescribedType {
         return getFieldValue(index);
     }
 
-    public abstract <E> void invoke(PerformativeHandler<E> handler, int frameSize, Buffer payload, int channel, E context);
+    public abstract <E> void invoke(PerformativeHandler<E> handler, int frameSize, ByteBuffer payload, int channel, E context);
 
     @Override
     public String toString() {
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Transfer.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Transfer.java
index e616f729..9fd34c8e 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Transfer.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/transport/Transfer.java
@@ -16,6 +16,7 @@
  */
 package org.apache.qpid.protonj2.test.driver.codec.transport;
 
+import java.nio.ByteBuffer;
 import java.util.List;
 
 import org.apache.qpid.protonj2.test.driver.codec.primitives.Binary;
@@ -25,8 +26,6 @@ import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedByte;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedInteger;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedLong;
 
-import io.netty5.buffer.Buffer;
-
 public class Transfer extends PerformativeDescribedType {
 
     public static final Symbol DESCRIPTOR_SYMBOL = Symbol.valueOf("amqp:transfer:list");
@@ -172,7 +171,7 @@ public class Transfer extends PerformativeDescribedType {
     }
 
     @Override
-    public <E> void invoke(PerformativeHandler<E> handler, int frameSize, Buffer payload, int channel, E context) {
+    public <E> void invoke(PerformativeHandler<E> handler, int frameSize, ByteBuffer payload, int channel, E context) {
         handler.handleTransfer(frameSize, this, payload, channel, context);
     }
 
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/AMQPHeaderExpectation.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/AMQPHeaderExpectation.java
index 61ee0f7a..8cf464c3 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/AMQPHeaderExpectation.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/AMQPHeaderExpectation.java
@@ -19,15 +19,14 @@ package org.apache.qpid.protonj2.test.driver.expectations;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import java.nio.ByteBuffer;
+
 import org.apache.qpid.protonj2.test.driver.AMQPTestDriver;
 import org.apache.qpid.protonj2.test.driver.ScriptedExpectation;
 import org.apache.qpid.protonj2.test.driver.actions.AMQPHeaderInjectAction;
 import org.apache.qpid.protonj2.test.driver.actions.ByteBufferInjectAction;
 import org.apache.qpid.protonj2.test.driver.codec.transport.AMQPHeader;
 
-import io.netty5.buffer.Buffer;
-import io.netty5.buffer.BufferAllocator;
-
 /**
  * Expectation entry for AMQP Headers
  */
@@ -54,12 +53,15 @@ public class AMQPHeaderExpectation implements ScriptedExpectation {
     }
 
     public ByteBufferInjectAction respondWithBytes(byte[] buffer) {
-        ByteBufferInjectAction response = new ByteBufferInjectAction(driver, BufferAllocator.onHeapUnpooled().copyOf(buffer));
+        final ByteBuffer copy = ByteBuffer.allocate(buffer.length);
+        copy.put(buffer).flip().asReadOnlyBuffer();
+
+        final ByteBufferInjectAction response = new ByteBufferInjectAction(driver, copy.asReadOnlyBuffer());
         driver.addScriptedElement(response);
         return response;
     }
 
-    public ByteBufferInjectAction respondWithBytes(Buffer buffer) {
+    public ByteBufferInjectAction respondWithBytes(ByteBuffer buffer) {
         ByteBufferInjectAction response = new ByteBufferInjectAction(driver, buffer);
         driver.addScriptedElement(response);
         return response;
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/AbstractExpectation.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/AbstractExpectation.java
index 498d4da5..38cd7e8b 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/AbstractExpectation.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/AbstractExpectation.java
@@ -18,6 +18,8 @@ package org.apache.qpid.protonj2.test.driver.expectations;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import java.nio.ByteBuffer;
+
 import org.apache.qpid.protonj2.test.driver.AMQPTestDriver;
 import org.apache.qpid.protonj2.test.driver.ScriptedExpectation;
 import org.apache.qpid.protonj2.test.driver.codec.ListDescribedType;
@@ -43,8 +45,6 @@ import org.hamcrest.Matcher;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import io.netty5.buffer.Buffer;
-
 /**
  * Abstract base for expectations that need to handle matchers against fields in the
  * value being expected.
@@ -121,7 +121,7 @@ public abstract class AbstractExpectation<T extends ListDescribedType> implement
         assertThat("Performative does not match expectation", performative, getExpectationMatcher());
     }
 
-    protected final void verifyPayload(Buffer payload) {
+    protected final void verifyPayload(ByteBuffer payload) {
         if (getPayloadMatcher() != null) {
             assertThat("Payload does not match expectation", payload, getPayloadMatcher());
         } else if (payload != null) {
@@ -146,59 +146,59 @@ public abstract class AbstractExpectation<T extends ListDescribedType> implement
 
     protected abstract Class<T> getExpectedTypeClass();
 
-    protected Matcher<Buffer> getPayloadMatcher() {
+    protected Matcher<ByteBuffer> getPayloadMatcher() {
         return null;
     }
 
     //----- Base implementation of the handle methods to describe when we get wrong type.
 
     @Override
-    public void handleOpen(int frameSize, Open open, Buffer payload, int channel, AMQPTestDriver context) {
+    public void handleOpen(int frameSize, Open open, ByteBuffer payload, int channel, AMQPTestDriver context) {
         doVerification(frameSize, open, payload, channel, context);
     }
 
     @Override
-    public void handleBegin(int frameSize, Begin begin, Buffer payload, int channel, AMQPTestDriver context) {
+    public void handleBegin(int frameSize, Begin begin, ByteBuffer payload, int channel, AMQPTestDriver context) {
         doVerification(frameSize, begin, payload, channel, context);
     }
 
     @Override
-    public void handleAttach(int frameSize, Attach attach, Buffer payload, int channel, AMQPTestDriver context) {
+    public void handleAttach(int frameSize, Attach attach, ByteBuffer payload, int channel, AMQPTestDriver context) {
         doVerification(frameSize, attach, payload, channel, context);
     }
 
     @Override
-    public void handleFlow(int frameSize, Flow flow, Buffer payload, int channel, AMQPTestDriver context) {
+    public void handleFlow(int frameSize, Flow flow, ByteBuffer payload, int channel, AMQPTestDriver context) {
         doVerification(frameSize, flow, payload, channel, context);
     }
 
     @Override
-    public void handleTransfer(int frameSize, Transfer transfer, Buffer payload, int channel,AMQPTestDriver context) {
+    public void handleTransfer(int frameSize, Transfer transfer, ByteBuffer payload, int channel,AMQPTestDriver context) {
         doVerification(frameSize, transfer, payload, channel, context);
     }
 
     @Override
-    public void handleDisposition(int frameSize, Disposition disposition, Buffer payload, int channel, AMQPTestDriver context) {
+    public void handleDisposition(int frameSize, Disposition disposition, ByteBuffer payload, int channel, AMQPTestDriver context) {
         doVerification(frameSize, disposition, payload, channel, context);
     }
 
     @Override
-    public void handleDetach(int frameSize, Detach detach, Buffer payload, int channel, AMQPTestDriver context) {
+    public void handleDetach(int frameSize, Detach detach, ByteBuffer payload, int channel, AMQPTestDriver context) {
         doVerification(frameSize, detach, payload, channel, context);
     }
 
     @Override
-    public void handleEnd(int frameSize, End end, Buffer payload, int channel, AMQPTestDriver context) {
+    public void handleEnd(int frameSize, End end, ByteBuffer payload, int channel, AMQPTestDriver context) {
         doVerification(frameSize, end, payload, channel, context);
     }
 
     @Override
-    public void handleClose(int frameSize, Close close, Buffer payload, int channel, AMQPTestDriver context) {
+    public void handleClose(int frameSize, Close close, ByteBuffer payload, int channel, AMQPTestDriver context) {
         doVerification(frameSize, close, payload, channel, context);
     }
 
     @Override
-    public void handleHeartBeat(int frameSize, HeartBeat thump, Buffer payload, int channel, AMQPTestDriver context) {
+    public void handleHeartBeat(int frameSize, HeartBeat thump, ByteBuffer payload, int channel, AMQPTestDriver context) {
         doVerification(frameSize, thump, payload, channel, context);
     }
 
@@ -239,7 +239,7 @@ public abstract class AbstractExpectation<T extends ListDescribedType> implement
 
     //----- Internal implementation
 
-    private void doVerification(int frameSize, Object performative, Buffer payload, int channel, AMQPTestDriver driver) {
+    private void doVerification(int frameSize, Object performative, ByteBuffer payload, int channel, AMQPTestDriver driver) {
         if (getExpectedTypeClass().equals(performative.getClass())) {
             verifyFrameSize(frameSize);
             verifyPayload(payload);
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/AttachExpectation.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/AttachExpectation.java
index d54ac73d..3f1be105 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/AttachExpectation.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/AttachExpectation.java
@@ -21,6 +21,7 @@ import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.CoreMatchers.nullValue;
 
+import java.nio.ByteBuffer;
 import java.util.Map;
 import java.util.UUID;
 
@@ -54,8 +55,6 @@ import org.apache.qpid.protonj2.test.driver.matchers.transactions.CoordinatorMat
 import org.apache.qpid.protonj2.test.driver.matchers.transport.AttachMatcher;
 import org.hamcrest.Matcher;
 
-import io.netty5.buffer.Buffer;
-
 /**
  * Scripted expectation for the AMQP Attach performative
  */
@@ -106,7 +105,7 @@ public class AttachExpectation extends AbstractExpectation<Attach> {
     //----- Handle the performative and configure response is told to respond
 
     @Override
-    public void handleAttach(int frameSize, Attach attach, Buffer payload, int channel, AMQPTestDriver context) {
+    public void handleAttach(int frameSize, Attach attach, ByteBuffer payload, int channel, AMQPTestDriver context) {
         super.handleAttach(frameSize, attach, payload, channel, context);
 
         final UnsignedShort remoteChannel = UnsignedShort.valueOf(channel);
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/BeginExpectation.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/BeginExpectation.java
index 0aba203f..ef7e0c9b 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/BeginExpectation.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/BeginExpectation.java
@@ -21,6 +21,7 @@ import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.CoreMatchers.nullValue;
 
+import java.nio.ByteBuffer;
 import java.util.Map;
 
 import org.apache.qpid.protonj2.test.driver.AMQPTestDriver;
@@ -35,8 +36,6 @@ import org.apache.qpid.protonj2.test.driver.codec.util.TypeMapper;
 import org.apache.qpid.protonj2.test.driver.matchers.transport.BeginMatcher;
 import org.hamcrest.Matcher;
 
-import io.netty5.buffer.Buffer;
-
 /**
  * Scripted expectation for the AMQP Begin performative
  */
@@ -91,7 +90,7 @@ public class BeginExpectation extends AbstractExpectation<Begin> {
     //----- Handle the performative and configure response is told to respond
 
     @Override
-    public void handleBegin(int frameSize, Begin begin, Buffer payload, int channel, AMQPTestDriver context) {
+    public void handleBegin(int frameSize, Begin begin, ByteBuffer payload, int channel, AMQPTestDriver context) {
         super.handleBegin(frameSize, begin, payload, channel, context);
 
         context.sessions().handleBegin(begin, UnsignedShort.valueOf(channel));
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/CloseExpectation.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/CloseExpectation.java
index 3721ea2b..9014ae59 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/CloseExpectation.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/CloseExpectation.java
@@ -18,6 +18,7 @@ package org.apache.qpid.protonj2.test.driver.expectations;
 
 import static org.hamcrest.CoreMatchers.equalTo;
 
+import java.nio.ByteBuffer;
 import java.util.Map;
 
 import org.apache.qpid.protonj2.test.driver.AMQPTestDriver;
@@ -31,8 +32,6 @@ import org.apache.qpid.protonj2.test.driver.codec.util.TypeMapper;
 import org.apache.qpid.protonj2.test.driver.matchers.transport.CloseMatcher;
 import org.hamcrest.Matcher;
 
-import io.netty5.buffer.Buffer;
-
 /**
  * Scripted expectation for the AMQP Close performative
  */
@@ -55,7 +54,7 @@ public class CloseExpectation extends AbstractExpectation<Close> {
     //----- Handle the performative and configure response is told to respond
 
     @Override
-    public void handleClose(int frameSize, Close close, Buffer payload, int channel, AMQPTestDriver context) {
+    public void handleClose(int frameSize, Close close, ByteBuffer payload, int channel, AMQPTestDriver context) {
         super.handleClose(frameSize, close, payload, channel, context);
 
         if (response == null) {
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/DetachExpectation.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/DetachExpectation.java
index 720381a5..7eac73df 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/DetachExpectation.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/DetachExpectation.java
@@ -19,6 +19,7 @@ package org.apache.qpid.protonj2.test.driver.expectations;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.notNullValue;
 
+import java.nio.ByteBuffer;
 import java.util.Map;
 
 import org.apache.qpid.protonj2.test.driver.AMQPTestDriver;
@@ -36,8 +37,6 @@ import org.apache.qpid.protonj2.test.driver.codec.util.TypeMapper;
 import org.apache.qpid.protonj2.test.driver.matchers.transport.DetachMatcher;
 import org.hamcrest.Matcher;
 
-import io.netty5.buffer.Buffer;
-
 /**
  * Scripted expectation for the AMQP Detach performative
  */
@@ -69,7 +68,7 @@ public class DetachExpectation extends AbstractExpectation<Detach> {
     //----- Handle the performative and configure response is told to respond
 
     @Override
-    public void handleDetach(int frameSize, Detach detach, Buffer payload, int channel, AMQPTestDriver context) {
+    public void handleDetach(int frameSize, Detach detach, ByteBuffer payload, int channel, AMQPTestDriver context) {
         super.handleDetach(frameSize, detach, payload, channel, context);
 
         final UnsignedShort remoteChannel = UnsignedShort.valueOf(channel);
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/DispositionExpectation.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/DispositionExpectation.java
index ba311a69..994de96f 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/DispositionExpectation.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/DispositionExpectation.java
@@ -19,6 +19,7 @@ package org.apache.qpid.protonj2.test.driver.expectations;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.notNullValue;
 
+import java.nio.ByteBuffer;
 import java.util.Random;
 
 import org.apache.qpid.protonj2.test.driver.AMQPTestDriver;
@@ -41,8 +42,6 @@ import org.apache.qpid.protonj2.test.driver.matchers.transactions.TransactionalS
 import org.apache.qpid.protonj2.test.driver.matchers.transport.DispositionMatcher;
 import org.hamcrest.Matcher;
 
-import io.netty5.buffer.Buffer;
-
 /**
  * Scripted expectation for the AMQP Disposition performative
  */
@@ -68,7 +67,7 @@ public class DispositionExpectation extends AbstractExpectation<Disposition> {
     //----- Handle the incoming Disposition validation and update local side if able
 
     @Override
-    public void handleDisposition(int frameSize, Disposition disposition, Buffer payload, int channel, AMQPTestDriver context) {
+    public void handleDisposition(int frameSize, Disposition disposition, ByteBuffer payload, int channel, AMQPTestDriver context) {
         super.handleDisposition(frameSize, disposition, payload, channel, context);
 
         final UnsignedShort remoteChannel = UnsignedShort.valueOf(channel);
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/EndExpectation.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/EndExpectation.java
index 22dee07a..891c5dcc 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/EndExpectation.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/EndExpectation.java
@@ -18,6 +18,7 @@ package org.apache.qpid.protonj2.test.driver.expectations;
 
 import static org.hamcrest.CoreMatchers.equalTo;
 
+import java.nio.ByteBuffer;
 import java.util.Map;
 
 import org.apache.qpid.protonj2.test.driver.AMQPTestDriver;
@@ -33,8 +34,6 @@ import org.apache.qpid.protonj2.test.driver.codec.util.TypeMapper;
 import org.apache.qpid.protonj2.test.driver.matchers.transport.EndMatcher;
 import org.hamcrest.Matcher;
 
-import io.netty5.buffer.Buffer;
-
 /**
  * Scripted expectation for the AMQP End performative
  */
@@ -63,7 +62,7 @@ public class EndExpectation extends AbstractExpectation<End> {
     //----- Handle the performative and configure response is told to respond
 
     @Override
-    public void handleEnd(int frameSize, End end, Buffer payload, int channel, AMQPTestDriver context) {
+    public void handleEnd(int frameSize, End end, ByteBuffer payload, int channel, AMQPTestDriver context) {
         super.handleEnd(frameSize, end, payload, channel, context);
 
         // Ensure that local session tracking knows that remote ended a Session.
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/FlowExpectation.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/FlowExpectation.java
index d7b2708a..ecb7266e 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/FlowExpectation.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/FlowExpectation.java
@@ -21,6 +21,7 @@ import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.CoreMatchers.nullValue;
 
+import java.nio.ByteBuffer;
 import java.util.Map;
 
 import org.apache.qpid.protonj2.test.driver.AMQPTestDriver;
@@ -36,8 +37,6 @@ import org.apache.qpid.protonj2.test.driver.codec.transport.Flow;
 import org.apache.qpid.protonj2.test.driver.matchers.transport.FlowMatcher;
 import org.hamcrest.Matcher;
 
-import io.netty5.buffer.Buffer;
-
 /**
  * Scripted expectation for the AMQP Flow performative
  */
@@ -71,7 +70,7 @@ public class FlowExpectation extends AbstractExpectation<Flow> {
     //----- Handle the performative and configure response is told to respond
 
     @Override
-    public void handleFlow(int frameSize, Flow flow, Buffer payload, int channel, AMQPTestDriver context) {
+    public void handleFlow(int frameSize, Flow flow, ByteBuffer payload, int channel, AMQPTestDriver context) {
         super.handleFlow(frameSize, flow, payload, channel, context);
 
         final UnsignedShort remoteChannel = UnsignedShort.valueOf(channel);
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/OpenExpectation.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/OpenExpectation.java
index 90624ad5..afdda58b 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/OpenExpectation.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/OpenExpectation.java
@@ -19,6 +19,7 @@ package org.apache.qpid.protonj2.test.driver.expectations;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.notNullValue;
 
+import java.nio.ByteBuffer;
 import java.util.Map;
 
 import org.apache.qpid.protonj2.test.driver.AMQPTestDriver;
@@ -34,8 +35,6 @@ import org.apache.qpid.protonj2.test.driver.codec.util.TypeMapper;
 import org.apache.qpid.protonj2.test.driver.matchers.transport.OpenMatcher;
 import org.hamcrest.Matcher;
 
-import io.netty5.buffer.Buffer;
-
 /**
  * Scripted expectation for the AMQP Open performative
  */
@@ -96,7 +95,7 @@ public class OpenExpectation extends AbstractExpectation<Open> {
     //----- Handle the performative and configure response is told to respond
 
     @Override
-    public void handleOpen(int frameSize, Open open, Buffer payload, int channel, AMQPTestDriver context) {
+    public void handleOpen(int frameSize, Open open, ByteBuffer payload, int channel, AMQPTestDriver context) {
         super.handleOpen(frameSize, open, payload, channel, context);
 
         if (response != null) {
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/TransferExpectation.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/TransferExpectation.java
index 617e7bba..6e8675b0 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/TransferExpectation.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/expectations/TransferExpectation.java
@@ -20,6 +20,8 @@ import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.CoreMatchers.nullValue;
 
+import java.nio.ByteBuffer;
+
 import org.apache.qpid.protonj2.test.driver.AMQPTestDriver;
 import org.apache.qpid.protonj2.test.driver.LinkTracker;
 import org.apache.qpid.protonj2.test.driver.SessionTracker;
@@ -43,9 +45,6 @@ import org.apache.qpid.protonj2.test.driver.matchers.transport.TransferMatcher;
 import org.hamcrest.Matcher;
 import org.hamcrest.Matchers;
 
-import io.netty5.buffer.Buffer;
-import io.netty5.buffer.BufferAllocator;
-
 /**
  * Scripted expectation for the AMQP Transfer performative
  */
@@ -54,7 +53,7 @@ public class TransferExpectation extends AbstractExpectation<Transfer> {
     private final TransferMatcher matcher = new TransferMatcher();
     private final DeliveryStateBuilder stateBuilder = new DeliveryStateBuilder();
 
-    private Matcher<Buffer> payloadMatcher = Matchers.any(Buffer.class);
+    private Matcher<ByteBuffer> payloadMatcher = Matchers.any(ByteBuffer.class);
 
     protected DispositionInjectAction response;
 
@@ -130,7 +129,7 @@ public class TransferExpectation extends AbstractExpectation<Transfer> {
     }
 
     @Override
-    public void handleTransfer(int frameSize, Transfer transfer, Buffer payload, int channel, AMQPTestDriver driver) {
+    public void handleTransfer(int frameSize, Transfer transfer, ByteBuffer payload, int channel, AMQPTestDriver driver) {
         super.handleTransfer(frameSize, transfer, payload, channel, driver);
 
         final UnsignedShort remoteChannel = UnsignedShort.valueOf(channel);
@@ -254,18 +253,21 @@ public class TransferExpectation extends AbstractExpectation<Transfer> {
     }
 
     public TransferExpectation withNonNullPayload() {
-        this.payloadMatcher = notNullValue(Buffer.class);
+        this.payloadMatcher = notNullValue(ByteBuffer.class);
         return this;
     }
 
     public TransferExpectation withNullPayload() {
-        this.payloadMatcher = nullValue(Buffer.class);
+        this.payloadMatcher = nullValue(ByteBuffer.class);
         return this;
     }
 
     public TransferExpectation withPayload(byte[] buffer) {
+        final ByteBuffer copy = ByteBuffer.allocate(buffer.length);
+        copy.put(buffer).flip();
+
         // TODO - Create Matcher which describes the mismatch in detail
-        this.payloadMatcher = Matchers.equalTo(BufferAllocator.onHeapUnpooled().copyOf(buffer));
+        this.payloadMatcher = Matchers.equalTo(copy.asReadOnlyBuffer());
         return this;
     }
 
@@ -326,7 +328,7 @@ public class TransferExpectation extends AbstractExpectation<Transfer> {
         return this;
     }
 
-    public TransferExpectation withPayload(Matcher<Buffer> payloadMatcher) {
+    public TransferExpectation withPayload(Matcher<ByteBuffer> payloadMatcher) {
         this.payloadMatcher = payloadMatcher;
         return this;
     }
@@ -337,7 +339,7 @@ public class TransferExpectation extends AbstractExpectation<Transfer> {
     }
 
     @Override
-    protected Matcher<Buffer> getPayloadMatcher() {
+    protected Matcher<ByteBuffer> getPayloadMatcher() {
         return payloadMatcher;
     }
 
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/messaging/AbstractMessageSectionMatcher.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/messaging/AbstractMessageSectionMatcher.java
index a49d1781..5c1e822f 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/messaging/AbstractMessageSectionMatcher.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/messaging/AbstractMessageSectionMatcher.java
@@ -20,6 +20,7 @@ package org.apache.qpid.protonj2.test.driver.matchers.messaging;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import java.nio.ByteBuffer;
 import java.util.Map;
 
 import org.apache.qpid.protonj2.test.driver.codec.Codec;
@@ -30,8 +31,6 @@ import org.hamcrest.Matcher;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import io.netty5.buffer.Buffer;
-
 public abstract class AbstractMessageSectionMatcher {
 
     private final Logger LOG = LoggerFactory.getLogger(getClass());
@@ -68,8 +67,8 @@ public abstract class AbstractMessageSectionMatcher {
      * @throws RuntimeException
      *         if the provided Binary does not match expectation in some way
      */
-    public int verify(Buffer receivedBinary) throws RuntimeException {
-        final int length = receivedBinary.readableBytes();
+    public int verify(ByteBuffer receivedBinary) throws RuntimeException {
+        final int length = receivedBinary.remaining();
         final Codec data = Codec.Factory.create();
         final long decoded = data.decode(receivedBinary);
 
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/transport/TransferPayloadCompositeMatcher.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/transport/TransferPayloadCompositeMatcher.java
index 0abff7a8..95dcd64d 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/transport/TransferPayloadCompositeMatcher.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/transport/TransferPayloadCompositeMatcher.java
@@ -22,6 +22,7 @@ package org.apache.qpid.protonj2.test.driver.matchers.transport;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -36,13 +37,11 @@ import org.hamcrest.Matcher;
 import org.hamcrest.StringDescription;
 import org.hamcrest.TypeSafeMatcher;
 
-import io.netty5.buffer.Buffer;
-
 /**
  * Used to verify the Transfer frame payload, i.e the sections of the AMQP
  * message such as the header, properties, and body sections.
  */
-public class TransferPayloadCompositeMatcher extends TypeSafeMatcher<Buffer> {
+public class TransferPayloadCompositeMatcher extends TypeSafeMatcher<ByteBuffer> {
 
     private HeaderMatcher headersMatcher;
     private String headerMatcherFailureDescription;
@@ -54,7 +53,7 @@ public class TransferPayloadCompositeMatcher extends TypeSafeMatcher<Buffer> {
     private String propertiesMatcherFailureDescription;
     private ApplicationPropertiesMatcher applicationPropertiesMatcher;
     private String applicationPropertiesMatcherFailureDescription;
-    private List<Matcher<Buffer>> msgContentMatchers = new ArrayList<>();
+    private List<Matcher<ByteBuffer>> msgContentMatchers = new ArrayList<>();
     private String msgContentMatcherFailureDescription;
     private FooterMatcher footersMatcher;
     private String footerMatcherFailureDescription;
@@ -65,8 +64,10 @@ public class TransferPayloadCompositeMatcher extends TypeSafeMatcher<Buffer> {
     }
 
     @Override
-    protected boolean matchesSafely(final Buffer receivedBinary) {
-        int origLength = receivedBinary.readableBytes();
+    protected boolean matchesSafely(final ByteBuffer receivedBinary) {
+        final ByteBuffer receivedSlice = receivedBinary.slice().asReadOnlyBuffer();
+        final int origLength = receivedBinary.remaining();
+
         int bytesConsumed = 0;
 
         // Length Matcher
@@ -82,110 +83,106 @@ public class TransferPayloadCompositeMatcher extends TypeSafeMatcher<Buffer> {
 
         // MessageHeader Section
         if (headersMatcher != null) {
-            try (Buffer msgHeaderEtcSubBinary = receivedBinary.copy(bytesConsumed, origLength - bytesConsumed, true)) {
-                try {
-                    bytesConsumed += headersMatcher.verify(msgHeaderEtcSubBinary);
-                } catch (Throwable t) {
-                    headerMatcherFailureDescription = "\nActual encoded form of remaining bytes passed to MessageHeaderMatcher: " + msgHeaderEtcSubBinary;
-                    headerMatcherFailureDescription += "\nMessageHeaderMatcher generated throwable: " + t;
+            try {
+                bytesConsumed += headersMatcher.verify(receivedSlice.slice());
+                receivedSlice.position(bytesConsumed);
+            } catch (Throwable t) {
+                headerMatcherFailureDescription = "\nActual encoded form of remaining bytes passed to MessageHeaderMatcher: " + receivedSlice;
+                headerMatcherFailureDescription += "\nMessageHeaderMatcher generated throwable: " + t;
 
-                    return false;
-                }
+                return false;
             }
         }
 
         // DeliveryAnnotations Section
         if (deliveryAnnotationsMatcher != null) {
-            try (Buffer daAnnotationsEtcSubBinary = receivedBinary.copy(bytesConsumed, origLength - bytesConsumed, true)) {
-                try {
-                    bytesConsumed += deliveryAnnotationsMatcher.verify(daAnnotationsEtcSubBinary);
-                } catch (Throwable t) {
-                    deliveryAnnotationsMatcherFailureDescription = "\nActual encoded form of remaining bytes passed to DeliveryAnnotationsMatcher: "
-                        + daAnnotationsEtcSubBinary;
-                    deliveryAnnotationsMatcherFailureDescription += "\nDeliveryAnnotationsMatcher generated throwable: " + t;
+            try {
+                bytesConsumed += deliveryAnnotationsMatcher.verify(receivedSlice.slice());
+                receivedSlice.position(bytesConsumed);
+            } catch (Throwable t) {
+                deliveryAnnotationsMatcherFailureDescription = "\nActual encoded form of remaining bytes passed " +
+                                                               "to DeliveryAnnotationsMatcher: " + receivedSlice;
+                deliveryAnnotationsMatcherFailureDescription += "\nDeliveryAnnotationsMatcher generated throwable: " + t;
 
-                    return false;
-                }
+                return false;
             }
         }
 
         // MessageAnnotations Section
         if (messageAnnotationsMatcher != null) {
-            try (Buffer msgAnnotationsEtcSubBinary = receivedBinary.copy(bytesConsumed, origLength - bytesConsumed, true)) {
-                try {
-                    bytesConsumed += messageAnnotationsMatcher.verify(msgAnnotationsEtcSubBinary);
-                } catch (Throwable t) {
-                    messageAnnotationsMatcherFailureDescription = "\nActual encoded form of remaining bytes passed to MessageAnnotationsMatcher: "
-                        + msgAnnotationsEtcSubBinary;
-                    messageAnnotationsMatcherFailureDescription += "\nMessageAnnotationsMatcher generated throwable: " + t;
+            try {
+                bytesConsumed += messageAnnotationsMatcher.verify(receivedSlice.slice());
+                receivedSlice.position(bytesConsumed);
+            } catch (Throwable t) {
+                messageAnnotationsMatcherFailureDescription = "\nActual encoded form of remaining bytes passed to " +
+                                                              "MessageAnnotationsMatcher: " + receivedSlice;
+                messageAnnotationsMatcherFailureDescription += "\nMessageAnnotationsMatcher generated throwable: " + t;
 
-                    return false;
-                }
+                return false;
             }
         }
 
         // Properties Section
         if (propertiesMatcher != null) {
-            try (Buffer propsEtcSubBinary = receivedBinary.copy(bytesConsumed, origLength - bytesConsumed, true)) {
-                try {
-                    bytesConsumed += propertiesMatcher.verify(propsEtcSubBinary);
-                } catch (Throwable t) {
-                    propertiesMatcherFailureDescription = "\nActual encoded form of remaining bytes passed to PropertiesMatcher: " + propsEtcSubBinary;
-                    propertiesMatcherFailureDescription += "\nPropertiesMatcher generated throwable: " + t;
+            try {
+                bytesConsumed += propertiesMatcher.verify(receivedSlice.slice());
+                receivedSlice.position(bytesConsumed);
+            } catch (Throwable t) {
+                propertiesMatcherFailureDescription = "\nActual encoded form of remaining bytes passed to " +
+                                                      "PropertiesMatcher: " + receivedSlice;
+                propertiesMatcherFailureDescription += "\nPropertiesMatcher generated throwable: " + t;
 
-                    return false;
-                }
+                return false;
             }
         }
 
         // Application Properties Section
         if (applicationPropertiesMatcher != null) {
-            try (Buffer appPropsEtcSubBinary = receivedBinary.copy(bytesConsumed, origLength - bytesConsumed, true)) {
-                try {
-                    bytesConsumed += applicationPropertiesMatcher.verify(appPropsEtcSubBinary);
-                } catch (Throwable t) {
-                    applicationPropertiesMatcherFailureDescription = "\nActual encoded form of remaining bytes passed to ApplicationPropertiesMatcher: " + appPropsEtcSubBinary;
-                    applicationPropertiesMatcherFailureDescription += "\nApplicationPropertiesMatcher generated throwable: " + t;
+            try {
+                bytesConsumed += applicationPropertiesMatcher.verify(receivedSlice.slice());
+                receivedSlice.position(bytesConsumed);
+            } catch (Throwable t) {
+                applicationPropertiesMatcherFailureDescription = "\nActual encoded form of remaining bytes passed to " +
+                                                                 "ApplicationPropertiesMatcher: " + receivedSlice;
+                applicationPropertiesMatcherFailureDescription += "\nApplicationPropertiesMatcher generated throwable: " + t;
 
-                    return false;
-                }
+                return false;
             }
         }
 
         // Message Content Body Section, already a Matcher<Binary>
         if (!msgContentMatchers.isEmpty()) {
-            for (Matcher<Buffer> msgContentMatcher : msgContentMatchers) {
-                try (Buffer msgContentBodyEtcSubBinary = receivedBinary.copy(bytesConsumed, origLength - bytesConsumed, true)) {
-                    final int originalReadableBytes = msgContentBodyEtcSubBinary.readableBytes();
-                    final boolean contentMatches = msgContentMatcher.matches(msgContentBodyEtcSubBinary);
-                    if (!contentMatches) {
-                        Description desc = new StringDescription();
-                        msgContentMatcher.describeTo(desc);
-                        msgContentMatcher.describeMismatch(msgContentBodyEtcSubBinary, desc);
-
-                        msgContentMatcherFailureDescription = "\nMessageContentMatcher mismatch Description:";
-                        msgContentMatcherFailureDescription += desc.toString();
-
-                        return false;
-                    }
-
-                    bytesConsumed += originalReadableBytes - msgContentBodyEtcSubBinary.readableBytes();
+            final ByteBuffer slicedMsgContext = receivedSlice.slice();
+
+            for (Matcher<ByteBuffer> msgContentMatcher : msgContentMatchers) {
+                final int originalReadableBytes = slicedMsgContext.remaining();
+                final boolean contentMatches = msgContentMatcher.matches(slicedMsgContext);
+                if (!contentMatches) {
+                    Description desc = new StringDescription();
+                    msgContentMatcher.describeTo(desc);
+                    msgContentMatcher.describeMismatch(receivedSlice, desc);
+
+                    msgContentMatcherFailureDescription = "\nMessageContentMatcher mismatch Description:";
+                    msgContentMatcherFailureDescription += desc.toString();
+
+                    return false;
                 }
+
+                bytesConsumed += originalReadableBytes - slicedMsgContext.remaining();
+                receivedSlice.position(bytesConsumed);
             }
         }
 
         // MessageAnnotations Section
         if (footersMatcher != null) {
-            try (Buffer footersSubBinary = receivedBinary.copy(bytesConsumed, origLength - bytesConsumed, true)) {
-                try {
-                    bytesConsumed += footersMatcher.verify(footersSubBinary);
-                } catch (Throwable t) {
-                    footerMatcherFailureDescription = "\nActual encoded form of remaining bytes passed to FooterMatcher: "
-                        + footersSubBinary;
-                    footerMatcherFailureDescription += "\nFooterMatcher generated throwable: " + t;
+            try {
+                bytesConsumed += footersMatcher.verify(receivedSlice.slice());
+            } catch (Throwable t) {
+                footerMatcherFailureDescription = "\nActual encoded form of remaining bytes passed to " +
+                                                  "FooterMatcher: " + receivedSlice;
+                footerMatcherFailureDescription += "\nFooterMatcher generated throwable: " + t;
 
-                    return false;
-                }
+                return false;
             }
         }
 
@@ -198,7 +195,7 @@ public class TransferPayloadCompositeMatcher extends TypeSafeMatcher<Buffer> {
     }
 
     @Override
-    protected void describeMismatchSafely(Buffer item, Description mismatchDescription) {
+    protected void describeMismatchSafely(ByteBuffer item, Description mismatchDescription) {
         mismatchDescription.appendText("\nActual encoded form of the full Transfer frame payload: ").appendValue(item);
 
         // Payload Length
@@ -277,7 +274,7 @@ public class TransferPayloadCompositeMatcher extends TypeSafeMatcher<Buffer> {
         this.applicationPropertiesMatcher = appPropsMatcher;
     }
 
-    public void setMessageContentMatcher(Matcher<Buffer> msgContentMatcher) {
+    public void setMessageContentMatcher(Matcher<ByteBuffer> msgContentMatcher) {
         if (msgContentMatchers.isEmpty()) {
             msgContentMatchers.add(msgContentMatcher);
         } else {
@@ -285,7 +282,7 @@ public class TransferPayloadCompositeMatcher extends TypeSafeMatcher<Buffer> {
         }
     }
 
-    public void addMessageContentMatcher(Matcher<Buffer> msgContentMatcher) {
+    public void addMessageContentMatcher(Matcher<ByteBuffer> msgContentMatcher) {
         msgContentMatchers.add(msgContentMatcher);
     }
 
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/types/EncodedAmqpTypeMatcher.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/types/EncodedAmqpTypeMatcher.java
index c11a7049..59743d27 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/types/EncodedAmqpTypeMatcher.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/types/EncodedAmqpTypeMatcher.java
@@ -18,6 +18,8 @@
  */
 package org.apache.qpid.protonj2.test.driver.matchers.types;
 
+import java.nio.ByteBuffer;
+
 import org.apache.qpid.protonj2.test.driver.codec.Codec;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.DescribedType;
 import org.apache.qpid.protonj2.test.driver.codec.primitives.Symbol;
@@ -26,9 +28,7 @@ import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.hamcrest.TypeSafeMatcher;
 
-import io.netty5.buffer.Buffer;
-
-public abstract class EncodedAmqpTypeMatcher extends TypeSafeMatcher<Buffer> {
+public abstract class EncodedAmqpTypeMatcher extends TypeSafeMatcher<ByteBuffer> {
 
     private final Symbol descriptorSymbol;
     private final UnsignedLong descriptorCode;
@@ -53,8 +53,8 @@ public abstract class EncodedAmqpTypeMatcher extends TypeSafeMatcher<Buffer> {
     }
 
     @Override
-    protected boolean matchesSafely(Buffer receivedBinary) {
-        int length = receivedBinary.readableBytes();
+    protected boolean matchesSafely(ByteBuffer receivedBinary) {
+        int length = receivedBinary.remaining();
         Codec data = Codec.Factory.create();
         long decoded = data.decode(receivedBinary);
         decodedDescribedType = data.getDescribedType();
@@ -86,7 +86,7 @@ public abstract class EncodedAmqpTypeMatcher extends TypeSafeMatcher<Buffer> {
     }
 
     @Override
-    protected void describeMismatchSafely(Buffer item, Description mismatchDescription) {
+    protected void describeMismatchSafely(ByteBuffer item, Description mismatchDescription) {
         mismatchDescription.appendText("\nActual encoded form: ").appendValue(item);
 
         if (decodedDescribedType != null) {
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/types/EncodedCompositingDataSectionMatcher.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/types/EncodedCompositingDataSectionMatcher.java
index 4526f630..5ef9dba8 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/types/EncodedCompositingDataSectionMatcher.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/types/EncodedCompositingDataSectionMatcher.java
@@ -19,6 +19,7 @@
 package org.apache.qpid.protonj2.test.driver.matchers.types;
 
 import java.nio.ByteBuffer;
+import java.util.Arrays;
 import java.util.Objects;
 
 import org.apache.qpid.protonj2.test.driver.codec.EncodingCodes;
@@ -29,21 +30,18 @@ import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedLong;
 import org.hamcrest.Description;
 import org.hamcrest.TypeSafeMatcher;
 
-import io.netty5.buffer.Buffer;
-import io.netty5.buffer.BufferAllocator;
-
 /**
  * Data Section matcher that can be used with multiple expectTransfer calls to match a larger
  * given payload block to the contents of one or more incoming Data sections split across multiple
  * transfer frames and or multiple Data Sections within those transfer frames.
  */
-public class EncodedCompositingDataSectionMatcher extends TypeSafeMatcher<Buffer> {
+public class EncodedCompositingDataSectionMatcher extends TypeSafeMatcher<ByteBuffer> {
 
     private static final Symbol DESCRIPTOR_SYMBOL = Symbol.valueOf("amqp:data:binary");
     private static final UnsignedLong DESCRIPTOR_CODE = UnsignedLong.valueOf(0x0000000000000075L);
 
     private final int expectedValueSize;
-    private final Buffer expectedValue;
+    private final ByteBuffer expectedValue;
 
     private boolean expectTrailingBytes;
     private String decodingErrorDescription;
@@ -59,7 +57,7 @@ public class EncodedCompositingDataSectionMatcher extends TypeSafeMatcher<Buffer
      *        the value that is expected to be IN the received {@link Data}
      */
     public EncodedCompositingDataSectionMatcher(byte[] expectedValue) {
-        this(BufferAllocator.onHeapUnpooled().copyOf(expectedValue).makeReadOnly());
+        this(ByteBuffer.wrap(Arrays.copyOf(expectedValue, expectedValue.length)).asReadOnlyBuffer());
     }
 
     /**
@@ -67,18 +65,18 @@ public class EncodedCompositingDataSectionMatcher extends TypeSafeMatcher<Buffer
      *        the value that is expected to be IN the received {@link Data}
      */
     public EncodedCompositingDataSectionMatcher(Binary expectedValue) {
-        this(BufferAllocator.onHeapUnpooled().copyOf(expectedValue.asByteBuffer()).makeReadOnly());
+        this(ByteBuffer.wrap(expectedValue.arrayCopy()).asReadOnlyBuffer());
     }
 
     /**
      * @param expectedValue
      *        the value that is expected to be IN the received {@link Data}
      */
-    public EncodedCompositingDataSectionMatcher(Buffer expectedValue) {
+    public EncodedCompositingDataSectionMatcher(ByteBuffer expectedValue) {
         Objects.requireNonNull(expectedValue, "The expected value cannot be null for this matcher");
 
-        this.expectedValue = expectedValue;
-        this.expectedRemainingBytes = this.expectedValue.readableBytes();
+        this.expectedValue = expectedValue.asReadOnlyBuffer();
+        this.expectedRemainingBytes = this.expectedValue.remaining();
         this.expectedValueSize = this.expectedRemainingBytes;
     }
 
@@ -96,7 +94,7 @@ public class EncodedCompositingDataSectionMatcher extends TypeSafeMatcher<Buffer
     }
 
     @Override
-    protected boolean matchesSafely(Buffer receivedBinary) {
+    protected boolean matchesSafely(ByteBuffer receivedBinary) {
         if (expectDataSectionPreamble) {
             Object descriptor = readDescribedTypeEncoding(receivedBinary);
 
@@ -105,12 +103,12 @@ public class EncodedCompositingDataSectionMatcher extends TypeSafeMatcher<Buffer
             }
 
             // Should be a Binary AMQP type with a length value and possibly some bytes
-            byte encodingCode = receivedBinary.readByte();
+            byte encodingCode = receivedBinary.get();
 
             if (encodingCode == EncodingCodes.VBIN8) {
-                expectedCurrentDataSectionBytes = receivedBinary.readByte() & 0xFF;
+                expectedCurrentDataSectionBytes = receivedBinary.get() & 0xFF;
             } else if (encodingCode == EncodingCodes.VBIN32) {
-                expectedCurrentDataSectionBytes = receivedBinary.readInt();
+                expectedCurrentDataSectionBytes = receivedBinary.getInt();
             } else {
                 decodingErrorDescription = "Expected to read a Binary Type but read encoding code: " + encodingCode;
                 return false;
@@ -126,28 +124,28 @@ public class EncodedCompositingDataSectionMatcher extends TypeSafeMatcher<Buffer
         }
 
         if (expectedRemainingBytes != 0) {
-            final int currentChunkSize = Math.min(expectedCurrentDataSectionBytes, receivedBinary.readableBytes());
-            try (Buffer expectedValueChunk = expectedValue.copy(expectedValue.readerOffset(), currentChunkSize, true);
-                 Buffer currentChunk = receivedBinary.copy(receivedBinary.readerOffset(), currentChunkSize, true)) {
+            final int currentChunkSize = Math.min(expectedCurrentDataSectionBytes, receivedBinary.remaining());
 
-                receivedBinary.skipReadableBytes(currentChunkSize);
-                expectedValue.skipReadableBytes(currentChunkSize);
+            final ByteBuffer expectedValueChunk = expectedValue.slice().limit(currentChunkSize);
+            final ByteBuffer currentChunk = receivedBinary.slice().limit(currentChunkSize);
 
-                if (!expectedValueChunk.equals(currentChunk)) {
-                    return false;
-                }
+            receivedBinary.position(receivedBinary.position() + currentChunkSize);
+            expectedValue.position(expectedValue.position() + currentChunkSize);
 
-                expectedRemainingBytes -= currentChunkSize;
-                expectedCurrentDataSectionBytes -= currentChunkSize;
+            if (!expectedValueChunk.equals(currentChunk)) {
+                return false;
             }
 
+            expectedRemainingBytes -= currentChunkSize;
+            expectedCurrentDataSectionBytes -= currentChunkSize;
+
             if (expectedRemainingBytes != 0 && expectedCurrentDataSectionBytes == 0) {
                 expectDataSectionPreamble = true;
                 expectedCurrentDataSectionBytes = -1;
             }
         }
 
-        if (expectedRemainingBytes == 0 && receivedBinary.readableBytes() > 0 && !isTrailingBytesExpected()) {
+        if (expectedRemainingBytes == 0 && receivedBinary.remaining() > 0 && !isTrailingBytesExpected()) {
             unexpectedTrailingBytes = true;
             return false;
         } else {
@@ -157,18 +155,18 @@ public class EncodedCompositingDataSectionMatcher extends TypeSafeMatcher<Buffer
 
     private static final int DESCRIBED_TYPE_INDICATOR = 0;
 
-    private Object readDescribedTypeEncoding(Buffer data) {
-        byte encodingCode = data.readByte();
+    private Object readDescribedTypeEncoding(ByteBuffer data) {
+        byte encodingCode = data.get();
 
         if (encodingCode == DESCRIBED_TYPE_INDICATOR) {
-            encodingCode = data.readByte();
+            encodingCode = data.get();
             switch (encodingCode) {
                 case EncodingCodes.ULONG0:
                     return UnsignedLong.ZERO;
                 case EncodingCodes.SMALLULONG:
-                    return UnsignedLong.valueOf(data.readByte() & 0xff);
+                    return UnsignedLong.valueOf(data.get() & 0xff);
                 case EncodingCodes.ULONG:
-                    return UnsignedLong.valueOf(data.readLong());
+                    return UnsignedLong.valueOf(data.getLong());
                 case EncodingCodes.SYM8:
                     return readSymbol8(data);
                 case EncodingCodes.SYM32:
@@ -183,34 +181,30 @@ public class EncodedCompositingDataSectionMatcher extends TypeSafeMatcher<Buffer
         return null;
     }
 
-    private Symbol readSymbol32(Buffer buffer) {
-        int length = buffer.readInt();
-
-        if (length == 0) {
-            return Symbol.valueOf("");
-        } else {
-            final ByteBuffer symbolBuffer = ByteBuffer.allocate(length);
-            buffer.readBytes(symbolBuffer);
-
-            return Symbol.getSymbol(symbolBuffer, false);
-        }
+    private static Symbol readSymbol32(ByteBuffer buffer) {
+        return readSymbol(buffer.getInt(), buffer);
     }
 
-    private Symbol readSymbol8(Buffer buffer) {
-        int length = buffer.readByte() & 0xFF;
+    private static Symbol readSymbol8(ByteBuffer buffer) {
+        return readSymbol(buffer.get() & 0xFF, buffer);
+    }
 
+    private static Symbol readSymbol(int length, ByteBuffer buffer) {
         if (length == 0) {
             return Symbol.valueOf("");
         } else {
-            final ByteBuffer symbolBuffer = ByteBuffer.allocate(length);
-            buffer.readBytes(symbolBuffer);
+            final byte[] symbolBytes = new byte[length];
+
+            buffer.get(symbolBytes);
+
+            final ByteBuffer symbolBuffer = ByteBuffer.wrap(symbolBytes).asReadOnlyBuffer();
 
             return Symbol.getSymbol(symbolBuffer, false);
         }
     }
 
     @Override
-    protected void describeMismatchSafely(Buffer item, Description mismatchDescription) {
+    protected void describeMismatchSafely(ByteBuffer item, Description mismatchDescription) {
         mismatchDescription.appendText("\nActual encoded form: ").appendValue(item);
 
         if (decodingErrorDescription != null) {
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/types/EncodedPartialDataSectionMatcher.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/types/EncodedPartialDataSectionMatcher.java
index 5e396473..80855da6 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/types/EncodedPartialDataSectionMatcher.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/types/EncodedPartialDataSectionMatcher.java
@@ -19,6 +19,7 @@
 package org.apache.qpid.protonj2.test.driver.matchers.types;
 
 import java.nio.ByteBuffer;
+import java.util.Arrays;
 
 import org.apache.qpid.protonj2.test.driver.codec.EncodingCodes;
 import org.apache.qpid.protonj2.test.driver.codec.messaging.Data;
@@ -28,16 +29,13 @@ import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedLong;
 import org.hamcrest.Description;
 import org.hamcrest.TypeSafeMatcher;
 
-import io.netty5.buffer.Buffer;
-import io.netty5.buffer.BufferAllocator;
-
-public class EncodedPartialDataSectionMatcher extends TypeSafeMatcher<Buffer> {
+public class EncodedPartialDataSectionMatcher extends TypeSafeMatcher<ByteBuffer> {
 
     private static final Symbol DESCRIPTOR_SYMBOL = Symbol.valueOf("amqp:data:binary");
     private static final UnsignedLong DESCRIPTOR_CODE = UnsignedLong.valueOf(0x0000000000000075L);
 
     private final boolean expectDataSectionPreamble;
-    private final Buffer expectedValue;
+    private final ByteBuffer expectedValue;
     private final int expectedEncodedSize;
     private boolean expectTrailingBytes;
     private String decodingErrorDescription;
@@ -51,7 +49,7 @@ public class EncodedPartialDataSectionMatcher extends TypeSafeMatcher<Buffer> {
      *        the value that is expected to be IN the received {@link Data}
      */
     public EncodedPartialDataSectionMatcher(int expectedEncodedSize, byte[] expectedValue) {
-        this(expectedEncodedSize, BufferAllocator.onHeapUnpooled().copyOf(expectedValue), true);
+        this(expectedEncodedSize, ByteBuffer.wrap(Arrays.copyOf(expectedValue, expectedValue.length)).asReadOnlyBuffer(), true);
     }
 
     /**
@@ -62,7 +60,7 @@ public class EncodedPartialDataSectionMatcher extends TypeSafeMatcher<Buffer> {
      *        the value that is expected to be IN the received {@link Data}
      */
     public EncodedPartialDataSectionMatcher(int expectedEncodedSize, Binary expectedValue) {
-        this(expectedEncodedSize, BufferAllocator.onHeapUnpooled().copyOf(expectedValue.asByteBuffer()), true);
+        this(expectedEncodedSize, ByteBuffer.wrap(expectedValue.arrayCopy()).asReadOnlyBuffer(), true);
     }
 
     /**
@@ -72,7 +70,7 @@ public class EncodedPartialDataSectionMatcher extends TypeSafeMatcher<Buffer> {
      * @param expectedValue
      *        the value that is expected to be IN the received {@link Data}
      */
-    public EncodedPartialDataSectionMatcher(int expectedEncodedSize, Buffer expectedValue) {
+    public EncodedPartialDataSectionMatcher(int expectedEncodedSize, ByteBuffer expectedValue) {
         this(expectedEncodedSize, expectedValue, true);
     }
 
@@ -81,7 +79,7 @@ public class EncodedPartialDataSectionMatcher extends TypeSafeMatcher<Buffer> {
      *        the value that is expected to be IN the received {@link Data}
      */
     public EncodedPartialDataSectionMatcher(byte[] expectedValue) {
-        this(-1, BufferAllocator.onHeapUnpooled().copyOf(expectedValue), false);
+        this(-1, ByteBuffer.wrap(Arrays.copyOf(expectedValue, expectedValue.length)).asReadOnlyBuffer(), false);
     }
 
     /**
@@ -89,14 +87,14 @@ public class EncodedPartialDataSectionMatcher extends TypeSafeMatcher<Buffer> {
      *        the value that is expected to be IN the received {@link Data}
      */
     public EncodedPartialDataSectionMatcher(Binary expectedValue) {
-        this(-1, BufferAllocator.onHeapUnpooled().copyOf(expectedValue.asByteBuffer()), false);
+        this(-1, ByteBuffer.wrap(expectedValue.arrayCopy()).asReadOnlyBuffer(), false);
     }
 
     /**
      * @param expectedValue
      *        the value that is expected to be IN the received {@link Data}
      */
-    public EncodedPartialDataSectionMatcher(Buffer expectedValue) {
+    public EncodedPartialDataSectionMatcher(ByteBuffer expectedValue) {
         this(-1, expectedValue, false);
     }
 
@@ -110,8 +108,8 @@ public class EncodedPartialDataSectionMatcher extends TypeSafeMatcher<Buffer> {
      *        should the matcher check for the Data and Binary section encoding
      *        meta-data or only match the payload to the given expected value.
      */
-    protected EncodedPartialDataSectionMatcher(int expectedEncodedSize, Buffer expectedValue, boolean expectDataSectionPreamble) {
-        this.expectedValue = expectedValue;
+    protected EncodedPartialDataSectionMatcher(int expectedEncodedSize, ByteBuffer expectedValue, boolean expectDataSectionPreamble) {
+        this.expectedValue = expectedValue.asReadOnlyBuffer();
         this.expectedEncodedSize = expectedEncodedSize;
         this.expectDataSectionPreamble = expectDataSectionPreamble;
     }
@@ -130,7 +128,7 @@ public class EncodedPartialDataSectionMatcher extends TypeSafeMatcher<Buffer> {
     }
 
     @Override
-    protected boolean matchesSafely(Buffer receivedBinary) {
+    protected boolean matchesSafely(ByteBuffer receivedBinary) {
         if (expectDataSectionPreamble) {
             Object descriptor = readDescribedTypeEncoding(receivedBinary);
 
@@ -139,13 +137,13 @@ public class EncodedPartialDataSectionMatcher extends TypeSafeMatcher<Buffer> {
             }
 
             // Should be a Binary AMQP type with a length value and possibly some bytes
-            byte encodingCode = receivedBinary.readByte();
+            byte encodingCode = receivedBinary.get();
             int binaryEncodedSize = -1;
 
             if (encodingCode == EncodingCodes.VBIN8) {
-                binaryEncodedSize = receivedBinary.readByte() & 0xFF;
+                binaryEncodedSize = receivedBinary.get() & 0xFF;
             } else if (encodingCode == EncodingCodes.VBIN32) {
-                binaryEncodedSize = receivedBinary.readInt();
+                binaryEncodedSize = receivedBinary.getInt();
             } else {
                 decodingErrorDescription = "Expected to read a Binary Type but read encoding code: " + encodingCode;
                 return false;
@@ -159,14 +157,14 @@ public class EncodedPartialDataSectionMatcher extends TypeSafeMatcher<Buffer> {
         }
 
         if (expectedValue != null) {
-            Buffer payload = receivedBinary.copy(true);
-            receivedBinary.skipReadableBytes(payload.readableBytes());
+            final ByteBuffer payload = receivedBinary.slice().asReadOnlyBuffer();
+            receivedBinary.position(receivedBinary.position() + payload.remaining());
             if (!expectedValue.equals(payload)) {
                 return false;
             }
         }
 
-        if (receivedBinary.readableBytes() > 0 && !isTrailingBytesExpected()) {
+        if (receivedBinary.remaining() > 0 && !isTrailingBytesExpected()) {
             unexpectedTrailingBytes = true;
             return false;
         } else {
@@ -176,18 +174,18 @@ public class EncodedPartialDataSectionMatcher extends TypeSafeMatcher<Buffer> {
 
     private static final int DESCRIBED_TYPE_INDICATOR = 0;
 
-    private Object readDescribedTypeEncoding(Buffer data) {
-        byte encodingCode = data.readByte();
+    private Object readDescribedTypeEncoding(ByteBuffer data) {
+        byte encodingCode = data.get();
 
         if (encodingCode == DESCRIBED_TYPE_INDICATOR) {
-            encodingCode = data.readByte();
+            encodingCode = data.get();
             switch (encodingCode) {
                 case EncodingCodes.ULONG0:
                     return UnsignedLong.ZERO;
                 case EncodingCodes.SMALLULONG:
-                    return UnsignedLong.valueOf(data.readByte() & 0xff);
+                    return UnsignedLong.valueOf(data.get() & 0xff);
                 case EncodingCodes.ULONG:
-                    return UnsignedLong.valueOf(data.readLong());
+                    return UnsignedLong.valueOf(data.getLong());
                 case EncodingCodes.SYM8:
                     return readSymbol8(data);
                 case EncodingCodes.SYM32:
@@ -202,34 +200,30 @@ public class EncodedPartialDataSectionMatcher extends TypeSafeMatcher<Buffer> {
         return null;
     }
 
-    private Symbol readSymbol32(Buffer buffer) {
-        int length = buffer.readInt();
-
-        if (length == 0) {
-            return Symbol.valueOf("");
-        } else {
-            final ByteBuffer symbolBuffer = ByteBuffer.allocate(length);
-            buffer.readBytes(symbolBuffer);
-
-            return Symbol.getSymbol(symbolBuffer, false);
-        }
+    private static Symbol readSymbol32(ByteBuffer buffer) {
+        return readSymbol(buffer.getInt(), buffer);
     }
 
-    private Symbol readSymbol8(Buffer buffer) {
-        int length = buffer.readByte() & 0xFF;
+    private static Symbol readSymbol8(ByteBuffer buffer) {
+        return readSymbol(buffer.get() & 0xFF, buffer);
+    }
 
+    private static Symbol readSymbol(int length, ByteBuffer buffer) {
         if (length == 0) {
             return Symbol.valueOf("");
         } else {
-            final ByteBuffer symbolBuffer = ByteBuffer.allocate(length);
-            buffer.readBytes(symbolBuffer);
+            final byte[] symbolBytes = new byte[length];
+
+            buffer.get(symbolBytes);
+
+            final ByteBuffer symbolBuffer = ByteBuffer.wrap(symbolBytes).asReadOnlyBuffer();
 
             return Symbol.getSymbol(symbolBuffer, false);
         }
     }
 
     @Override
-    protected void describeMismatchSafely(Buffer item, Description mismatchDescription) {
+    protected void describeMismatchSafely(ByteBuffer item, Description mismatchDescription) {
         mismatchDescription.appendText("\nActual encoded form: ").appendValue(item);
 
         if (decodingErrorDescription != null) {
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyClient.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyClient.java
index ae2fbdcf..b90af803 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyClient.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyClient.java
@@ -14,438 +14,56 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.apache.qpid.protonj2.test.driver.netty;
 
 import java.io.IOException;
 import java.net.URI;
-import java.net.URISyntaxException;
 import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.qpid.protonj2.test.driver.ProtonTestClientOptions;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import io.netty5.bootstrap.Bootstrap;
-import io.netty5.buffer.Buffer;
-import io.netty5.buffer.BufferAllocator;
-import io.netty5.channel.Channel;
-import io.netty5.channel.ChannelFutureListeners;
-import io.netty5.channel.ChannelHandler;
-import io.netty5.channel.ChannelHandlerAdapter;
-import io.netty5.channel.ChannelHandlerContext;
-import io.netty5.channel.ChannelInitializer;
-import io.netty5.channel.ChannelOption;
-import io.netty5.channel.EventLoop;
-import io.netty5.channel.EventLoopGroup;
-import io.netty5.channel.MultithreadEventLoopGroup;
-import io.netty5.channel.nio.NioHandler;
-import io.netty5.channel.socket.nio.NioSocketChannel;
-import io.netty5.handler.codec.http.DefaultHttpContent;
-import io.netty5.handler.codec.http.FullHttpResponse;
-import io.netty5.handler.codec.http.HttpClientCodec;
-import io.netty5.handler.codec.http.HttpObjectAggregator;
-import io.netty5.handler.codec.http.headers.HttpHeaders;
-import io.netty5.handler.codec.http.websocketx.BinaryWebSocketFrame;
-import io.netty5.handler.codec.http.websocketx.CloseWebSocketFrame;
-import io.netty5.handler.codec.http.websocketx.ContinuationWebSocketFrame;
-import io.netty5.handler.codec.http.websocketx.PingWebSocketFrame;
-import io.netty5.handler.codec.http.websocketx.PongWebSocketFrame;
-import io.netty5.handler.codec.http.websocketx.TextWebSocketFrame;
-import io.netty5.handler.codec.http.websocketx.WebSocketClientHandshaker;
-import io.netty5.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
-import io.netty5.handler.codec.http.websocketx.WebSocketFrame;
-import io.netty5.handler.codec.http.websocketx.WebSocketVersion;
-import io.netty5.handler.logging.LoggingHandler;
-import io.netty5.handler.ssl.SslHandler;
-import io.netty5.util.concurrent.Future;
-import io.netty5.util.concurrent.FutureListener;
 
 /**
- * Self contained Netty client implementation that provides a base for more
- * complex client implementations to use as the IO layer.
+ * Base API for Netty Client implementations
  */
-public abstract class NettyClient implements AutoCloseable {
-
-    private static final Logger LOG = LoggerFactory.getLogger(NettyClient.class);
-
-    private static final String AMQP_SUB_PROTOCOL = "amqp";
-
-    private Bootstrap bootstrap;
-    private EventLoopGroup group;
-    private Channel channel;
-    private String host;
-    private int port;
-    protected volatile IOException failureCause;
-    private final ProtonTestClientOptions options;
-    private volatile SslHandler sslHandler;
-    protected final AtomicBoolean connected = new AtomicBoolean();
-    protected final AtomicBoolean closed = new AtomicBoolean();
-    protected final CountDownLatch connectedLatch = new CountDownLatch(1);
-
-    public NettyClient(ProtonTestClientOptions options) {
-        this.options = options;
-    }
-
-    @Override
-    public void close() throws Exception {
-        if (closed.compareAndSet(false, true)) {
-            connected.set(false);
-            connectedLatch.countDown();
-            if (channel != null) {
-                try {
-                    if (!channel.close().asStage().await(10, TimeUnit.SECONDS)) {
-                        LOG.info("Channel close timed out waiting for result");
-                    }
-                } catch (InterruptedException e) {
-                    Thread.interrupted();
-                    LOG.debug("Close of channel interrupted while awaiting result");
-                }
-            }
-        }
-    }
-
-    public void connect(String host, int port) throws IOException {
-        if (closed.get()) {
-            throw new IllegalStateException("Netty client has already been closed");
-        }
-
-        if (host == null || host.isEmpty()) {
-            throw new IllegalArgumentException("Transport host value cannot be null");
-        }
-
-        this.host = host;
-
-        if (port > 0) {
-            this.port = port;
-        } else {
-            if (options.isSecure()) {
-                this.port = ProtonTestClientOptions.DEFAULT_SSL_PORT;
-            } else {
-                this.port = ProtonTestClientOptions.DEFAULT_TCP_PORT;
-            }
-        }
-
-        group = new MultithreadEventLoopGroup(1, NioHandler.newFactory());
-        bootstrap = new Bootstrap().channel(NioSocketChannel.class).group(group);
-        bootstrap.handler(new ChannelInitializer<Channel>() {
-            @Override
-            public void initChannel(Channel transportChannel) throws Exception {
-                channel = transportChannel;
-                configureChannel(transportChannel);
-            }
-        });
-
-        configureNetty(bootstrap, options);
-
-        bootstrap.connect(host, port).addListener(channel, ChannelFutureListeners.FIRE_EXCEPTION_ON_FAILURE);
-        try {
-            connectedLatch.await();
-        } catch (InterruptedException e) {
-            Thread.interrupted();
-        }
-
-        if (!connected.get()) {
-            if (failureCause != null) {
-                throw failureCause;
-            } else {
-                throw new IOException("Netty client was closed before a connection was established.");
-            }
-        }
-    }
-
-    public EventLoop eventLoop() {
-        if (channel == null || !channel.isActive()) {
-            throw new IllegalStateException("Channel is not connected or has closed");
-        }
-
-        return channel.executor();
-    }
-
-    public void write(ByteBuffer buffer) {
-        if (channel == null || !channel.isActive()) {
-            throw new IllegalStateException("Channel is not connected or has closed");
-        }
-
-        channel.writeAndFlush(BufferAllocator.onHeapUnpooled().copyOf(buffer).makeReadOnly());
-    }
-
-    public boolean isConnected() {
-        return connected.get();
-    }
-
-    public boolean isSecure() {
-        return options.isSecure();
-    }
-
-    public URI getRemoteURI() {
-        if (host != null) {
-            try {
-                if (options.isUseWebSockets()) {
-                    return new URI(options.isSecure() ? "wss" : "ws", null, host, port, options.getWebSocketPath(), null, null);
-                } else {
-                    return new URI(options.isSecure() ? "ssl" : "tcp", null, host, port, null, null, null);
-                }
-            } catch (URISyntaxException e) {
-            }
-        }
-
-        return null;
-    }
-
-    //----- Default implementation of Netty handler
-
-    protected class NettyClientInboundHandler implements ChannelHandler {
-
-        private final WebSocketClientHandshaker handshaker;
-        private Future<Void> handshakeTimeoutFuture;
-
-        public NettyClientInboundHandler() {
-            if (options.isUseWebSockets()) {
-                HttpHeaders headers = HttpHeaders.newHeaders();
-
-                options.getHttpHeaders().forEach((key, value) -> {
-                    headers.set(key, value);
-                });
-
-                handshaker = WebSocketClientHandshakerFactory.newHandshaker(
-                    getRemoteURI(), WebSocketVersion.V13, AMQP_SUB_PROTOCOL,
-                    true, headers, options.getWebSocketMaxFrameSize());
-            } else {
-                handshaker = null;
-            }
-        }
-
-        @Override
-        public final void channelRegistered(ChannelHandlerContext context) throws Exception {
-            channel = context.channel();
-        }
-
-        @Override
-        public void channelActive(ChannelHandlerContext context) throws Exception {
-            if (options.isUseWebSockets()) {
-                handshaker.handshake(context.channel());
-
-                handshakeTimeoutFuture = context.executor().schedule(()-> {
-                    LOG.trace("WebSocket handshake timed out! Channel is {}", context.channel());
-                    if (!handshaker.isHandshakeComplete()) {
-                        NettyClient.this.handleTransportFailure(channel, new IOException("WebSocket handshake timed out"));
-                    }
-                }, options.getConnectTimeout(), TimeUnit.MILLISECONDS);
-            }
-
-            // In the Secure case we need to let the handshake complete before we
-            // trigger the connected event.
-            if (!isSecure()) {
-                if (!options.isUseWebSockets()) {
-                    handleConnected(context.channel());
-                }
-            } else {
-                SslHandler sslHandler = context.pipeline().get(SslHandler.class);
-                sslHandler.handshakeFuture().addListener(new FutureListener<Channel>() {
-                    @Override
-                    public void operationComplete(Future<? extends Channel> future) throws Exception {
-                        if (future.isSuccess()) {
-                            LOG.trace("SSL Handshake has completed: {}", channel);
-                            if (!options.isUseWebSockets()) {
-                                handleConnected(channel);
-                            }
-                        } else {
-                            LOG.trace("SSL Handshake has failed: {}", channel);
-                            handleTransportFailure(channel, future.cause());
-                        }
-                    }
-                });
-            }
-        }
-
-        @Override
-        public void channelInactive(ChannelHandlerContext context) throws Exception {
-            if (handshakeTimeoutFuture != null) {
-                handshakeTimeoutFuture.cancel();
-            }
-
-            handleTransportFailure(context.channel(), new IOException("Remote closed connection unexpectedly"));
-        }
-
-        @Override
-        public void channelExceptionCaught(ChannelHandlerContext context, Throwable cause) throws Exception {
-            handleTransportFailure(context.channel(), cause);
-        }
-
-        @Override
-        public void channelRead(ChannelHandlerContext ctx, Object message) {
-            if (options.isUseWebSockets()) {
-                LOG.trace("New data read: incoming: {}", message);
-
-                Channel ch = ctx.channel();
-                if (!handshaker.isHandshakeComplete()) {
-                    handshaker.finishHandshake(ch, (FullHttpResponse) message);
-                    LOG.trace("WebSocket Client connected! {}", ctx.channel());
-                    // Now trigger super processing as we are really connected.
-                    if (handshakeTimeoutFuture.cancel()) {
-                        handleConnected(ch);
-                    }
-
-                    return;
-                }
-
-                // We shouldn't get this since we handle the handshake previously.
-                if (message instanceof FullHttpResponse) {
-                    FullHttpResponse response = (FullHttpResponse) message;
-                    throw new IllegalStateException(
-                        "Unexpected FullHttpResponse (getStatus=" + response.status() +
-                        ", content=" + response.payload().toString(StandardCharsets.UTF_8) + ')');
-                }
-
-                WebSocketFrame frame = (WebSocketFrame) message;
-                if (frame instanceof TextWebSocketFrame) {
-                    TextWebSocketFrame textFrame = (TextWebSocketFrame) frame;
-                    LOG.warn("WebSocket Client received message: " + textFrame.text());
-                    ctx.fireChannelExceptionCaught(new IOException("Received invalid frame over WebSocket."));
-                } else if (frame instanceof BinaryWebSocketFrame) {
-                    BinaryWebSocketFrame binaryFrame = (BinaryWebSocketFrame) frame;
-                    LOG.trace("WebSocket Client received data: {} bytes", binaryFrame.binaryData().readableBytes());
-                    ctx.fireChannelRead(binaryFrame.binaryData());
-                } else if (frame instanceof ContinuationWebSocketFrame) {
-                    ContinuationWebSocketFrame continuationFrame = (ContinuationWebSocketFrame) frame;
-                    LOG.trace("WebSocket Client received data continuation: {} bytes", continuationFrame.binaryData().readableBytes());
-                    ctx.fireChannelRead(continuationFrame.binaryData());
-                } else if (frame instanceof PingWebSocketFrame) {
-                    LOG.trace("WebSocket Client received ping, response with pong");
-                    ch.write(new PongWebSocketFrame(frame.binaryData()));
-                } else if (frame instanceof CloseWebSocketFrame) {
-                    LOG.trace("WebSocket Client received closing");
-                    ch.close();
-                }
-            } else {
-                ctx.fireChannelRead(message);
-            }
-        }
-    }
-
-    private class NettyClientOutboundHandler extends ChannelHandlerAdapter  {
-
-        @Override
-        public Future<Void> write(ChannelHandlerContext ctx, Object msg) {
-            LOG.trace("NettyServerHandler: Channel write: {}", msg);
-            if (options.isUseWebSockets() && msg instanceof Buffer) {
-                if (options.isFragmentWrites()) {
-                    Buffer orig = (Buffer) msg;
-                    int origIndex = orig.readerOffset();
-                    int split = orig.readableBytes()/2;
-
-                    Buffer part1 = orig.copy(origIndex, split);
-                    LOG.trace("NettyClientOutboundHandler: Part1: {}", part1);
-                    orig.readerOffset(origIndex + split);
-                    LOG.trace("NettyClientOutboundHandler: Part2: {}", orig);
-
-                    BinaryWebSocketFrame frame1 = new BinaryWebSocketFrame(false, 0, part1);
-                    ctx.writeAndFlush(frame1);
-                    ContinuationWebSocketFrame frame2 = new ContinuationWebSocketFrame(true, 0, orig);
-                    return ctx.write(frame2);
-                } else {
-                    BinaryWebSocketFrame frame = new BinaryWebSocketFrame((Buffer) msg);
-                    return ctx.write(frame);
-                }
-            } else {
-                return ctx.write(msg);
-            }
-        }
-    }
-
-    //----- Internal Client implementation API
-
-    protected abstract ChannelHandler getClientHandler();
-
-    protected EventLoop getEventLoop() {
-        if (channel == null || !channel.isActive()) {
-            throw new IllegalStateException("Channel is not connected or has closed");
-        }
-
-        return channel.executor();
-    }
-
-    protected SslHandler getSslHandler() {
-        return sslHandler;
-    }
-
-    private void configureChannel(final Channel channel) throws Exception {
-        if (isSecure()) {
-            final SslHandler sslHandler;
-            try {
-                sslHandler = SslSupport.createClientSslHandler(getRemoteURI(), options);
-            } catch (Exception ex) {
-                LOG.warn("Error during initialization of channel from SSL Handler creation:");
-                handleTransportFailure(channel, ex);
-                throw new IOException(ex);
-            }
-
-            channel.pipeline().addLast("ssl", sslHandler);
-        }
-
-        if (options.isTraceBytes()) {
-            channel.pipeline().addLast("logger", new LoggingHandler(getClass()));
-        }
-
-        if (options.isUseWebSockets()) {
-            channel.pipeline().addLast(new HttpClientCodec());
-            channel.pipeline().addLast(new HttpObjectAggregator<DefaultHttpContent>(8192));
-        }
-
-        channel.pipeline().addLast(new NettyClientOutboundHandler());
-        channel.pipeline().addLast(new NettyClientInboundHandler());
-        channel.pipeline().addLast(getClientHandler());
-    }
-
-    private void configureNetty(Bootstrap bootstrap, ProtonTestClientOptions options) {
-        bootstrap.option(ChannelOption.TCP_NODELAY, options.isTcpNoDelay());
-        bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, options.getConnectTimeout());
-        bootstrap.option(ChannelOption.SO_KEEPALIVE, options.isTcpKeepAlive());
-        bootstrap.option(ChannelOption.SO_LINGER, options.getSoLinger());
-
-        if (options.getSendBufferSize() != -1) {
-            bootstrap.option(ChannelOption.SO_SNDBUF, options.getSendBufferSize());
-        }
-
-        if (options.getReceiveBufferSize() != -1) {
-            bootstrap.option(ChannelOption.SO_RCVBUF, options.getReceiveBufferSize());
-        }
-
-        if (options.getTrafficClass() != -1) {
-            bootstrap.option(ChannelOption.IP_TOS, options.getTrafficClass());
-        }
-
-        if (options.getLocalAddress() != null || options.getLocalPort() != 0) {
-            if (options.getLocalAddress() != null) {
-                bootstrap.localAddress(options.getLocalAddress(), options.getLocalPort());
-            } else {
-                bootstrap.localAddress(options.getLocalPort());
-            }
-        }
-    }
-
-    //----- Event Handlers which can be overridden in subclasses -------------//
-
-    protected void handleConnected(Channel connectedChannel) {
-        LOG.trace("Channel has become active! Channel is {}", connectedChannel);
-        channel = connectedChannel;
-        connected.set(true);
-        connectedLatch.countDown();
-    }
+public interface NettyClient extends AutoCloseable {
+
+    /**
+     * Connect to the specified host on the given port.
+     *
+     * @param host
+     * 		The host to connect to
+     * @param port
+     * 		The port on the host to connect to.
+     *
+     * @throws IOException if the connect fails immediately.
+     */
+    void connect(String host, int port) throws IOException;
+
+    /**
+     * @return a wrapped event loop that exposes common netty event loop APIs
+     */
+    NettyEventLoop eventLoop();
+
+    /**
+     * Writes the given {@link ByteBuffer} to the client IO layer.
+     *
+     * @param buffer
+     * 		The buffer of bytes to write.
+     */
+    void write(ByteBuffer buffer);
+
+    /**
+     * @return is the client currently connected.
+     */
+    boolean isConnected();
+
+    /**
+     * @return is the connection secure.
+     */
+    boolean isSecure();
+
+    /**
+     * @return a URI that represents the client connection.
+     */
+    URI getRemoteURI();
 
-    protected void handleTransportFailure(Channel failedChannel, Throwable cause) {
-        if (!closed.get()) {
-            LOG.trace("Channel indicates connection failure! Channel is {}", failedChannel);
-            failureCause = new IOException(cause);
-            channel = failedChannel;
-            connected.set(false);
-            connectedLatch.countDown();
-        } else {
-            LOG.trace("Closed Channel signalled that the channel ended: {}", channel);
-        }
-    }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyEventLoop.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyEventLoop.java
new file mode 100644
index 00000000..59fb4745
--- /dev/null
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyEventLoop.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.qpid.protonj2.test.driver.netty;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Abstraction around Netty event loops to allow change of Netty version
+ */
+public interface NettyEventLoop {
+
+    /**
+     * @return true if the current thread is the event loop thread.
+     */
+    boolean inEventLoop();
+
+    /**
+     * Run the given {@link Runnable} on the Netty event loop.
+     *
+     * @param runnable
+     * 		The {@link Runnable} to schedule to run on the event loop.
+     */
+    void execute(Runnable runnable);
+
+    /**
+     * Schedule the given {@link Runnable} to run on the event loop after the given delay
+     *
+     * @param runnable
+     * 		The {@link Runnable} to schedule to run on the event loop.
+     * @param delay
+     * 		The time delay before the {@link Runnable} should be executed.
+     * @param unit
+     * 		The units that the time delay is given in.
+     */
+    void schedule(Runnable runnable, int delay, TimeUnit unit);
+
+}
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyIOBuilder.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyIOBuilder.java
new file mode 100644
index 00000000..cfa728d9
--- /dev/null
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyIOBuilder.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.qpid.protonj2.test.driver.netty;
+
+import java.nio.ByteBuffer;
+import java.util.function.Consumer;
+
+import org.apache.qpid.protonj2.test.driver.ProtonTestClientOptions;
+import org.apache.qpid.protonj2.test.driver.ProtonTestServerOptions;
+import org.apache.qpid.protonj2.test.driver.netty.netty4.Netty4Client;
+import org.apache.qpid.protonj2.test.driver.netty.netty4.Netty4Server;
+import org.apache.qpid.protonj2.test.driver.netty.netty4.Netty4Support;
+import org.apache.qpid.protonj2.test.driver.netty.netty5.Netty5Client;
+import org.apache.qpid.protonj2.test.driver.netty.netty5.Netty5Server;
+import org.apache.qpid.protonj2.test.driver.netty.netty5.Netty5Support;
+
+/**
+ * An I/O context used to abstract the implementation of the IO layer in use.
+ */
+public interface NettyIOBuilder {
+
+    /**
+     * Create an NettyClient from the available options.
+     *
+     * @param options
+     * 		The {@link ProtonTestClientOptions} that configure the IO Transport the context creates.
+     * @param connectedHandler
+     * 		A handler that should be invoked when a connection attempt succeeds.
+     * @param inputHandler
+     * 		A {@link Consumer} that accept incoming {@link ByteBuffer} data from the remote.
+     *
+     * @return a new {@link NettyClient} from available options.
+     */
+    public static NettyClient createClient(ProtonTestClientOptions options, Runnable connectedHandler, Consumer<ByteBuffer> inputHandler) {
+        if (Netty4Support.isAvailable()) {
+            return new Netty4Client(options, connectedHandler, inputHandler);
+        } else if (Netty5Support.isAvailable()) {
+            return new Netty5Client(options, connectedHandler, inputHandler);
+        }
+
+        throw new UnsupportedOperationException("Netty not available on the class path");
+    }
+
+    /**
+     * Create an NettyServer from the available options.
+     *
+     * @param options
+     * 		The {@link ProtonTestServerOptions} that configure the IO Transport the context creates.
+     * @param connectedHandler
+     * 		A handler that should be invoked when a connection attempt succeeds.
+     * @param inputHandler
+     * 		A {@link Consumer} that accept incoming {@link ByteBuffer} data from the remote.
+     *
+     * @return a new {@link NettyServer} from available options.
+     */
+    public static NettyServer createServer(ProtonTestServerOptions options, Runnable connectedHandler, Consumer<ByteBuffer> inputHandler) {
+        if (Netty4Support.isAvailable()) {
+            return new Netty4Server(options, connectedHandler, inputHandler);
+        } else if (Netty5Support.isAvailable()) {
+            return new Netty5Server(options, connectedHandler, inputHandler);
+        }
+
+        throw new UnsupportedOperationException("Netty not available on the class path");
+    }
+}
\ No newline at end of file
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyServer.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyServer.java
index 26f16892..4291bee1 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyServer.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyServer.java
@@ -14,431 +14,58 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.apache.qpid.protonj2.test.driver.netty;
 
-import java.net.InetSocketAddress;
 import java.net.URI;
 import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.Objects;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLPeerUnverifiedException;
-
-import org.apache.qpid.protonj2.test.driver.ProtonTestServerOptions;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import io.netty5.bootstrap.ServerBootstrap;
-import io.netty5.buffer.Buffer;
-import io.netty5.buffer.BufferAllocator;
-import io.netty5.buffer.DefaultBufferAllocators;
-import io.netty5.channel.Channel;
-import io.netty5.channel.ChannelFutureListeners;
-import io.netty5.channel.ChannelHandler;
-import io.netty5.channel.ChannelHandlerAdapter;
-import io.netty5.channel.ChannelHandlerContext;
-import io.netty5.channel.ChannelInitializer;
-import io.netty5.channel.ChannelOption;
-import io.netty5.channel.EventLoop;
-import io.netty5.channel.EventLoopGroup;
-import io.netty5.channel.MultithreadEventLoopGroup;
-import io.netty5.channel.nio.NioHandler;
-import io.netty5.channel.socket.nio.NioServerSocketChannel;
-import io.netty5.handler.codec.http.DefaultFullHttpResponse;
-import io.netty5.handler.codec.http.DefaultHttpContent;
-import io.netty5.handler.codec.http.FullHttpRequest;
-import io.netty5.handler.codec.http.FullHttpResponse;
-import io.netty5.handler.codec.http.HttpObjectAggregator;
-import io.netty5.handler.codec.http.HttpResponseStatus;
-import io.netty5.handler.codec.http.HttpServerCodec;
-import io.netty5.handler.codec.http.HttpUtil;
-import io.netty5.handler.codec.http.HttpVersion;
-import io.netty5.handler.codec.http.websocketx.BinaryWebSocketFrame;
-import io.netty5.handler.codec.http.websocketx.ContinuationWebSocketFrame;
-import io.netty5.handler.codec.http.websocketx.WebSocketFrame;
-import io.netty5.handler.codec.http.websocketx.WebSocketServerHandshakeCompletionEvent;
-import io.netty5.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
-import io.netty5.handler.logging.LogLevel;
-import io.netty5.handler.logging.LoggingHandler;
-import io.netty5.handler.ssl.SslHandler;
-import io.netty5.util.concurrent.Future;
-import io.netty5.util.concurrent.FutureListener;
 
 /**
- * Base Server implementation used to create Netty based server implementations for
- * unit testing aspects of the client code.
+ *
  */
-public abstract class NettyServer implements AutoCloseable {
-
-    private static final Logger LOG = LoggerFactory.getLogger(NettyServer.class);
-
-    static final int PORT = Integer.parseInt(System.getProperty("port", "5672"));
-    static final String WEBSOCKET_PATH = "/";
-    static final int DEFAULT_MAX_FRAME_SIZE = 65535;
-
-    private EventLoopGroup bossGroup;
-    private EventLoopGroup workerGroup;
-    private Channel serverChannel;
-    private Channel clientChannel;
-    private final ProtonTestServerOptions options;
-    private int maxFrameSize = DEFAULT_MAX_FRAME_SIZE;
-    private String webSocketPath = WEBSOCKET_PATH;
-    private volatile SslHandler sslHandler;
-    private volatile WebSocketServerHandshakeCompletionEvent handshakeComplete;
-    private final CountDownLatch handshakeCompletion = new CountDownLatch(1);
-
-    private final AtomicBoolean started = new AtomicBoolean();
-
-    public NettyServer(ProtonTestServerOptions options) {
-        this.options = options;
-    }
-
-    public boolean isSecureServer() {
-        return options.isSecure();
-    }
-
-    public boolean isAcceptingConnections() {
-        return serverChannel != null && serverChannel.isOpen();
-    }
-
-    public boolean hasSecureConnection() {
-        return sslHandler != null;
-    }
-
-    public boolean hasClientConnection() {
-        return clientChannel != null && clientChannel.isOpen();
-    }
-
-    public int getClientPort() {
-        Objects.requireNonNull(clientChannel);
-        return (((InetSocketAddress) clientChannel.remoteAddress()).getPort());
-    }
-
-    public boolean isPeerVerified() {
-        try {
-            if (hasSecureConnection()) {
-                return sslHandler.engine().getSession().getPeerPrincipal() != null;
-            } else {
-                return false;
-            }
-        } catch (SSLPeerUnverifiedException unverified) {
-            return false;
-        }
-    }
-
-    public SSLEngine getConnectionSSLEngine() {
-        if (hasSecureConnection()) {
-            return sslHandler.engine();
-        } else {
-            return null;
-        }
-    }
-
-    public boolean isWebSocketServer() {
-        return options.isUseWebSockets();
-    }
-
-    public String getWebSocketPath() {
-        return webSocketPath;
-    }
-
-    public void setWebSocketPath(String webSocketPath) {
-        this.webSocketPath = webSocketPath;
-    }
-
-    public int getMaxFrameSize() {
-        return maxFrameSize;
-    }
-
-    public void setMaxFrameSize(int maxFrameSize) {
-        this.maxFrameSize = maxFrameSize;
-    }
-
-    public boolean awaitHandshakeCompletion(long delayMs) throws InterruptedException {
-        return handshakeCompletion.await(delayMs, TimeUnit.MILLISECONDS);
-    }
-
-    public WebSocketServerHandshakeCompletionEvent getHandshakeComplete() {
-        return handshakeComplete;
-    }
-
-    public URI getConnectionURI(String queryString) throws Exception {
-        if (!started.get()) {
-            throw new IllegalStateException("Cannot get URI of non-started server");
-        }
+public interface NettyServer extends AutoCloseable {
 
-        int port = getServerPort();
+    boolean isSecureServer();
 
-        String scheme;
-        String path;
+    boolean isAcceptingConnections();
 
-        if (isWebSocketServer()) {
-            if (isSecureServer()) {
-                scheme = "amqpwss";
-            } else {
-                scheme = "amqpws";
-            }
-        } else {
-            if (isSecureServer()) {
-                scheme = "amqps";
-            } else {
-                scheme = "amqp";
-            }
-        }
+    boolean hasSecureConnection();
 
-        if (isWebSocketServer()) {
-            path = getWebSocketPath();
-        } else {
-            path = null;
-        }
+    boolean hasClientConnection();
 
-        if (queryString != null && queryString.startsWith("?")) {
-            queryString = queryString.substring(1);
-        }
+    int getClientPort();
 
-        return new URI(scheme, null, "localhost", port, path, queryString, null);
-    }
+    boolean isPeerVerified();
 
-    public void start() throws Exception {
-        if (started.compareAndSet(false, true)) {
-            // Configure the server to basic NIO type channels
-            bossGroup = new MultithreadEventLoopGroup(1, NioHandler.newFactory());
-            workerGroup = new MultithreadEventLoopGroup(NioHandler.newFactory());
+    SSLEngine getConnectionSSLEngine();
 
-            ServerBootstrap server = new ServerBootstrap();
-            server.group(bossGroup, workerGroup);
-            server.channel(NioServerSocketChannel.class);
-            server.option(ChannelOption.SO_BACKLOG, 100);
-            server.handler(new LoggingHandler(LogLevel.INFO));
-            server.childHandler(new ChannelInitializer<Channel>() {
+    boolean isWebSocketServer();
 
-                @Override
-                public void initChannel(Channel ch) throws Exception {
-                    // Don't accept any new connections.
-                    serverChannel.close();
-                    // Now we know who the client is
-                    clientChannel = ch;
+    String getWebSocketPath();
 
-                    if (isSecureServer()) {
-                        ch.pipeline().addLast(sslHandler = SslSupport.createServerSslHandler(null, options));
-                    }
+    void setWebSocketPath(String webSocketPath);
 
-                    if (options.isUseWebSockets()) {
-                        ch.pipeline().addLast(new HttpServerCodec());
-                        ch.pipeline().addLast(new HttpObjectAggregator<DefaultHttpContent>(65536));
-                        ch.pipeline().addLast(new WebSocketServerProtocolHandler(getWebSocketPath(), "amqp", true, maxFrameSize));
-                    }
+    int getMaxFrameSize();
 
-                    ch.pipeline().addLast(new NettyServerOutboundHandler());
-                    ch.pipeline().addLast(new NettyServerInboundHandler());
-                    ch.pipeline().addLast(getServerHandler());
-                }
-            });
+    void setMaxFrameSize(int maxFrameSize);
 
-            // Start the server and then update the server port in case the configuration
-            // was such that the server chose a free port.
-            serverChannel = server.bind(options.getServerPort()).asStage().get();
-            options.setServerPort(((InetSocketAddress) serverChannel.localAddress()).getPort());
-        }
-    }
+    URI getConnectionURI(String queryString) throws Exception;
 
-    protected abstract ChannelHandler getServerHandler();
+    void start() throws Exception;
 
-    public void write(ByteBuffer frame) {
-        if (clientChannel == null || !clientChannel.isActive()) {
-            throw new IllegalStateException("Channel is not connected or has closed");
-        }
+    void write(ByteBuffer frame);
 
-        clientChannel.writeAndFlush(BufferAllocator.onHeapUnpooled().copyOf(frame).makeReadOnly());
-    }
+    NettyEventLoop eventLoop();
 
-    public EventLoop eventLoop() {
-        if (clientChannel == null || !clientChannel.isActive()) {
-            throw new IllegalStateException("Channel is not connected or has closed");
-        }
+    void stop() throws InterruptedException;
 
-        return clientChannel.executor();
-    }
-
-    public void stop() throws InterruptedException {
-        if (started.compareAndSet(true, false)) {
-            LOG.info("Syncing channel close");
-            serverChannel.close().asStage().sync();
-
-            if (clientChannel != null) {
-                try {
-                    if (!clientChannel.close().asStage().await(10, TimeUnit.SECONDS)) {
-                        LOG.info("Connected Client channel close timed out waiting for result");
-                    }
-                } catch (InterruptedException e) {
-                    Thread.interrupted();
-                    LOG.debug("Close of connected client channel interrupted while awaiting result");
-                }
-            }
-
-            // Shut down all event loops to terminate all threads.
-            int timeout = 100;
-            LOG.trace("Shutting down boss group");
-            bossGroup.shutdownGracefully(0, timeout, TimeUnit.MILLISECONDS).asStage().await(timeout, TimeUnit.MILLISECONDS);
-            LOG.trace("Boss group shut down");
-
-            LOG.trace("Shutting down worker group");
-            workerGroup.shutdownGracefully(0, timeout, TimeUnit.MILLISECONDS).asStage().await(timeout, TimeUnit.MILLISECONDS);
-            LOG.trace("Worker group shut down");
-        }
-    }
-
-    public void stopAsync() throws InterruptedException {
-        if (started.compareAndSet(true, false)) {
-            LOG.info("Closing channel asynchronously");
-            serverChannel.close().asStage().sync();
-
-            if (clientChannel != null) {
-                clientChannel.close();
-            }
-
-            // Shut down all event loops to terminate all threads.
-            int timeout = 100;
-            LOG.trace("Shutting down boss group asynchronously");
-            bossGroup.shutdownGracefully(0, timeout, TimeUnit.MILLISECONDS);
-
-            LOG.trace("Shutting down worker group asynchronously");
-            workerGroup.shutdownGracefully(0, timeout, TimeUnit.MILLISECONDS);
-        }
-    }
+    void stopAsync() throws InterruptedException;
 
     @Override
-    public void close() throws InterruptedException {
-        stop();
-    }
-
-    public int getServerPort() {
-        if (!started.get()) {
-            throw new IllegalStateException("Cannot get server port of non-started server");
-        }
-
-        return options.getServerPort();
-    }
-
-    private class NettyServerOutboundHandler extends ChannelHandlerAdapter  {
-
-        @Override
-        public Future<Void> write(ChannelHandlerContext ctx, Object msg) {
-            LOG.trace("NettyServerHandler: Channel write: {}", msg);
-            if (isWebSocketServer() && msg instanceof Buffer) {
-                if (options.isFragmentWrites()) {
-                    Buffer orig = (Buffer) msg;
-                    int origIndex = orig.readerOffset();
-                    int split = orig.readableBytes()/2;
-
-                    Buffer part1 = orig.copy(origIndex, split);
-                    LOG.trace("NettyServerHandler: Part1: {}", part1);
-                    orig.readerOffset(origIndex + split);
-                    LOG.trace("NettyServerHandler: Part2: {}", orig);
-
-                    BinaryWebSocketFrame frame1 = new BinaryWebSocketFrame(false, 0, part1);
-                    ctx.writeAndFlush(frame1);
-                    ContinuationWebSocketFrame frame2 = new ContinuationWebSocketFrame(true, 0, orig);
-                    return ctx.write(frame2);
-                } else {
-                    BinaryWebSocketFrame frame = new BinaryWebSocketFrame((Buffer) msg);
-                    return ctx.write(frame);
-                }
-            } else {
-                return ctx.write(msg);
-            }
-        }
-    }
-
-    private class NettyServerInboundHandler extends ChannelHandlerAdapter  {
-
-        @Override
-        public void channelInboundEvent(ChannelHandlerContext context, Object payload) {
-            if (payload instanceof WebSocketServerHandshakeCompletionEvent) {
-                handshakeComplete = (WebSocketServerHandshakeCompletionEvent) payload;
-                handshakeCompletion.countDown();
-            }
-        }
-
-        @Override
-        public void channelActive(final ChannelHandlerContext ctx) {
-            LOG.info("NettyServerHandler -> New active channel: {}", ctx.channel());
-            SslHandler handler = ctx.pipeline().get(SslHandler.class);
-            if (handler != null) {
-                handler.handshakeFuture().addListener(new FutureListener<Channel>() {
-                    @Override
-                    public void operationComplete(Future<? extends Channel> future) throws Exception {
-                        LOG.info("Server -> SSL handshake completed. Succeeded: {}", future.isSuccess());
-                        if (!future.isSuccess()) {
-                            ctx.close();
-                        }
-                    }
-                });
-            }
-
-            ctx.fireChannelActive();
-        }
-
-        @Override
-        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
-            LOG.info("NettyServerHandler: channel has gone inactive: {}", ctx.channel());
-            ctx.close();
-            ctx.fireChannelInactive();
-        }
-
-        @Override
-        public void channelRead(ChannelHandlerContext ctx, Object msg) {
-            LOG.trace("NettyServerHandler: Channel read: {}", msg);
-            if (msg instanceof WebSocketFrame) {
-                WebSocketFrame frame = (WebSocketFrame) msg;
-                ctx.fireChannelRead(frame.binaryData());
-            } else if (msg instanceof FullHttpRequest) {
-                // Reject anything not on the WebSocket path
-                FullHttpRequest request = (FullHttpRequest) msg;
-
-                sendHttpResponse(ctx, request,
-                    new DefaultFullHttpResponse(
-                        HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST, DefaultBufferAllocators.onHeapAllocator().allocate(0)));
-            } else {
-                // Forward anything else along to the next handler.
-                ctx.fireChannelRead(msg);
-            }
-        }
-
-        @Override
-        public void channelReadComplete(ChannelHandlerContext ctx) {
-            ctx.flush();
-        }
-
-        @Override
-        public void channelExceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
-            LOG.info("NettyServerHandler: NettyServerHandlerException caught on channel: {}", ctx.channel());
-            // Close the connection when an exception is raised.
-            cause.printStackTrace();
-            ctx.close();
-        }
-    }
-
-    private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest request, FullHttpResponse response) {
-        // Generate an error page if response getStatus code is not OK (200).
-        if (response.status().code() != 200) {
-            byte[] status = response.status().toString().getBytes(StandardCharsets.UTF_8);
-            response.payload().writeBytes(status);
-            HttpUtil.setContentLength(response, response.payload().readableBytes());
-        }
+    void close() throws InterruptedException;
 
-        // Send the response and close the connection if necessary.
-        Future<Void> f = ctx.channel().writeAndFlush(response);
-        if (!HttpUtil.isKeepAlive(request) || response.status().code() != 200) {
-            f.addListener(ctx.channel(), ChannelFutureListeners.CLOSE);
-        }
-    }
+    int getServerPort();
 
-    protected SslHandler getSslHandler() {
-        return sslHandler;
-    }
-}
+}
\ No newline at end of file
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyClient.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty4/Netty4Client.java
similarity index 68%
copy from protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyClient.java
copy to protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty4/Netty4Client.java
index ae2fbdcf..30a0207e 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyClient.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty4/Netty4Client.java
@@ -14,66 +14,74 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.qpid.protonj2.test.driver.netty;
+package org.apache.qpid.protonj2.test.driver.netty.netty4;
 
 import java.io.IOException;
+import java.lang.invoke.MethodHandles;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
+import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
 
 import org.apache.qpid.protonj2.test.driver.ProtonTestClientOptions;
+import org.apache.qpid.protonj2.test.driver.netty.NettyClient;
+import org.apache.qpid.protonj2.test.driver.netty.NettyEventLoop;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import io.netty5.bootstrap.Bootstrap;
-import io.netty5.buffer.Buffer;
-import io.netty5.buffer.BufferAllocator;
-import io.netty5.channel.Channel;
-import io.netty5.channel.ChannelFutureListeners;
-import io.netty5.channel.ChannelHandler;
-import io.netty5.channel.ChannelHandlerAdapter;
-import io.netty5.channel.ChannelHandlerContext;
-import io.netty5.channel.ChannelInitializer;
-import io.netty5.channel.ChannelOption;
-import io.netty5.channel.EventLoop;
-import io.netty5.channel.EventLoopGroup;
-import io.netty5.channel.MultithreadEventLoopGroup;
-import io.netty5.channel.nio.NioHandler;
-import io.netty5.channel.socket.nio.NioSocketChannel;
-import io.netty5.handler.codec.http.DefaultHttpContent;
-import io.netty5.handler.codec.http.FullHttpResponse;
-import io.netty5.handler.codec.http.HttpClientCodec;
-import io.netty5.handler.codec.http.HttpObjectAggregator;
-import io.netty5.handler.codec.http.headers.HttpHeaders;
-import io.netty5.handler.codec.http.websocketx.BinaryWebSocketFrame;
-import io.netty5.handler.codec.http.websocketx.CloseWebSocketFrame;
-import io.netty5.handler.codec.http.websocketx.ContinuationWebSocketFrame;
-import io.netty5.handler.codec.http.websocketx.PingWebSocketFrame;
-import io.netty5.handler.codec.http.websocketx.PongWebSocketFrame;
-import io.netty5.handler.codec.http.websocketx.TextWebSocketFrame;
-import io.netty5.handler.codec.http.websocketx.WebSocketClientHandshaker;
-import io.netty5.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
-import io.netty5.handler.codec.http.websocketx.WebSocketFrame;
-import io.netty5.handler.codec.http.websocketx.WebSocketVersion;
-import io.netty5.handler.logging.LoggingHandler;
-import io.netty5.handler.ssl.SslHandler;
-import io.netty5.util.concurrent.Future;
-import io.netty5.util.concurrent.FutureListener;
+import io.netty.bootstrap.Bootstrap;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.ChannelOutboundHandlerAdapter;
+import io.netty.channel.ChannelPromise;
+import io.netty.channel.EventLoop;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.codec.http.DefaultHttpHeaders;
+import io.netty.handler.codec.http.FullHttpResponse;
+import io.netty.handler.codec.http.HttpClientCodec;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.ContinuationWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
+import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
+import io.netty.handler.codec.http.websocketx.WebSocketFrame;
+import io.netty.handler.codec.http.websocketx.WebSocketVersion;
+import io.netty.handler.logging.LoggingHandler;
+import io.netty.handler.ssl.SslHandler;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.GenericFutureListener;
+import io.netty.util.concurrent.ScheduledFuture;
 
 /**
  * Self contained Netty client implementation that provides a base for more
  * complex client implementations to use as the IO layer.
  */
-public abstract class NettyClient implements AutoCloseable {
+public final class Netty4Client implements NettyClient {
 
-    private static final Logger LOG = LoggerFactory.getLogger(NettyClient.class);
+    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
     private static final String AMQP_SUB_PROTOCOL = "amqp";
 
+    private Netty4EventLoop eventLoop;
     private Bootstrap bootstrap;
     private EventLoopGroup group;
     private Channel channel;
@@ -86,8 +94,17 @@ public abstract class NettyClient implements AutoCloseable {
     protected final AtomicBoolean closed = new AtomicBoolean();
     protected final CountDownLatch connectedLatch = new CountDownLatch(1);
 
-    public NettyClient(ProtonTestClientOptions options) {
+    private final Consumer<ByteBuffer> inputConsumer;
+    private final Runnable connectedRunnable;
+
+    public Netty4Client(ProtonTestClientOptions options, Runnable connectedRunnable, Consumer<ByteBuffer> inputConsumer) {
+        Objects.requireNonNull(options);
+        Objects.requireNonNull(inputConsumer);
+        Objects.requireNonNull(connectedRunnable);
+
         this.options = options;
+        this.connectedRunnable = connectedRunnable;
+        this.inputConsumer = inputConsumer;
     }
 
     @Override
@@ -97,7 +114,7 @@ public abstract class NettyClient implements AutoCloseable {
             connectedLatch.countDown();
             if (channel != null) {
                 try {
-                    if (!channel.close().asStage().await(10, TimeUnit.SECONDS)) {
+                    if (!channel.close().await(10, TimeUnit.SECONDS)) {
                         LOG.info("Channel close timed out waiting for result");
                     }
                 } catch (InterruptedException e) {
@@ -108,6 +125,7 @@ public abstract class NettyClient implements AutoCloseable {
         }
     }
 
+    @Override
     public void connect(String host, int port) throws IOException {
         if (closed.get()) {
             throw new IllegalStateException("Netty client has already been closed");
@@ -129,19 +147,20 @@ public abstract class NettyClient implements AutoCloseable {
             }
         }
 
-        group = new MultithreadEventLoopGroup(1, NioHandler.newFactory());
+        group = new NioEventLoopGroup(1);
         bootstrap = new Bootstrap().channel(NioSocketChannel.class).group(group);
         bootstrap.handler(new ChannelInitializer<Channel>() {
             @Override
             public void initChannel(Channel transportChannel) throws Exception {
                 channel = transportChannel;
+                eventLoop = new Netty4EventLoop(channel.eventLoop());
                 configureChannel(transportChannel);
             }
         });
 
         configureNetty(bootstrap, options);
 
-        bootstrap.connect(host, port).addListener(channel, ChannelFutureListeners.FIRE_EXCEPTION_ON_FAILURE);
+        bootstrap.connect(host, port).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
         try {
             connectedLatch.await();
         } catch (InterruptedException e) {
@@ -157,30 +176,35 @@ public abstract class NettyClient implements AutoCloseable {
         }
     }
 
-    public EventLoop eventLoop() {
+    @Override
+    public NettyEventLoop eventLoop() {
         if (channel == null || !channel.isActive()) {
             throw new IllegalStateException("Channel is not connected or has closed");
         }
 
-        return channel.executor();
+        return eventLoop;
     }
 
+    @Override
     public void write(ByteBuffer buffer) {
         if (channel == null || !channel.isActive()) {
             throw new IllegalStateException("Channel is not connected or has closed");
         }
 
-        channel.writeAndFlush(BufferAllocator.onHeapUnpooled().copyOf(buffer).makeReadOnly());
+        channel.writeAndFlush(Unpooled.wrappedBuffer(buffer).asReadOnly());
     }
 
+    @Override
     public boolean isConnected() {
         return connected.get();
     }
 
+    @Override
     public boolean isSecure() {
         return options.isSecure();
     }
 
+    @Override
     public URI getRemoteURI() {
         if (host != null) {
             try {
@@ -198,14 +222,14 @@ public abstract class NettyClient implements AutoCloseable {
 
     //----- Default implementation of Netty handler
 
-    protected class NettyClientInboundHandler implements ChannelHandler {
+    protected class NettyClientInboundHandler extends ChannelInboundHandlerAdapter {
 
         private final WebSocketClientHandshaker handshaker;
-        private Future<Void> handshakeTimeoutFuture;
+        private ScheduledFuture<?> handshakeTimeoutFuture;
 
         public NettyClientInboundHandler() {
             if (options.isUseWebSockets()) {
-                HttpHeaders headers = HttpHeaders.newHeaders();
+                DefaultHttpHeaders headers = new DefaultHttpHeaders();
 
                 options.getHttpHeaders().forEach((key, value) -> {
                     headers.set(key, value);
@@ -232,7 +256,7 @@ public abstract class NettyClient implements AutoCloseable {
                 handshakeTimeoutFuture = context.executor().schedule(()-> {
                     LOG.trace("WebSocket handshake timed out! Channel is {}", context.channel());
                     if (!handshaker.isHandshakeComplete()) {
-                        NettyClient.this.handleTransportFailure(channel, new IOException("WebSocket handshake timed out"));
+                        Netty4Client.this.handleTransportFailure(channel, new IOException("WebSocket handshake timed out"));
                     }
                 }, options.getConnectTimeout(), TimeUnit.MILLISECONDS);
             }
@@ -245,9 +269,9 @@ public abstract class NettyClient implements AutoCloseable {
                 }
             } else {
                 SslHandler sslHandler = context.pipeline().get(SslHandler.class);
-                sslHandler.handshakeFuture().addListener(new FutureListener<Channel>() {
+                sslHandler.handshakeFuture().addListener(new GenericFutureListener<Future<Channel>>() {
                     @Override
-                    public void operationComplete(Future<? extends Channel> future) throws Exception {
+                    public void operationComplete(Future<Channel> future) throws Exception {
                         if (future.isSuccess()) {
                             LOG.trace("SSL Handshake has completed: {}", channel);
                             if (!options.isUseWebSockets()) {
@@ -261,18 +285,17 @@ public abstract class NettyClient implements AutoCloseable {
                 });
             }
         }
-
         @Override
         public void channelInactive(ChannelHandlerContext context) throws Exception {
             if (handshakeTimeoutFuture != null) {
-                handshakeTimeoutFuture.cancel();
+                handshakeTimeoutFuture.cancel(false);
             }
 
             handleTransportFailure(context.channel(), new IOException("Remote closed connection unexpectedly"));
         }
 
         @Override
-        public void channelExceptionCaught(ChannelHandlerContext context, Throwable cause) throws Exception {
+        public void exceptionCaught(ChannelHandlerContext context, Throwable cause) throws Exception {
             handleTransportFailure(context.channel(), cause);
         }
 
@@ -286,7 +309,7 @@ public abstract class NettyClient implements AutoCloseable {
                     handshaker.finishHandshake(ch, (FullHttpResponse) message);
                     LOG.trace("WebSocket Client connected! {}", ctx.channel());
                     // Now trigger super processing as we are really connected.
-                    if (handshakeTimeoutFuture.cancel()) {
+                    if (handshakeTimeoutFuture.cancel(false)) {
                         handleConnected(ch);
                     }
 
@@ -298,25 +321,25 @@ public abstract class NettyClient implements AutoCloseable {
                     FullHttpResponse response = (FullHttpResponse) message;
                     throw new IllegalStateException(
                         "Unexpected FullHttpResponse (getStatus=" + response.status() +
-                        ", content=" + response.payload().toString(StandardCharsets.UTF_8) + ')');
+                        ", content=" + response.content().toString(StandardCharsets.UTF_8) + ')');
                 }
 
                 WebSocketFrame frame = (WebSocketFrame) message;
                 if (frame instanceof TextWebSocketFrame) {
                     TextWebSocketFrame textFrame = (TextWebSocketFrame) frame;
                     LOG.warn("WebSocket Client received message: " + textFrame.text());
-                    ctx.fireChannelExceptionCaught(new IOException("Received invalid frame over WebSocket."));
+                    ctx.fireExceptionCaught(new IOException("Received invalid frame over WebSocket."));
                 } else if (frame instanceof BinaryWebSocketFrame) {
                     BinaryWebSocketFrame binaryFrame = (BinaryWebSocketFrame) frame;
-                    LOG.trace("WebSocket Client received data: {} bytes", binaryFrame.binaryData().readableBytes());
-                    ctx.fireChannelRead(binaryFrame.binaryData());
+                    LOG.trace("WebSocket Client received data: {} bytes", binaryFrame.content().readableBytes());
+                    ctx.fireChannelRead(binaryFrame.content());
                 } else if (frame instanceof ContinuationWebSocketFrame) {
                     ContinuationWebSocketFrame continuationFrame = (ContinuationWebSocketFrame) frame;
-                    LOG.trace("WebSocket Client received data continuation: {} bytes", continuationFrame.binaryData().readableBytes());
-                    ctx.fireChannelRead(continuationFrame.binaryData());
+                    LOG.trace("WebSocket Client received data continuation: {} bytes", continuationFrame.content().readableBytes());
+                    ctx.fireChannelRead(continuationFrame.content());
                 } else if (frame instanceof PingWebSocketFrame) {
                     LOG.trace("WebSocket Client received ping, response with pong");
-                    ch.write(new PongWebSocketFrame(frame.binaryData()));
+                    ch.write(new PongWebSocketFrame(frame.content()));
                 } else if (frame instanceof CloseWebSocketFrame) {
                     LOG.trace("WebSocket Client received closing");
                     ch.close();
@@ -327,46 +350,70 @@ public abstract class NettyClient implements AutoCloseable {
         }
     }
 
-    private class NettyClientOutboundHandler extends ChannelHandlerAdapter  {
+    private class NettyClientOutboundHandler extends ChannelOutboundHandlerAdapter  {
 
         @Override
-        public Future<Void> write(ChannelHandlerContext ctx, Object msg) {
+        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
             LOG.trace("NettyServerHandler: Channel write: {}", msg);
-            if (options.isUseWebSockets() && msg instanceof Buffer) {
+            if (options.isUseWebSockets() && msg instanceof ByteBuf) {
                 if (options.isFragmentWrites()) {
-                    Buffer orig = (Buffer) msg;
-                    int origIndex = orig.readerOffset();
+                    ByteBuf orig = (ByteBuf) msg;
+                    int origIndex = orig.readerIndex();
                     int split = orig.readableBytes()/2;
 
-                    Buffer part1 = orig.copy(origIndex, split);
+                    ByteBuf part1 = orig.copy(origIndex, split);
                     LOG.trace("NettyClientOutboundHandler: Part1: {}", part1);
-                    orig.readerOffset(origIndex + split);
+                    orig.readerIndex(origIndex + split);
                     LOG.trace("NettyClientOutboundHandler: Part2: {}", orig);
 
                     BinaryWebSocketFrame frame1 = new BinaryWebSocketFrame(false, 0, part1);
                     ctx.writeAndFlush(frame1);
                     ContinuationWebSocketFrame frame2 = new ContinuationWebSocketFrame(true, 0, orig);
-                    return ctx.write(frame2);
+                    ctx.write(frame2, promise);
                 } else {
-                    BinaryWebSocketFrame frame = new BinaryWebSocketFrame((Buffer) msg);
-                    return ctx.write(frame);
+                    BinaryWebSocketFrame frame = new BinaryWebSocketFrame((ByteBuf) msg);
+                    ctx.write(frame, promise);
                 }
             } else {
-                return ctx.write(msg);
+                ctx.write(msg, promise);
             }
         }
     }
 
     //----- Internal Client implementation API
 
-    protected abstract ChannelHandler getClientHandler();
+    protected ChannelHandler getClientHandler() {
+        return new SimpleChannelInboundHandler<ByteBuf>() {
+
+            @Override
+            public void channelActive(ChannelHandlerContext ctx) throws Exception {
+                connectedRunnable.run();
+                ctx.fireChannelActive();
+            }
+
+            @Override
+            protected void channelRead0(ChannelHandlerContext ctx, ByteBuf input) throws Exception {
+                LOG.trace("AMQP Test Client Channel read: {}", input);
+
+                // Driver processes new data and may produce output based on this.
+                try {
+                    final ByteBuffer copy = ByteBuffer.allocate(input.readableBytes());
+                    input.readBytes(copy);
+                    inputConsumer.accept(copy.flip().asReadOnlyBuffer());
+                } catch (Throwable e) {
+                    LOG.error("Closed AMQP Test client channel due to error: ", e);
+                    ctx.channel().close();
+                }
+            }
+        };
+    }
 
     protected EventLoop getEventLoop() {
         if (channel == null || !channel.isActive()) {
             throw new IllegalStateException("Channel is not connected or has closed");
         }
 
-        return channel.executor();
+        return channel.eventLoop();
     }
 
     protected SslHandler getSslHandler() {
@@ -393,7 +440,7 @@ public abstract class NettyClient implements AutoCloseable {
 
         if (options.isUseWebSockets()) {
             channel.pipeline().addLast(new HttpClientCodec());
-            channel.pipeline().addLast(new HttpObjectAggregator<DefaultHttpContent>(8192));
+            channel.pipeline().addLast(new HttpObjectAggregator(8192));
         }
 
         channel.pipeline().addLast(new NettyClientOutboundHandler());
@@ -445,7 +492,7 @@ public abstract class NettyClient implements AutoCloseable {
             connected.set(false);
             connectedLatch.countDown();
         } else {
-            LOG.trace("Closed Channel signalled that the channel ended: {}", channel);
+            LOG.trace("Closed Channel signaled that the channel ended: {}", channel);
         }
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/NullElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty4/Netty4EventLoop.java
similarity index 58%
copy from protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/NullElement.java
copy to protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty4/Netty4EventLoop.java
index 675c9da4..79015e4c 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/NullElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty4/Netty4EventLoop.java
@@ -14,37 +14,35 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.qpid.protonj2.test.driver.codec;
 
-import io.netty5.buffer.Buffer;
+package org.apache.qpid.protonj2.test.driver.netty.netty4;
 
-class NullElement extends AtomicElement<Void> {
+import java.util.concurrent.TimeUnit;
 
-    NullElement(Element<?> parent, Element<?> prev) {
-        super(parent, prev);
-    }
+import org.apache.qpid.protonj2.test.driver.netty.NettyEventLoop;
 
-    @Override
-    public int size() {
-        return isElementOfArray() ? 0 : 1;
+import io.netty.channel.EventLoop;
+
+public final class Netty4EventLoop implements NettyEventLoop {
+
+    private final EventLoop loop;
+
+    public Netty4EventLoop(EventLoop loop) {
+        this.loop = loop;
     }
 
     @Override
-    public Void getValue() {
-        return null;
+    public boolean inEventLoop() {
+        return loop.inEventLoop();
     }
 
     @Override
-    public Codec.DataType getDataType() {
-        return Codec.DataType.NULL;
+    public void execute(Runnable runnable) {
+        loop.execute(runnable);
     }
 
     @Override
-    public int encode(Buffer buffer) {
-        if (buffer.writableBytes() > 0 && !isElementOfArray()) {
-            buffer.writeByte((byte) 0x40);
-            return 1;
-        }
-        return 0;
+    public void schedule(Runnable runnable, int delay, TimeUnit unit) {
+        loop.schedule(runnable, delay, unit);
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyServer.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty4/Netty4Server.java
similarity index 65%
copy from protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyServer.java
copy to protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty4/Netty4Server.java
index 26f16892..cf7ba820 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyServer.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty4/Netty4Server.java
@@ -14,8 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.qpid.protonj2.test.driver.netty;
+package org.apache.qpid.protonj2.test.driver.netty.netty4;
 
+import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
+import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
+
+import java.lang.invoke.MethodHandles;
 import java.net.InetSocketAddress;
 import java.net.URI;
 import java.nio.ByteBuffer;
@@ -24,62 +28,64 @@ import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
 
 import javax.net.ssl.SSLEngine;
 import javax.net.ssl.SSLPeerUnverifiedException;
 
 import org.apache.qpid.protonj2.test.driver.ProtonTestServerOptions;
+import org.apache.qpid.protonj2.test.driver.netty.NettyEventLoop;
+import org.apache.qpid.protonj2.test.driver.netty.NettyServer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import io.netty5.bootstrap.ServerBootstrap;
-import io.netty5.buffer.Buffer;
-import io.netty5.buffer.BufferAllocator;
-import io.netty5.buffer.DefaultBufferAllocators;
-import io.netty5.channel.Channel;
-import io.netty5.channel.ChannelFutureListeners;
-import io.netty5.channel.ChannelHandler;
-import io.netty5.channel.ChannelHandlerAdapter;
-import io.netty5.channel.ChannelHandlerContext;
-import io.netty5.channel.ChannelInitializer;
-import io.netty5.channel.ChannelOption;
-import io.netty5.channel.EventLoop;
-import io.netty5.channel.EventLoopGroup;
-import io.netty5.channel.MultithreadEventLoopGroup;
-import io.netty5.channel.nio.NioHandler;
-import io.netty5.channel.socket.nio.NioServerSocketChannel;
-import io.netty5.handler.codec.http.DefaultFullHttpResponse;
-import io.netty5.handler.codec.http.DefaultHttpContent;
-import io.netty5.handler.codec.http.FullHttpRequest;
-import io.netty5.handler.codec.http.FullHttpResponse;
-import io.netty5.handler.codec.http.HttpObjectAggregator;
-import io.netty5.handler.codec.http.HttpResponseStatus;
-import io.netty5.handler.codec.http.HttpServerCodec;
-import io.netty5.handler.codec.http.HttpUtil;
-import io.netty5.handler.codec.http.HttpVersion;
-import io.netty5.handler.codec.http.websocketx.BinaryWebSocketFrame;
-import io.netty5.handler.codec.http.websocketx.ContinuationWebSocketFrame;
-import io.netty5.handler.codec.http.websocketx.WebSocketFrame;
-import io.netty5.handler.codec.http.websocketx.WebSocketServerHandshakeCompletionEvent;
-import io.netty5.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
-import io.netty5.handler.logging.LogLevel;
-import io.netty5.handler.logging.LoggingHandler;
-import io.netty5.handler.ssl.SslHandler;
-import io.netty5.util.concurrent.Future;
-import io.netty5.util.concurrent.FutureListener;
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.ChannelOutboundHandlerAdapter;
+import io.netty.channel.ChannelPromise;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.codec.http.DefaultFullHttpResponse;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.FullHttpResponse;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.HttpServerCodec;
+import io.netty.handler.codec.http.HttpUtil;
+import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.ContinuationWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.WebSocketFrame;
+import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
+import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler.HandshakeComplete;
+import io.netty.handler.logging.LogLevel;
+import io.netty.handler.logging.LoggingHandler;
+import io.netty.handler.ssl.SslHandler;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.GenericFutureListener;
 
 /**
  * Base Server implementation used to create Netty based server implementations for
  * unit testing aspects of the client code.
  */
-public abstract class NettyServer implements AutoCloseable {
+public final class Netty4Server implements NettyServer {
 
-    private static final Logger LOG = LoggerFactory.getLogger(NettyServer.class);
+    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
     static final int PORT = Integer.parseInt(System.getProperty("port", "5672"));
     static final String WEBSOCKET_PATH = "/";
     static final int DEFAULT_MAX_FRAME_SIZE = 65535;
 
+    private Netty4EventLoop eventLoop;
     private EventLoopGroup bossGroup;
     private EventLoopGroup workerGroup;
     private Channel serverChannel;
@@ -88,36 +94,51 @@ public abstract class NettyServer implements AutoCloseable {
     private int maxFrameSize = DEFAULT_MAX_FRAME_SIZE;
     private String webSocketPath = WEBSOCKET_PATH;
     private volatile SslHandler sslHandler;
-    private volatile WebSocketServerHandshakeCompletionEvent handshakeComplete;
+    private volatile HandshakeComplete handshakeComplete;
     private final CountDownLatch handshakeCompletion = new CountDownLatch(1);
 
     private final AtomicBoolean started = new AtomicBoolean();
 
-    public NettyServer(ProtonTestServerOptions options) {
+    private final Consumer<ByteBuffer> inputConsumer;
+    private final Runnable connectedRunnable;
+
+    public Netty4Server(ProtonTestServerOptions options, Runnable connectedRunnable, Consumer<ByteBuffer> inputConsumer) {
+        Objects.requireNonNull(options);
+        Objects.requireNonNull(inputConsumer);
+        Objects.requireNonNull(connectedRunnable);
+
         this.options = options;
+        this.connectedRunnable = connectedRunnable;
+        this.inputConsumer = inputConsumer;
     }
 
+    @Override
     public boolean isSecureServer() {
         return options.isSecure();
     }
 
+    @Override
     public boolean isAcceptingConnections() {
         return serverChannel != null && serverChannel.isOpen();
     }
 
+    @Override
     public boolean hasSecureConnection() {
         return sslHandler != null;
     }
 
+    @Override
     public boolean hasClientConnection() {
         return clientChannel != null && clientChannel.isOpen();
     }
 
+    @Override
     public int getClientPort() {
         Objects.requireNonNull(clientChannel);
         return (((InetSocketAddress) clientChannel.remoteAddress()).getPort());
     }
 
+    @Override
     public boolean isPeerVerified() {
         try {
             if (hasSecureConnection()) {
@@ -130,6 +151,7 @@ public abstract class NettyServer implements AutoCloseable {
         }
     }
 
+    @Override
     public SSLEngine getConnectionSSLEngine() {
         if (hasSecureConnection()) {
             return sslHandler.engine();
@@ -138,22 +160,27 @@ public abstract class NettyServer implements AutoCloseable {
         }
     }
 
+    @Override
     public boolean isWebSocketServer() {
         return options.isUseWebSockets();
     }
 
+    @Override
     public String getWebSocketPath() {
         return webSocketPath;
     }
 
+    @Override
     public void setWebSocketPath(String webSocketPath) {
         this.webSocketPath = webSocketPath;
     }
 
+    @Override
     public int getMaxFrameSize() {
         return maxFrameSize;
     }
 
+    @Override
     public void setMaxFrameSize(int maxFrameSize) {
         this.maxFrameSize = maxFrameSize;
     }
@@ -162,10 +189,11 @@ public abstract class NettyServer implements AutoCloseable {
         return handshakeCompletion.await(delayMs, TimeUnit.MILLISECONDS);
     }
 
-    public WebSocketServerHandshakeCompletionEvent getHandshakeComplete() {
+    public HandshakeComplete getHandshakeComplete() {
         return handshakeComplete;
     }
 
+    @Override
     public URI getConnectionURI(String queryString) throws Exception {
         if (!started.get()) {
             throw new IllegalStateException("Cannot get URI of non-started server");
@@ -203,11 +231,12 @@ public abstract class NettyServer implements AutoCloseable {
         return new URI(scheme, null, "localhost", port, path, queryString, null);
     }
 
+    @Override
     public void start() throws Exception {
         if (started.compareAndSet(false, true)) {
             // Configure the server to basic NIO type channels
-            bossGroup = new MultithreadEventLoopGroup(1, NioHandler.newFactory());
-            workerGroup = new MultithreadEventLoopGroup(NioHandler.newFactory());
+            bossGroup = new NioEventLoopGroup(1);
+            workerGroup = new NioEventLoopGroup();
 
             ServerBootstrap server = new ServerBootstrap();
             server.group(bossGroup, workerGroup);
@@ -223,13 +252,15 @@ public abstract class NettyServer implements AutoCloseable {
                     // Now we know who the client is
                     clientChannel = ch;
 
+                    eventLoop = new Netty4EventLoop(ch.eventLoop());
+
                     if (isSecureServer()) {
                         ch.pipeline().addLast(sslHandler = SslSupport.createServerSslHandler(null, options));
                     }
 
                     if (options.isUseWebSockets()) {
                         ch.pipeline().addLast(new HttpServerCodec());
-                        ch.pipeline().addLast(new HttpObjectAggregator<DefaultHttpContent>(65536));
+                        ch.pipeline().addLast(new HttpObjectAggregator(65536));
                         ch.pipeline().addLast(new WebSocketServerProtocolHandler(getWebSocketPath(), "amqp", true, maxFrameSize));
                     }
 
@@ -241,37 +272,84 @@ public abstract class NettyServer implements AutoCloseable {
 
             // Start the server and then update the server port in case the configuration
             // was such that the server chose a free port.
-            serverChannel = server.bind(options.getServerPort()).asStage().get();
+            serverChannel = server.bind(options.getServerPort()).sync().channel();
             options.setServerPort(((InetSocketAddress) serverChannel.localAddress()).getPort());
         }
     }
 
-    protected abstract ChannelHandler getServerHandler();
+    protected ChannelHandler getServerHandler() {
+        return new SimpleChannelInboundHandler<ByteBuf>() {
+
+            @Override
+            public void channelActive(ChannelHandlerContext ctx) throws Exception {
+                connectedRunnable.run();
+                ctx.fireChannelActive();
+            }
+
+            @Override
+            protected void channelRead0(ChannelHandlerContext ctx, ByteBuf input) throws Exception {
+                LOG.trace("AMQP Test Server Channel read: {}", input);
+
+                // Driver processes new data and may produce output based on this.
+                try {
+                    final ByteBuffer copy = ByteBuffer.allocate(input.readableBytes());
+                    input.readBytes(copy);
+                    inputConsumer.accept(copy.flip().asReadOnlyBuffer());
+                } catch (Throwable e) {
+                    LOG.error("Closed AMQP Test server channel due to error: ", e);
+                    ctx.channel().close();
+                }
+            }
+        };
+    }
 
+    @Override
     public void write(ByteBuffer frame) {
         if (clientChannel == null || !clientChannel.isActive()) {
             throw new IllegalStateException("Channel is not connected or has closed");
         }
 
-        clientChannel.writeAndFlush(BufferAllocator.onHeapUnpooled().copyOf(frame).makeReadOnly());
+        clientChannel.writeAndFlush(Unpooled.wrappedBuffer(frame), clientChannel.voidPromise());
     }
 
-    public EventLoop eventLoop() {
+    @Override
+    public NettyEventLoop eventLoop() {
         if (clientChannel == null || !clientChannel.isActive()) {
             throw new IllegalStateException("Channel is not connected or has closed");
         }
 
-        return clientChannel.executor();
+        return eventLoop;
+    }
+
+    @Override
+    public void stopAsync() throws InterruptedException {
+        if (started.compareAndSet(true, false)) {
+            LOG.info("Closing channel asynchronously");
+            serverChannel.close().sync();
+
+            if (clientChannel != null) {
+                clientChannel.close();
+            }
+
+            // Shut down all event loops to terminate all threads.
+            int timeout = 100;
+            LOG.trace("Shutting down boss group asynchronously");
+            bossGroup.shutdownGracefully(0, timeout, TimeUnit.MILLISECONDS);
+
+            LOG.trace("Shutting down worker group asynchronously");
+            workerGroup.shutdownGracefully(0, timeout, TimeUnit.MILLISECONDS);
+        }
     }
 
+    @Override
     public void stop() throws InterruptedException {
         if (started.compareAndSet(true, false)) {
             LOG.info("Syncing channel close");
-            serverChannel.close().asStage().sync();
+            serverChannel.close().syncUninterruptibly();
 
             if (clientChannel != null) {
                 try {
-                    if (!clientChannel.close().asStage().await(10, TimeUnit.SECONDS)) {
+                    if (!clientChannel.close().await(10, TimeUnit.SECONDS)) {
                         LOG.info("Connected Client channel close timed out waiting for result");
                     }
                 } catch (InterruptedException e) {
@@ -283,39 +361,21 @@ public abstract class NettyServer implements AutoCloseable {
             // Shut down all event loops to terminate all threads.
             int timeout = 100;
             LOG.trace("Shutting down boss group");
-            bossGroup.shutdownGracefully(0, timeout, TimeUnit.MILLISECONDS).asStage().await(timeout, TimeUnit.MILLISECONDS);
+            bossGroup.shutdownGracefully(0, timeout, TimeUnit.MILLISECONDS).awaitUninterruptibly(timeout);
             LOG.trace("Boss group shut down");
 
             LOG.trace("Shutting down worker group");
-            workerGroup.shutdownGracefully(0, timeout, TimeUnit.MILLISECONDS).asStage().await(timeout, TimeUnit.MILLISECONDS);
+            workerGroup.shutdownGracefully(0, timeout, TimeUnit.MILLISECONDS).awaitUninterruptibly(timeout);
             LOG.trace("Worker group shut down");
         }
     }
 
-    public void stopAsync() throws InterruptedException {
-        if (started.compareAndSet(true, false)) {
-            LOG.info("Closing channel asynchronously");
-            serverChannel.close().asStage().sync();
-
-            if (clientChannel != null) {
-                clientChannel.close();
-            }
-
-            // Shut down all event loops to terminate all threads.
-            int timeout = 100;
-            LOG.trace("Shutting down boss group asynchronously");
-            bossGroup.shutdownGracefully(0, timeout, TimeUnit.MILLISECONDS);
-
-            LOG.trace("Shutting down worker group asynchronously");
-            workerGroup.shutdownGracefully(0, timeout, TimeUnit.MILLISECONDS);
-        }
-    }
-
     @Override
     public void close() throws InterruptedException {
         stop();
     }
 
+    @Override
     public int getServerPort() {
         if (!started.get()) {
             throw new IllegalStateException("Cannot get server port of non-started server");
@@ -324,42 +384,42 @@ public abstract class NettyServer implements AutoCloseable {
         return options.getServerPort();
     }
 
-    private class NettyServerOutboundHandler extends ChannelHandlerAdapter  {
+    private class NettyServerOutboundHandler extends ChannelOutboundHandlerAdapter  {
 
         @Override
-        public Future<Void> write(ChannelHandlerContext ctx, Object msg) {
+        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
             LOG.trace("NettyServerHandler: Channel write: {}", msg);
-            if (isWebSocketServer() && msg instanceof Buffer) {
+            if (isWebSocketServer() && msg instanceof ByteBuf) {
                 if (options.isFragmentWrites()) {
-                    Buffer orig = (Buffer) msg;
-                    int origIndex = orig.readerOffset();
+                    ByteBuf orig = (ByteBuf) msg;
+                    int origIndex = orig.readerIndex();
                     int split = orig.readableBytes()/2;
 
-                    Buffer part1 = orig.copy(origIndex, split);
+                    ByteBuf part1 = orig.copy(origIndex, split);
                     LOG.trace("NettyServerHandler: Part1: {}", part1);
-                    orig.readerOffset(origIndex + split);
+                    orig.readerIndex(origIndex + split);
                     LOG.trace("NettyServerHandler: Part2: {}", orig);
 
                     BinaryWebSocketFrame frame1 = new BinaryWebSocketFrame(false, 0, part1);
                     ctx.writeAndFlush(frame1);
                     ContinuationWebSocketFrame frame2 = new ContinuationWebSocketFrame(true, 0, orig);
-                    return ctx.write(frame2);
+                    ctx.write(frame2, promise);
                 } else {
-                    BinaryWebSocketFrame frame = new BinaryWebSocketFrame((Buffer) msg);
-                    return ctx.write(frame);
+                    BinaryWebSocketFrame frame = new BinaryWebSocketFrame((ByteBuf) msg);
+                    ctx.write(frame, promise);
                 }
             } else {
-                return ctx.write(msg);
+                ctx.write(msg, promise);
             }
         }
     }
 
-    private class NettyServerInboundHandler extends ChannelHandlerAdapter  {
+    private class NettyServerInboundHandler extends ChannelInboundHandlerAdapter  {
 
         @Override
-        public void channelInboundEvent(ChannelHandlerContext context, Object payload) {
-            if (payload instanceof WebSocketServerHandshakeCompletionEvent) {
-                handshakeComplete = (WebSocketServerHandshakeCompletionEvent) payload;
+        public void userEventTriggered(ChannelHandlerContext context, Object payload) {
+            if (payload instanceof HandshakeComplete) {
+                handshakeComplete = (HandshakeComplete) payload;
                 handshakeCompletion.countDown();
             }
         }
@@ -369,9 +429,9 @@ public abstract class NettyServer implements AutoCloseable {
             LOG.info("NettyServerHandler -> New active channel: {}", ctx.channel());
             SslHandler handler = ctx.pipeline().get(SslHandler.class);
             if (handler != null) {
-                handler.handshakeFuture().addListener(new FutureListener<Channel>() {
+                handler.handshakeFuture().addListener(new GenericFutureListener<Future<Channel>>() {
                     @Override
-                    public void operationComplete(Future<? extends Channel> future) throws Exception {
+                    public void operationComplete(Future<Channel> future) throws Exception {
                         LOG.info("Server -> SSL handshake completed. Succeeded: {}", future.isSuccess());
                         if (!future.isSuccess()) {
                             ctx.close();
@@ -395,14 +455,11 @@ public abstract class NettyServer implements AutoCloseable {
             LOG.trace("NettyServerHandler: Channel read: {}", msg);
             if (msg instanceof WebSocketFrame) {
                 WebSocketFrame frame = (WebSocketFrame) msg;
-                ctx.fireChannelRead(frame.binaryData());
+                ctx.fireChannelRead(frame.content());
             } else if (msg instanceof FullHttpRequest) {
                 // Reject anything not on the WebSocket path
                 FullHttpRequest request = (FullHttpRequest) msg;
-
-                sendHttpResponse(ctx, request,
-                    new DefaultFullHttpResponse(
-                        HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST, DefaultBufferAllocators.onHeapAllocator().allocate(0)));
+                sendHttpResponse(ctx, request, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST));
             } else {
                 // Forward anything else along to the next handler.
                 ctx.fireChannelRead(msg);
@@ -415,7 +472,7 @@ public abstract class NettyServer implements AutoCloseable {
         }
 
         @Override
-        public void channelExceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
             LOG.info("NettyServerHandler: NettyServerHandlerException caught on channel: {}", ctx.channel());
             // Close the connection when an exception is raised.
             cause.printStackTrace();
@@ -426,15 +483,16 @@ public abstract class NettyServer implements AutoCloseable {
     private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest request, FullHttpResponse response) {
         // Generate an error page if response getStatus code is not OK (200).
         if (response.status().code() != 200) {
-            byte[] status = response.status().toString().getBytes(StandardCharsets.UTF_8);
-            response.payload().writeBytes(status);
-            HttpUtil.setContentLength(response, response.payload().readableBytes());
+            ByteBuf buf = Unpooled.copiedBuffer(response.status().toString(), StandardCharsets.UTF_8);
+            response.content().writeBytes(buf);
+            buf.release();
+            HttpUtil.setContentLength(response, response.content().readableBytes());
         }
 
         // Send the response and close the connection if necessary.
-        Future<Void> f = ctx.channel().writeAndFlush(response);
+        ChannelFuture f = ctx.channel().writeAndFlush(response);
         if (!HttpUtil.isKeepAlive(request) || response.status().code() != 200) {
-            f.addListener(ctx.channel(), ChannelFutureListeners.CLOSE);
+            f.addListener(ChannelFutureListener.CLOSE);
         }
     }
 
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/NullElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty4/Netty4Support.java
similarity index 52%
copy from protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/NullElement.java
copy to protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty4/Netty4Support.java
index 675c9da4..2b82e725 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/NullElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty4/Netty4Support.java
@@ -14,37 +14,35 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.qpid.protonj2.test.driver.codec;
 
-import io.netty5.buffer.Buffer;
+package org.apache.qpid.protonj2.test.driver.netty.netty4;
 
-class NullElement extends AtomicElement<Void> {
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-    NullElement(Element<?> parent, Element<?> prev) {
-        super(parent, prev);
-    }
+import io.netty.buffer.Unpooled;
 
-    @Override
-    public int size() {
-        return isElementOfArray() ? 0 : 1;
-    }
+/**
+ * Support class used to detect if Netty 4 is available on the class path.
+ */
+public final class Netty4Support {
 
-    @Override
-    public Void getValue() {
-        return null;
-    }
+    private static final Logger LOG = LoggerFactory.getLogger(Netty4Support.class);
 
-    @Override
-    public Codec.DataType getDataType() {
-        return Codec.DataType.NULL;
+    private static final Throwable UNAVAILABILITY_CAUSE;
+    static {
+        Throwable cause = null;
+        try {
+            Unpooled.wrappedBuffer(new byte[0]);
+        } catch (Throwable ex) {
+            LOG.debug("Netty 4 not available for use.");
+            cause = ex;
+        }
+
+        UNAVAILABILITY_CAUSE = cause;
     }
 
-    @Override
-    public int encode(Buffer buffer) {
-        if (buffer.writableBytes() > 0 && !isElementOfArray()) {
-            buffer.writeByte((byte) 0x40);
-            return 1;
-        }
-        return 0;
+    public static final boolean isAvailable() {
+        return UNAVAILABILITY_CAUSE == null;
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/SslSupport.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty4/SslSupport.java
similarity index 98%
copy from protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/SslSupport.java
copy to protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty4/SslSupport.java
index 256122ee..d88b0f51 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/SslSupport.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty4/SslSupport.java
@@ -14,11 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.qpid.protonj2.test.driver.netty;
+package org.apache.qpid.protonj2.test.driver.netty.netty4;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.InputStream;
+import java.lang.invoke.MethodHandles;
 import java.net.URI;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
@@ -38,18 +39,19 @@ import javax.net.ssl.X509ExtendedKeyManager;
 
 import org.apache.qpid.protonj2.test.driver.ProtonTestClientOptions;
 import org.apache.qpid.protonj2.test.driver.ProtonTestServerOptions;
+import org.apache.qpid.protonj2.test.driver.netty.X509AliasKeyManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import io.netty5.handler.ssl.SslHandler;
-import io.netty5.handler.ssl.util.InsecureTrustManagerFactory;
+import io.netty.handler.ssl.SslHandler;
+import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
 
 /**
  * Static class that provides various utility methods used by Transport implementations.
  */
 public class SslSupport {
 
-    private static final Logger LOG = LoggerFactory.getLogger(SslSupport.class);
+    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
     /**
      * Creates a Netty SslHandler instance for use in client instances that require
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyClient.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty5/Netty5Client.java
similarity index 88%
copy from protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyClient.java
copy to protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty5/Netty5Client.java
index ae2fbdcf..61134119 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyClient.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty5/Netty5Client.java
@@ -14,18 +14,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.qpid.protonj2.test.driver.netty;
+package org.apache.qpid.protonj2.test.driver.netty.netty5;
 
 import java.io.IOException;
+import java.lang.invoke.MethodHandles;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
+import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
 
 import org.apache.qpid.protonj2.test.driver.ProtonTestClientOptions;
+import org.apache.qpid.protonj2.test.driver.netty.NettyClient;
+import org.apache.qpid.protonj2.test.driver.netty.NettyEventLoop;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -42,6 +47,7 @@ import io.netty5.channel.ChannelOption;
 import io.netty5.channel.EventLoop;
 import io.netty5.channel.EventLoopGroup;
 import io.netty5.channel.MultithreadEventLoopGroup;
+import io.netty5.channel.SimpleChannelInboundHandler;
 import io.netty5.channel.nio.NioHandler;
 import io.netty5.channel.socket.nio.NioSocketChannel;
 import io.netty5.handler.codec.http.DefaultHttpContent;
@@ -68,12 +74,13 @@ import io.netty5.util.concurrent.FutureListener;
  * Self contained Netty client implementation that provides a base for more
  * complex client implementations to use as the IO layer.
  */
-public abstract class NettyClient implements AutoCloseable {
+public final class Netty5Client implements NettyClient {
 
-    private static final Logger LOG = LoggerFactory.getLogger(NettyClient.class);
+    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
     private static final String AMQP_SUB_PROTOCOL = "amqp";
 
+    private Netty5EventLoop eventLoop;
     private Bootstrap bootstrap;
     private EventLoopGroup group;
     private Channel channel;
@@ -86,8 +93,17 @@ public abstract class NettyClient implements AutoCloseable {
     protected final AtomicBoolean closed = new AtomicBoolean();
     protected final CountDownLatch connectedLatch = new CountDownLatch(1);
 
-    public NettyClient(ProtonTestClientOptions options) {
+    private final Consumer<ByteBuffer> inputConsumer;
+    private final Runnable connectedRunnable;
+
+    public Netty5Client(ProtonTestClientOptions options, Runnable connectedRunnable, Consumer<ByteBuffer> inputConsumer) {
+        Objects.requireNonNull(options);
+        Objects.requireNonNull(inputConsumer);
+        Objects.requireNonNull(connectedRunnable);
+
         this.options = options;
+        this.connectedRunnable = connectedRunnable;
+        this.inputConsumer = inputConsumer;
     }
 
     @Override
@@ -108,6 +124,7 @@ public abstract class NettyClient implements AutoCloseable {
         }
     }
 
+    @Override
     public void connect(String host, int port) throws IOException {
         if (closed.get()) {
             throw new IllegalStateException("Netty client has already been closed");
@@ -135,6 +152,7 @@ public abstract class NettyClient implements AutoCloseable {
             @Override
             public void initChannel(Channel transportChannel) throws Exception {
                 channel = transportChannel;
+                eventLoop = new Netty5EventLoop(channel.executor());
                 configureChannel(transportChannel);
             }
         });
@@ -157,14 +175,16 @@ public abstract class NettyClient implements AutoCloseable {
         }
     }
 
-    public EventLoop eventLoop() {
+    @Override
+    public NettyEventLoop eventLoop() {
         if (channel == null || !channel.isActive()) {
             throw new IllegalStateException("Channel is not connected or has closed");
         }
 
-        return channel.executor();
+        return eventLoop;
     }
 
+    @Override
     public void write(ByteBuffer buffer) {
         if (channel == null || !channel.isActive()) {
             throw new IllegalStateException("Channel is not connected or has closed");
@@ -173,14 +193,17 @@ public abstract class NettyClient implements AutoCloseable {
         channel.writeAndFlush(BufferAllocator.onHeapUnpooled().copyOf(buffer).makeReadOnly());
     }
 
+    @Override
     public boolean isConnected() {
         return connected.get();
     }
 
+    @Override
     public boolean isSecure() {
         return options.isSecure();
     }
 
+    @Override
     public URI getRemoteURI() {
         if (host != null) {
             try {
@@ -232,7 +255,7 @@ public abstract class NettyClient implements AutoCloseable {
                 handshakeTimeoutFuture = context.executor().schedule(()-> {
                     LOG.trace("WebSocket handshake timed out! Channel is {}", context.channel());
                     if (!handshaker.isHandshakeComplete()) {
-                        NettyClient.this.handleTransportFailure(channel, new IOException("WebSocket handshake timed out"));
+                        Netty5Client.this.handleTransportFailure(channel, new IOException("WebSocket handshake timed out"));
                     }
                 }, options.getConnectTimeout(), TimeUnit.MILLISECONDS);
             }
@@ -359,7 +382,31 @@ public abstract class NettyClient implements AutoCloseable {
 
     //----- Internal Client implementation API
 
-    protected abstract ChannelHandler getClientHandler();
+    protected ChannelHandler getClientHandler() {
+        return new SimpleChannelInboundHandler<Buffer>() {
+
+            @Override
+            public void channelActive(ChannelHandlerContext ctx) throws Exception {
+                connectedRunnable.run();
+                ctx.fireChannelActive();
+            }
+
+            @Override
+            protected void messageReceived(ChannelHandlerContext ctx, Buffer input) throws Exception {
+                LOG.trace("AMQP Test Client Channel read: {}", input);
+
+                // Driver processes new data and may produce output based on this.
+                try {
+                    final ByteBuffer copy = ByteBuffer.allocate(input.readableBytes());
+                    input.readBytes(copy);
+                    inputConsumer.accept(copy.flip().asReadOnlyBuffer());
+                } catch (Throwable e) {
+                    LOG.error("Closed AMQP Test client channel due to error: ", e);
+                    ctx.channel().close();
+                }
+            }
+        };
+    }
 
     protected EventLoop getEventLoop() {
         if (channel == null || !channel.isActive()) {
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/NullElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty5/Netty5EventLoop.java
similarity index 58%
copy from protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/NullElement.java
copy to protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty5/Netty5EventLoop.java
index 675c9da4..16056003 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/NullElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty5/Netty5EventLoop.java
@@ -14,37 +14,35 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.qpid.protonj2.test.driver.codec;
 
-import io.netty5.buffer.Buffer;
+package org.apache.qpid.protonj2.test.driver.netty.netty5;
 
-class NullElement extends AtomicElement<Void> {
+import java.util.concurrent.TimeUnit;
 
-    NullElement(Element<?> parent, Element<?> prev) {
-        super(parent, prev);
-    }
+import org.apache.qpid.protonj2.test.driver.netty.NettyEventLoop;
 
-    @Override
-    public int size() {
-        return isElementOfArray() ? 0 : 1;
+import io.netty5.channel.EventLoop;
+
+public final class Netty5EventLoop implements NettyEventLoop {
+
+    private final EventLoop loop;
+
+    public Netty5EventLoop(EventLoop loop) {
+        this.loop = loop;
     }
 
     @Override
-    public Void getValue() {
-        return null;
+    public boolean inEventLoop() {
+        return loop.inEventLoop();
     }
 
     @Override
-    public Codec.DataType getDataType() {
-        return Codec.DataType.NULL;
+    public void execute(Runnable runnable) {
+        loop.execute(runnable);
     }
 
     @Override
-    public int encode(Buffer buffer) {
-        if (buffer.writableBytes() > 0 && !isElementOfArray()) {
-            buffer.writeByte((byte) 0x40);
-            return 1;
-        }
-        return 0;
+    public void schedule(Runnable runnable, int delay, TimeUnit unit) {
+        loop.schedule(runnable, delay, unit);
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyServer.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty5/Netty5Server.java
similarity index 87%
copy from protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyServer.java
copy to protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty5/Netty5Server.java
index 26f16892..11cf9652 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/NettyServer.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty5/Netty5Server.java
@@ -14,8 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.qpid.protonj2.test.driver.netty;
+package org.apache.qpid.protonj2.test.driver.netty.netty5;
 
+import java.lang.invoke.MethodHandles;
 import java.net.InetSocketAddress;
 import java.net.URI;
 import java.nio.ByteBuffer;
@@ -24,11 +25,14 @@ import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
 
 import javax.net.ssl.SSLEngine;
 import javax.net.ssl.SSLPeerUnverifiedException;
 
 import org.apache.qpid.protonj2.test.driver.ProtonTestServerOptions;
+import org.apache.qpid.protonj2.test.driver.netty.NettyEventLoop;
+import org.apache.qpid.protonj2.test.driver.netty.NettyServer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -43,9 +47,9 @@ import io.netty5.channel.ChannelHandlerAdapter;
 import io.netty5.channel.ChannelHandlerContext;
 import io.netty5.channel.ChannelInitializer;
 import io.netty5.channel.ChannelOption;
-import io.netty5.channel.EventLoop;
 import io.netty5.channel.EventLoopGroup;
 import io.netty5.channel.MultithreadEventLoopGroup;
+import io.netty5.channel.SimpleChannelInboundHandler;
 import io.netty5.channel.nio.NioHandler;
 import io.netty5.channel.socket.nio.NioServerSocketChannel;
 import io.netty5.handler.codec.http.DefaultFullHttpResponse;
@@ -72,14 +76,15 @@ import io.netty5.util.concurrent.FutureListener;
  * Base Server implementation used to create Netty based server implementations for
  * unit testing aspects of the client code.
  */
-public abstract class NettyServer implements AutoCloseable {
+public final class Netty5Server implements NettyServer {
 
-    private static final Logger LOG = LoggerFactory.getLogger(NettyServer.class);
+    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
     static final int PORT = Integer.parseInt(System.getProperty("port", "5672"));
     static final String WEBSOCKET_PATH = "/";
     static final int DEFAULT_MAX_FRAME_SIZE = 65535;
 
+    private Netty5EventLoop eventLoop;
     private EventLoopGroup bossGroup;
     private EventLoopGroup workerGroup;
     private Channel serverChannel;
@@ -93,31 +98,46 @@ public abstract class NettyServer implements AutoCloseable {
 
     private final AtomicBoolean started = new AtomicBoolean();
 
-    public NettyServer(ProtonTestServerOptions options) {
+    private final Consumer<ByteBuffer> inputConsumer;
+    private final Runnable connectedRunnable;
+
+    public Netty5Server(ProtonTestServerOptions options, Runnable connectedRunnable, Consumer<ByteBuffer> inputConsumer) {
+        Objects.requireNonNull(options);
+        Objects.requireNonNull(inputConsumer);
+        Objects.requireNonNull(connectedRunnable);
+
         this.options = options;
+        this.connectedRunnable = connectedRunnable;
+        this.inputConsumer = inputConsumer;
     }
 
+    @Override
     public boolean isSecureServer() {
         return options.isSecure();
     }
 
+    @Override
     public boolean isAcceptingConnections() {
         return serverChannel != null && serverChannel.isOpen();
     }
 
+    @Override
     public boolean hasSecureConnection() {
         return sslHandler != null;
     }
 
+    @Override
     public boolean hasClientConnection() {
         return clientChannel != null && clientChannel.isOpen();
     }
 
+    @Override
     public int getClientPort() {
         Objects.requireNonNull(clientChannel);
         return (((InetSocketAddress) clientChannel.remoteAddress()).getPort());
     }
 
+    @Override
     public boolean isPeerVerified() {
         try {
             if (hasSecureConnection()) {
@@ -130,6 +150,7 @@ public abstract class NettyServer implements AutoCloseable {
         }
     }
 
+    @Override
     public SSLEngine getConnectionSSLEngine() {
         if (hasSecureConnection()) {
             return sslHandler.engine();
@@ -138,22 +159,27 @@ public abstract class NettyServer implements AutoCloseable {
         }
     }
 
+    @Override
     public boolean isWebSocketServer() {
         return options.isUseWebSockets();
     }
 
+    @Override
     public String getWebSocketPath() {
         return webSocketPath;
     }
 
+    @Override
     public void setWebSocketPath(String webSocketPath) {
         this.webSocketPath = webSocketPath;
     }
 
+    @Override
     public int getMaxFrameSize() {
         return maxFrameSize;
     }
 
+    @Override
     public void setMaxFrameSize(int maxFrameSize) {
         this.maxFrameSize = maxFrameSize;
     }
@@ -166,6 +192,7 @@ public abstract class NettyServer implements AutoCloseable {
         return handshakeComplete;
     }
 
+    @Override
     public URI getConnectionURI(String queryString) throws Exception {
         if (!started.get()) {
             throw new IllegalStateException("Cannot get URI of non-started server");
@@ -203,6 +230,7 @@ public abstract class NettyServer implements AutoCloseable {
         return new URI(scheme, null, "localhost", port, path, queryString, null);
     }
 
+    @Override
     public void start() throws Exception {
         if (started.compareAndSet(false, true)) {
             // Configure the server to basic NIO type channels
@@ -222,6 +250,7 @@ public abstract class NettyServer implements AutoCloseable {
                     serverChannel.close();
                     // Now we know who the client is
                     clientChannel = ch;
+                    eventLoop = new Netty5EventLoop(ch.executor());
 
                     if (isSecureServer()) {
                         ch.pipeline().addLast(sslHandler = SslSupport.createServerSslHandler(null, options));
@@ -246,8 +275,33 @@ public abstract class NettyServer implements AutoCloseable {
         }
     }
 
-    protected abstract ChannelHandler getServerHandler();
+    protected ChannelHandler getServerHandler() {
+        return new SimpleChannelInboundHandler<Buffer>() {
+
+            @Override
+            public void channelActive(ChannelHandlerContext ctx) throws Exception {
+                connectedRunnable.run();
+                ctx.fireChannelActive();
+            }
+
+            @Override
+            protected void messageReceived(ChannelHandlerContext ctx, Buffer input) throws Exception {
+                LOG.trace("AMQP Test Server Channel read: {}", input);
+
+                // Driver processes new data and may produce output based on this.
+                try {
+                    final ByteBuffer copy = ByteBuffer.allocate(input.readableBytes());
+                    input.readBytes(copy);
+                    inputConsumer.accept(copy.flip().asReadOnlyBuffer());
+                } catch (Throwable e) {
+                    LOG.error("Closed AMQP Test server channel due to error: ", e);
+                    ctx.channel().close();
+                }
+            }
+        };
+    }
 
+    @Override
     public void write(ByteBuffer frame) {
         if (clientChannel == null || !clientChannel.isActive()) {
             throw new IllegalStateException("Channel is not connected or has closed");
@@ -256,14 +310,16 @@ public abstract class NettyServer implements AutoCloseable {
         clientChannel.writeAndFlush(BufferAllocator.onHeapUnpooled().copyOf(frame).makeReadOnly());
     }
 
-    public EventLoop eventLoop() {
+    @Override
+    public NettyEventLoop eventLoop() {
         if (clientChannel == null || !clientChannel.isActive()) {
             throw new IllegalStateException("Channel is not connected or has closed");
         }
 
-        return clientChannel.executor();
+        return eventLoop;
     }
 
+    @Override
     public void stop() throws InterruptedException {
         if (started.compareAndSet(true, false)) {
             LOG.info("Syncing channel close");
@@ -292,6 +348,7 @@ public abstract class NettyServer implements AutoCloseable {
         }
     }
 
+    @Override
     public void stopAsync() throws InterruptedException {
         if (started.compareAndSet(true, false)) {
             LOG.info("Closing channel asynchronously");
@@ -316,6 +373,7 @@ public abstract class NettyServer implements AutoCloseable {
         stop();
     }
 
+    @Override
     public int getServerPort() {
         if (!started.get()) {
             throw new IllegalStateException("Cannot get server port of non-started server");
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/NullElement.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty5/Netty5Support.java
similarity index 52%
copy from protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/NullElement.java
copy to protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty5/Netty5Support.java
index 675c9da4..776744fd 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/codec/NullElement.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty5/Netty5Support.java
@@ -14,37 +14,35 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.qpid.protonj2.test.driver.codec;
 
-import io.netty5.buffer.Buffer;
+package org.apache.qpid.protonj2.test.driver.netty.netty5;
 
-class NullElement extends AtomicElement<Void> {
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-    NullElement(Element<?> parent, Element<?> prev) {
-        super(parent, prev);
-    }
+import io.netty5.buffer.BufferAllocator;
 
-    @Override
-    public int size() {
-        return isElementOfArray() ? 0 : 1;
-    }
+/**
+ * Support class used to detect if Netty 5 is available on the class path.
+ */
+public final class Netty5Support {
 
-    @Override
-    public Void getValue() {
-        return null;
-    }
+    private static final Logger LOG = LoggerFactory.getLogger(Netty5Support.class);
 
-    @Override
-    public Codec.DataType getDataType() {
-        return Codec.DataType.NULL;
+    private static final Throwable UNAVAILABILITY_CAUSE;
+    static {
+        Throwable cause = null;
+        try {
+            BufferAllocator.onHeapUnpooled();
+        } catch (Throwable ex) {
+            LOG.debug("Netty 5 not available for use.");
+            cause = ex;
+        }
+
+        UNAVAILABILITY_CAUSE = cause;
     }
 
-    @Override
-    public int encode(Buffer buffer) {
-        if (buffer.writableBytes() > 0 && !isElementOfArray()) {
-            buffer.writeByte((byte) 0x40);
-            return 1;
-        }
-        return 0;
+    public static final boolean isAvailable() {
+        return UNAVAILABILITY_CAUSE == null;
     }
 }
diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/SslSupport.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty5/SslSupport.java
similarity index 98%
rename from protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/SslSupport.java
rename to protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty5/SslSupport.java
index 256122ee..5fbdcae2 100644
--- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/SslSupport.java
+++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/netty/netty5/SslSupport.java
@@ -14,11 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.qpid.protonj2.test.driver.netty;
+package org.apache.qpid.protonj2.test.driver.netty.netty5;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.InputStream;
+import java.lang.invoke.MethodHandles;
 import java.net.URI;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
@@ -38,6 +39,7 @@ import javax.net.ssl.X509ExtendedKeyManager;
 
 import org.apache.qpid.protonj2.test.driver.ProtonTestClientOptions;
 import org.apache.qpid.protonj2.test.driver.ProtonTestServerOptions;
+import org.apache.qpid.protonj2.test.driver.netty.X509AliasKeyManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -49,7 +51,7 @@ import io.netty5.handler.ssl.util.InsecureTrustManagerFactory;
  */
 public class SslSupport {
 
-    private static final Logger LOG = LoggerFactory.getLogger(SslSupport.class);
+    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
     /**
      * Creates a Netty SslHandler instance for use in client instances that require
diff --git a/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/codec/benchmark/Benchmark.java b/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/codec/benchmark/Benchmark.java
index 3747fc1d..868c3556 100644
--- a/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/codec/benchmark/Benchmark.java
+++ b/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/codec/benchmark/Benchmark.java
@@ -20,8 +20,10 @@
  */
 package org.apache.qpid.protonj2.codec.benchmark;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.lang.management.ManagementFactory;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.UUID;
@@ -44,14 +46,11 @@ import org.apache.qpid.protonj2.test.driver.codec.transport.Flow;
 import org.apache.qpid.protonj2.test.driver.codec.transport.Role;
 import org.apache.qpid.protonj2.test.driver.codec.transport.Transfer;
 
-import io.netty5.buffer.Buffer;
-import io.netty5.buffer.BufferAllocator;
-
 public class Benchmark implements Runnable {
 
     private static final int ITERATIONS = 10 * 1024 * 1024;
 
-    private Buffer buffer = BufferAllocator.onHeapUnpooled().allocate(8192);
+    private ByteArrayOutputStream output = new ByteArrayOutputStream(8192);
     private BenchmarkResult resultSet = new BenchmarkResult();
     private boolean warming = true;
 
@@ -105,16 +104,18 @@ public class Benchmark implements Runnable {
 
         resultSet.start();
         for (int i = 0; i < ITERATIONS; i++) {
-            buffer.resetOffsets();
+            output.reset();
             codec.putJavaList(list);
-            codec.encode(buffer);
+            codec.encode(output);
             codec.clear();
         }
         resultSet.encodesComplete();
 
+        final ByteBuffer buffer = ByteBuffer.wrap(output.toByteArray()).asReadOnlyBuffer();
+
         resultSet.start();
         for (int i = 0; i < ITERATIONS; i++) {
-            buffer.readerOffset(0);
+            buffer.position(0);
             codec.decode(buffer);
             codec.clear();
         }
@@ -128,16 +129,18 @@ public class Benchmark implements Runnable {
 
         resultSet.start();
         for (int i = 0; i < ITERATIONS; i++) {
-            buffer.resetOffsets();
+            output.reset();
             codec.putUUID(uuid);
-            codec.encode(buffer);
+            codec.encode(output);
             codec.clear();
         }
         resultSet.encodesComplete();
 
+        final ByteBuffer buffer = ByteBuffer.wrap(output.toByteArray()).asReadOnlyBuffer();
+
         resultSet.start();
         for (int i = 0; i < ITERATIONS; i++) {
-            buffer.readerOffset(0);
+            buffer.position(0);
             codec.decode(buffer);
             codec.clear();
         }
@@ -154,16 +157,18 @@ public class Benchmark implements Runnable {
 
         resultSet.start();
         for (int i = 0; i < ITERATIONS; i++) {
-            buffer.resetOffsets();
+            output.reset();
             codec.putDescribedType(transfer);
-            codec.encode(buffer);
+            codec.encode(output);
             codec.clear();
         }
         resultSet.encodesComplete();
 
+        final ByteBuffer buffer = ByteBuffer.wrap(output.toByteArray()).asReadOnlyBuffer();
+
         resultSet.start();
         for (int i = 0; i < ITERATIONS; i++) {
-            buffer.readerOffset(0);
+            buffer.position(0);
             codec.decode(buffer);
             codec.clear();
         }
@@ -184,16 +189,18 @@ public class Benchmark implements Runnable {
 
         resultSet.start();
         for (int i = 0; i < ITERATIONS; i++) {
-            buffer.resetOffsets();
+            output.reset();
             codec.putDescribedType(flow);
-            codec.encode(buffer);
+            codec.encode(output);
             codec.clear();
         }
         resultSet.encodesComplete();
 
+        final ByteBuffer buffer = ByteBuffer.wrap(output.toByteArray()).asReadOnlyBuffer();
+
         resultSet.start();
         for (int i = 0; i < ITERATIONS; i++) {
-            buffer.readerOffset(0);
+            buffer.position(0);
             codec.decode(buffer);
             codec.clear();
         }
@@ -209,16 +216,18 @@ public class Benchmark implements Runnable {
 
         resultSet.start();
         for (int i = 0; i < ITERATIONS; i++) {
-            buffer.resetOffsets();
+            output.reset();
             codec.putDescribedType(header);
-            codec.encode(buffer);
+            codec.encode(output);
             codec.clear();
         }
         resultSet.encodesComplete();
 
+        final ByteBuffer buffer = ByteBuffer.wrap(output.toByteArray()).asReadOnlyBuffer();
+
         resultSet.start();
         for (int i = 0; i < ITERATIONS; i++) {
-            buffer.readerOffset(0);
+            buffer.position(0);
             codec.decode(buffer);
             codec.clear();
         }
@@ -236,16 +245,18 @@ public class Benchmark implements Runnable {
 
         resultSet.start();
         for (int i = 0; i < ITERATIONS; i++) {
-            buffer.resetOffsets();
+            output.reset();
             codec.putDescribedType(properties);
-            codec.encode(buffer);
+            codec.encode(output);
             codec.clear();
         }
         resultSet.encodesComplete();
 
+        final ByteBuffer buffer = ByteBuffer.wrap(output.toByteArray()).asReadOnlyBuffer();
+
         resultSet.start();
         for (int i = 0; i < ITERATIONS; i++) {
-            buffer.readerOffset(0);
+            buffer.position(0);
             codec.decode(buffer);
             codec.clear();
         }
@@ -262,16 +273,18 @@ public class Benchmark implements Runnable {
 
         resultSet.start();
         for (int i = 0; i < ITERATIONS; i++) {
-            buffer.resetOffsets();
+            output.reset();
             codec.putDescribedType(annotations);
-            codec.encode(buffer);
+            codec.encode(output);
             codec.clear();
         }
         resultSet.encodesComplete();
 
+        final ByteBuffer buffer = ByteBuffer.wrap(output.toByteArray()).asReadOnlyBuffer();
+
         resultSet.start();
         for (int i = 0; i < ITERATIONS; i++) {
-            buffer.readerOffset(0);
+            buffer.position(0);
             codec.decode(buffer);
             codec.clear();
         }
@@ -288,16 +301,18 @@ public class Benchmark implements Runnable {
 
         resultSet.start();
         for (int i = 0; i < ITERATIONS; i++) {
-            buffer.resetOffsets();
+            output.reset();
             codec.putDescribedType(properties);
-            codec.encode(buffer);
+            codec.encode(output);
             codec.clear();
         }
         resultSet.encodesComplete();
 
+        final ByteBuffer buffer = ByteBuffer.wrap(output.toByteArray()).asReadOnlyBuffer();
+
         resultSet.start();
         for (int i = 0; i < ITERATIONS; i++) {
-            buffer.readerOffset(0);
+            buffer.position(0);
             codec.decode(buffer);
             codec.clear();
         }
@@ -313,18 +328,20 @@ public class Benchmark implements Runnable {
 
         resultSet.start();
         for (int i = 0; i < ITERATIONS; i++) {
-            buffer.resetOffsets();
+            output.reset();
             codec.putSymbol(symbol1);
             codec.putSymbol(symbol2);
             codec.putSymbol(symbol3);
-            codec.encode(buffer);
+            codec.encode(output);
             codec.clear();
         }
         resultSet.encodesComplete();
 
+        final ByteBuffer buffer = ByteBuffer.wrap(output.toByteArray()).asReadOnlyBuffer();
+
         resultSet.start();
         for (int i = 0; i < ITERATIONS; i++) {
-            buffer.readerOffset(0);
+            buffer.position(0);
             codec.decode(buffer);
             codec.clear();
         }
@@ -340,18 +357,20 @@ public class Benchmark implements Runnable {
 
         resultSet.start();
         for (int i = 0; i < ITERATIONS; i++) {
-            buffer.resetOffsets();
+            output.reset();
             codec.putString(string1);
             codec.putString(string2);
             codec.putString(string3);
-            codec.encode(buffer);
+            codec.encode(output);
             codec.clear();
         }
         resultSet.encodesComplete();
 
+        final ByteBuffer buffer = ByteBuffer.wrap(output.toByteArray()).asReadOnlyBuffer();
+
         resultSet.start();
         for (int i = 0; i < ITERATIONS; i++) {
-            buffer.readerOffset(0);
+            buffer.position(0);
             codec.decode(buffer);
             codec.decode(buffer);
             codec.decode(buffer);
@@ -372,16 +391,18 @@ public class Benchmark implements Runnable {
 
         resultSet.start();
         for (int i = 0; i < ITERATIONS; i++) {
-            buffer.resetOffsets();
+            output.reset();
             codec.putDescribedType(disposition);
-            codec.encode(buffer);
+            codec.encode(output);
             codec.clear();
         }
         resultSet.encodesComplete();
 
+        final ByteBuffer buffer = ByteBuffer.wrap(output.toByteArray()).asReadOnlyBuffer();
+
         resultSet.start();
         for (int i = 0; i < ITERATIONS; i++) {
-            buffer.readerOffset(0);
+            buffer.position(0);
             codec.decode(buffer);
             codec.clear();
         }
@@ -397,18 +418,20 @@ public class Benchmark implements Runnable {
 
         resultSet.start();
         for (int i = 0; i < ITERATIONS; i++) {
-            buffer.resetOffsets();
+            output.reset();
             codec.putDescribedType(data1);
             codec.putDescribedType(data2);
             codec.putDescribedType(data3);
-            codec.encode(buffer);
+            codec.encode(output);
             codec.clear();
         }
         resultSet.encodesComplete();
 
+        final ByteBuffer buffer = ByteBuffer.wrap(output.toByteArray()).asReadOnlyBuffer();
+
         resultSet.start();
         for (int i = 0; i < ITERATIONS; i++) {
-            buffer.readerOffset(0);
+            buffer.position(0);
             codec.decode(buffer);
             codec.clear();
         }
diff --git a/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/test/driver/codec/DataImplTest.java b/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/test/driver/codec/DataImplTest.java
index e3f3bf8f..c20d2a0f 100644
--- a/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/test/driver/codec/DataImplTest.java
+++ b/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/test/driver/codec/DataImplTest.java
@@ -21,7 +21,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
 import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.ByteBuffer;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -39,9 +43,6 @@ import org.apache.qpid.protonj2.test.driver.codec.transport.Role;
 import org.apache.qpid.protonj2.test.driver.codec.transport.SenderSettleMode;
 import org.junit.jupiter.api.Test;
 
-import io.netty5.buffer.Buffer;
-import io.netty5.buffer.BufferAllocator;
-
 /**
  * Test some basic operations of the Data type codec
  */
@@ -55,8 +56,8 @@ public class DataImplTest {
         open.setContainerId("test");
         open.setHostname("localhost");
 
-        Buffer encoded = encodeProtonPerformative(open);
-        int expectedRead = encoded.readableBytes();
+        ByteBuffer encoded = encodeProtonPerformative(open);
+        int expectedRead = encoded.remaining();
 
         Codec codec = Codec.Factory.create();
 
@@ -79,8 +80,16 @@ public class DataImplTest {
         Codec codec = Codec.Factory.create();
 
         codec.putDescribedType(open);
-        Buffer encoded = BufferAllocator.onHeapUnpooled().allocate((int) codec.encodedSize());
-        codec.encode(encoded);
+        final long encodedSizeEstimate = codec.encodedSize();
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream(256);
+        final DataOutputStream output = new DataOutputStream(baos);
+
+        codec.encode(output);
+
+        final ByteBuffer encoded = ByteBuffer.wrap(baos.toByteArray());
+
+        assertEquals(encodedSizeEstimate, encoded.remaining());
 
         DescribedType decoded = decodeProtonPerformative(encoded);
         assertNotNull(decoded);
@@ -97,8 +106,8 @@ public class DataImplTest {
         begin.setHandleMax(UnsignedInteger.valueOf(512));
         begin.setRemoteChannel(UnsignedShort.valueOf(1));
 
-        Buffer encoded = encodeProtonPerformative(begin);
-        int expectedRead = encoded.readableBytes();
+        ByteBuffer encoded = encodeProtonPerformative(begin);
+        int expectedRead = encoded.remaining();
 
         Codec codec = Codec.Factory.create();
 
@@ -124,8 +133,13 @@ public class DataImplTest {
         Codec codec = Codec.Factory.create();
 
         codec.putDescribedType(begin);
-        try (Buffer encoded = BufferAllocator.onHeapUnpooled().allocate((int) codec.encodedSize())) {
-            codec.encode(encoded);
+
+        try (ByteArrayOutputStream baos = new ByteArrayOutputStream((int) codec.encodedSize());
+             DataOutputStream output = new DataOutputStream(baos)) {
+
+            codec.encode(output);
+
+            final ByteBuffer encoded = ByteBuffer.wrap(baos.toByteArray());
 
             DescribedType decoded = decodeProtonPerformative(encoded);
             assertNotNull(decoded);
@@ -148,20 +162,19 @@ public class DataImplTest {
         attach.setSource(new Source());
         attach.setTarget(new Target());
 
-        try (Buffer encoded = encodeProtonPerformative(attach)) {
-            final int expectedRead = encoded.readableBytes();
+        final ByteBuffer encoded = encodeProtonPerformative(attach);
+        final int expectedRead = encoded.remaining();
 
-            Codec codec = Codec.Factory.create();
+        Codec codec = Codec.Factory.create();
 
-            assertEquals(expectedRead, codec.decode(encoded));
+        assertEquals(expectedRead, codec.decode(encoded));
 
-            Attach described = (Attach) codec.getDescribedType();
-            assertNotNull(described);
-            assertEquals(Attach.DESCRIPTOR_SYMBOL, described.getDescriptor());
+        Attach described = (Attach) codec.getDescribedType();
+        assertNotNull(described);
+        assertEquals(Attach.DESCRIPTOR_SYMBOL, described.getDescriptor());
 
-            assertEquals(described.getHandle(), UnsignedInteger.valueOf(1));
-            assertEquals(described.getName(), "test");
-        }
+        assertEquals(described.getHandle(), UnsignedInteger.valueOf(1));
+        assertEquals(described.getName(), "test");
     }
 
     @Test
@@ -178,8 +191,13 @@ public class DataImplTest {
         Codec codec = Codec.Factory.create();
 
         codec.putDescribedType(attach);
-        try (Buffer encoded = BufferAllocator.onHeapUnpooled().allocate((int) codec.encodedSize())) {
-            codec.encode(encoded);
+
+        try (ByteArrayOutputStream baos = new ByteArrayOutputStream((int) codec.encodedSize());
+             DataOutputStream output = new DataOutputStream(baos)) {
+
+           codec.encode(output);
+
+           final ByteBuffer encoded = ByteBuffer.wrap(baos.toByteArray());
 
             DescribedType decoded = decodeProtonPerformative(encoded);
             assertNotNull(decoded);
@@ -206,13 +224,13 @@ public class DataImplTest {
                                                 2, -95, 12, 113, 117, 101, 117, 101, 45, 112, 114, 101, 102, 105,
                                                 120, -95, 8, 113, 117, 101, 117, 101, 58, 47, 47};
 
-        Buffer encoded = BufferAllocator.onHeapUnpooled().copyOf(completeOpen);
+        final ByteBuffer encoded = ByteBuffer.wrap(completeOpen);
 
         DescribedType decoded = decodeProtonPerformative(encoded);
         assertNotNull(decoded);
         assertTrue(decoded instanceof Open);
 
-        Open performative = (Open) decoded;
+        final Open performative = (Open) decoded;
 
         assertEquals("container", performative.getContainerId());
         assertEquals("localhost", performative.getHostname());
@@ -232,7 +250,7 @@ public class DataImplTest {
         assertEquals(expected, performative.getProperties());
     }
 
-    private DescribedType decodeProtonPerformative(Buffer buffer) throws IOException {
+    private DescribedType decodeProtonPerformative(ByteBuffer buffer) throws IOException {
         DescribedType performative = null;
 
         try {
@@ -256,18 +274,20 @@ public class DataImplTest {
         return performative;
     }
 
-    private Buffer encodeProtonPerformative(DescribedType performative) {
-        Buffer buffer = BufferAllocator.onHeapPooled().allocate(256);
+    private ByteBuffer encodeProtonPerformative(DescribedType performative) {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream(256);
 
         if (performative != null) {
-            try {
+            try (DataOutputStream output = new DataOutputStream(baos)) {
                 codec.putDescribedType(performative);
-                codec.encode(buffer);
+                codec.encode(output);
+            } catch (IOException e) {
+                throw new UncheckedIOException(e);
             } finally {
                 codec.clear();
             }
         }
 
-        return buffer;
+        return ByteBuffer.wrap(baos.toByteArray());
     }
 }
diff --git a/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/test/driver/legacy/LegacyCodecTransferFramesTestDataGenerator.java b/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/test/driver/legacy/LegacyCodecTransferFramesTestDataGenerator.java
index 67e0dc03..83759208 100644
--- a/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/test/driver/legacy/LegacyCodecTransferFramesTestDataGenerator.java
+++ b/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/test/driver/legacy/LegacyCodecTransferFramesTestDataGenerator.java
@@ -21,9 +21,11 @@ import java.util.HashMap;
 import java.util.UUID;
 
 import org.apache.qpid.proton.amqp.Binary;
+import org.apache.qpid.proton.amqp.UnsignedByte;
 import org.apache.qpid.proton.amqp.UnsignedInteger;
 import org.apache.qpid.proton.amqp.messaging.ApplicationProperties;
 import org.apache.qpid.proton.amqp.messaging.Data;
+import org.apache.qpid.proton.amqp.messaging.Header;
 import org.apache.qpid.proton.amqp.transport.Transfer;
 import org.apache.qpid.proton.codec.ReadableBuffer;
 import org.apache.qpid.proton.codec.WritableBuffer;
@@ -46,11 +48,14 @@ public class LegacyCodecTransferFramesTestDataGenerator {
         completedTransfer.setMessageFormat(null);
         completedTransfer.setSettled(true);
 
-        String emptyOpenFrameString = LegacyFrameDataGenerator.generateUnitTestVariable("completedTransfer", completedTransfer, encodeMessage());
-        System.out.println(emptyOpenFrameString);
+        String encodedMessageOne = LegacyFrameDataGenerator.generateUnitTestVariable("completedTransfer1", completedTransfer, encodeMessage1());
+        System.out.println(encodedMessageOne);
+
+        String encodedMessageTwo = LegacyFrameDataGenerator.generateUnitTestVariable("completedTransfer2", completedTransfer, encodeMessage2());
+        System.out.println(encodedMessageTwo);
     }
 
-    private static ReadableBuffer encodeMessage() {
+    private static ReadableBuffer encodeMessage1() {
         final WritableBuffer.ByteBufferWrapper buffer = WritableBuffer.ByteBufferWrapper.allocate(1024);
         final byte[] body = new byte[100];
         Arrays.fill(body, (byte) 'A');
@@ -69,4 +74,25 @@ public class LegacyCodecTransferFramesTestDataGenerator {
 
         return buffer.toReadableBuffer();
     }
+
+    private static ReadableBuffer encodeMessage2() {
+        final WritableBuffer.ByteBufferWrapper buffer = WritableBuffer.ByteBufferWrapper.allocate(1024);
+        final byte[] body = new byte[] { 0, 1, 2, 3 };
+
+        Header header = new Header();
+        header.setDurable(true);
+        header.setPriority(UnsignedByte.valueOf((byte) 2));
+        header.setTtl(UnsignedInteger.valueOf(65535));
+        header.setFirstAcquirer(true);
+        header.setDeliveryCount(UnsignedInteger.valueOf(2));
+
+        Message message = Message.Factory.create();
+
+        message.setHeader(header);
+        message.setBody(new Data(new Binary(body)));
+
+        message.encode(buffer);
+
+        return buffer.toReadableBuffer();
+    }
 }
diff --git a/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/test/driver/matches/types/EncodedCompositingDataSectionMatcherTest.java b/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/test/driver/matches/types/EncodedCompositingDataSectionMatcherTest.java
index 5c481542..70771f71 100644
--- a/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/test/driver/matches/types/EncodedCompositingDataSectionMatcherTest.java
+++ b/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/test/driver/matches/types/EncodedCompositingDataSectionMatcherTest.java
@@ -19,6 +19,7 @@ package org.apache.qpid.protonj2.test.driver.matches.types;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 
+import java.nio.ByteBuffer;
 import java.util.Random;
 
 import org.apache.qpid.proton.codec.EncodingCodes;
@@ -26,9 +27,6 @@ import org.apache.qpid.protonj2.test.driver.codec.messaging.Data;
 import org.apache.qpid.protonj2.test.driver.matchers.types.EncodedCompositingDataSectionMatcher;
 import org.junit.jupiter.api.Test;
 
-import io.netty5.buffer.Buffer;
-import io.netty5.buffer.BufferAllocator;
-
 class EncodedCompositingDataSectionMatcherTest {
 
     @Test
@@ -39,9 +37,9 @@ class EncodedCompositingDataSectionMatcherTest {
 
         Random bytesGenerator = new Random(SEED);
         bytesGenerator.nextBytes(PAYLOAD);
-        Buffer incomingBytes = BufferAllocator.onHeapUnpooled().allocate(EXPECTED_SIZE);
+        ByteBuffer incomingBytes = ByteBuffer.allocate(EXPECTED_SIZE);
 
-        incomingBytes.writeBytes(PAYLOAD);
+        incomingBytes.put(PAYLOAD).flip();
 
         EncodedCompositingDataSectionMatcher matcher =
             new EncodedCompositingDataSectionMatcher(PAYLOAD);
@@ -61,14 +59,15 @@ class EncodedCompositingDataSectionMatcherTest {
         bytesGenerator.setSeed(SEED);
         bytesGenerator.nextBytes(CHUNK);
 
-        Buffer partial1 = BufferAllocator.onHeapUnpooled().allocate(EXPECTED_SIZE);
+        ByteBuffer partial1 = ByteBuffer.allocate(EXPECTED_SIZE);
 
-        partial1.writeByte(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
-        partial1.writeByte(EncodingCodes.SMALLULONG);
-        partial1.writeByte(Data.DESCRIPTOR_CODE.byteValue());
-        partial1.writeByte(EncodingCodes.VBIN32);
-        partial1.writeInt(EXPECTED_SIZE);
-        partial1.writeBytes(CHUNK);
+        partial1.put(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
+        partial1.put(EncodingCodes.SMALLULONG);
+        partial1.put(Data.DESCRIPTOR_CODE.byteValue());
+        partial1.put(EncodingCodes.VBIN32);
+        partial1.putInt(EXPECTED_SIZE);
+        partial1.put(CHUNK);
+        partial1.flip();
 
         EncodedCompositingDataSectionMatcher matcher =
             new EncodedCompositingDataSectionMatcher(PAYLOAD);
@@ -88,14 +87,15 @@ class EncodedCompositingDataSectionMatcherTest {
         bytesGenerator.setSeed(SEED + 1);
         bytesGenerator.nextBytes(CHUNK);
 
-        Buffer partial1 = BufferAllocator.onHeapUnpooled().allocate(EXPECTED_SIZE);
+        ByteBuffer partial1 = ByteBuffer.allocate(EXPECTED_SIZE);
 
-        partial1.writeByte(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
-        partial1.writeByte(EncodingCodes.SMALLULONG);
-        partial1.writeByte(Data.DESCRIPTOR_CODE.byteValue());
-        partial1.writeByte(EncodingCodes.VBIN32);
-        partial1.writeInt(EXPECTED_SIZE);
-        partial1.writeBytes(CHUNK);
+        partial1.put(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
+        partial1.put(EncodingCodes.SMALLULONG);
+        partial1.put(Data.DESCRIPTOR_CODE.byteValue());
+        partial1.put(EncodingCodes.VBIN32);
+        partial1.putInt(EXPECTED_SIZE);
+        partial1.put(CHUNK);
+        partial1.flip();
 
         EncodedCompositingDataSectionMatcher matcher =
             new EncodedCompositingDataSectionMatcher(PAYLOAD);
@@ -117,19 +117,21 @@ class EncodedCompositingDataSectionMatcherTest {
         bytesGenerator.nextBytes(CHUNK1);
         bytesGenerator.nextBytes(CHUNK2);
 
-        Buffer partial1 = BufferAllocator.onHeapUnpooled().allocate(EXPECTED_SIZE);
-        Buffer partial2 = BufferAllocator.onHeapUnpooled().allocate(EXPECTED_SIZE);
+        final ByteBuffer partial1 = ByteBuffer.allocate(EXPECTED_SIZE);
+        final ByteBuffer partial2 = ByteBuffer.allocate(EXPECTED_SIZE);
 
         // First half arrives with preamble
-        partial1.writeByte(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
-        partial1.writeByte(EncodingCodes.SMALLULONG);
-        partial1.writeByte(Data.DESCRIPTOR_CODE.byteValue());
-        partial1.writeByte(EncodingCodes.VBIN32);
-        partial1.writeInt(EXPECTED_SIZE);
-        partial1.writeBytes(CHUNK1);
+        partial1.put(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
+        partial1.put(EncodingCodes.SMALLULONG);
+        partial1.put(Data.DESCRIPTOR_CODE.byteValue());
+        partial1.put(EncodingCodes.VBIN32);
+        partial1.putInt(EXPECTED_SIZE);
+        partial1.put(CHUNK1);
+        partial1.flip();
 
         // Second half arrives without preamble as expected
-        partial2.writeBytes(CHUNK2);
+        partial2.put(CHUNK2);
+        partial2.flip();
 
         EncodedCompositingDataSectionMatcher matcher =
             new EncodedCompositingDataSectionMatcher(PAYLOAD);
@@ -156,25 +158,29 @@ class EncodedCompositingDataSectionMatcherTest {
         bytesGenerator.nextBytes(CHUNK3);
         bytesGenerator.nextBytes(CHUNK4);
 
-        Buffer partial1 = BufferAllocator.onHeapUnpooled().allocate(EXPECTED_SIZE);
-        Buffer partial2 = BufferAllocator.onHeapUnpooled().allocate(EXPECTED_SIZE);
-        Buffer partial3 = BufferAllocator.onHeapUnpooled().allocate(EXPECTED_SIZE);
-        Buffer partial4 = BufferAllocator.onHeapUnpooled().allocate(EXPECTED_SIZE);
+        final ByteBuffer partial1 = ByteBuffer.allocate(EXPECTED_SIZE);
+        final ByteBuffer partial2 = ByteBuffer.allocate(EXPECTED_SIZE);
+        final ByteBuffer partial3 = ByteBuffer.allocate(EXPECTED_SIZE);
+        final ByteBuffer partial4 = ByteBuffer.allocate(EXPECTED_SIZE);
 
         // First chunk arrives with preamble
-        partial1.writeByte(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
-        partial1.writeByte(EncodingCodes.SMALLULONG);
-        partial1.writeByte(Data.DESCRIPTOR_CODE.byteValue());
-        partial1.writeByte(EncodingCodes.VBIN32);
-        partial1.writeInt(EXPECTED_SIZE);
-        partial1.writeBytes(CHUNK1);
+        partial1.put(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
+        partial1.put(EncodingCodes.SMALLULONG);
+        partial1.put(Data.DESCRIPTOR_CODE.byteValue());
+        partial1.put(EncodingCodes.VBIN32);
+        partial1.putInt(EXPECTED_SIZE);
+        partial1.put(CHUNK1);
+        partial1.flip();
 
         // Second chunk arrives without preamble as expected
-        partial2.writeBytes(CHUNK2);
+        partial2.put(CHUNK2);
+        partial2.flip();
         // Third chunk arrives without preamble as expected
-        partial3.writeBytes(CHUNK3);
+        partial3.put(CHUNK3);
+        partial3.flip();
         // Fourth chunk arrives without preamble as expected
-        partial4.writeBytes(CHUNK4);
+        partial4.put(CHUNK4);
+        partial4.flip();
 
         EncodedCompositingDataSectionMatcher matcher =
             new EncodedCompositingDataSectionMatcher(PAYLOAD);
@@ -184,10 +190,13 @@ class EncodedCompositingDataSectionMatcherTest {
         assertThat(partial3, matcher);
         assertThat(partial4, matcher);
 
+        final ByteBuffer partial5 = ByteBuffer.allocate(4);
+
+        partial5.put(new byte[] { 3, 3, 3, 3});
+        partial5.flip();
+
         // Anything else that arrives that is handed to this matcher should fail
-        try (Buffer buffer = BufferAllocator.onHeapUnpooled().allocate(4).writeBytes(new byte[] { 3, 3, 3, 3})) {
-            assertFalse(matcher.matches(buffer));
-        }
+        assertFalse(matcher.matches(partial5));
     }
 
     @Test
@@ -196,19 +205,21 @@ class EncodedCompositingDataSectionMatcherTest {
         final byte[] CHUNK1 = new byte[] { 0, 1, 2 };
         final byte[] CHUNK2 = new byte[] { 3, 4, 5, 6, 7, 8, 9 };
 
-        Buffer partial1 = BufferAllocator.onHeapUnpooled().allocate(16);
-        Buffer partial2 = BufferAllocator.onHeapUnpooled().allocate(16);
+        final ByteBuffer partial1 = ByteBuffer.allocate(16);
+        final ByteBuffer partial2 = ByteBuffer.allocate(16);
 
         // First half arrives with preamble
-        partial1.writeByte(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
-        partial1.writeByte(EncodingCodes.SMALLULONG);
-        partial1.writeByte(Data.DESCRIPTOR_CODE.byteValue());
-        partial1.writeByte(EncodingCodes.VBIN32);
-        partial1.writeInt(PAYLOAD.length);
-        partial1.writeBytes(CHUNK1);
+        partial1.put(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
+        partial1.put(EncodingCodes.SMALLULONG);
+        partial1.put(Data.DESCRIPTOR_CODE.byteValue());
+        partial1.put(EncodingCodes.VBIN32);
+        partial1.putInt(PAYLOAD.length);
+        partial1.put(CHUNK1);
+        partial1.flip();
 
         // Second half arrives without preamble as expected
-        partial2.writeBytes(CHUNK2);
+        partial2.put(CHUNK2);
+        partial2.flip();
 
         EncodedCompositingDataSectionMatcher matcher =
             new EncodedCompositingDataSectionMatcher(PAYLOAD);
@@ -232,19 +243,21 @@ class EncodedCompositingDataSectionMatcherTest {
         bytesGenerator.setSeed(SEED);
         bytesGenerator.nextBytes(CHUNK2);
 
-        Buffer partial1 = BufferAllocator.onHeapUnpooled().allocate(16);
-        Buffer partial2 = BufferAllocator.onHeapUnpooled().allocate(16);
+        final ByteBuffer partial1 = ByteBuffer.allocate(256);
+        final ByteBuffer partial2 = ByteBuffer.allocate(128);
 
         // First half arrives with preamble
-        partial1.writeByte(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
-        partial1.writeByte(EncodingCodes.SMALLULONG);
-        partial1.writeByte(Data.DESCRIPTOR_CODE.byteValue());
-        partial1.writeByte(EncodingCodes.VBIN32);
-        partial1.writeInt(EXPECTED_SIZE);
-        partial1.writeBytes(CHUNK1);
+        partial1.put(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
+        partial1.put(EncodingCodes.SMALLULONG);
+        partial1.put(Data.DESCRIPTOR_CODE.byteValue());
+        partial1.put(EncodingCodes.VBIN32);
+        partial1.putInt(EXPECTED_SIZE);
+        partial1.put(CHUNK1);
+        partial1.flip();
 
         // Second half arrives without preamble as expected
-        partial2.writeBytes(CHUNK2);
+        partial2.put(CHUNK2);
+        partial2.flip();
 
         EncodedCompositingDataSectionMatcher matcher =
             new EncodedCompositingDataSectionMatcher(PAYLOAD);
@@ -266,15 +279,16 @@ class EncodedCompositingDataSectionMatcherTest {
         bytesGenerator.setSeed(SEED);
         bytesGenerator.nextBytes(CHUNK);
 
-        Buffer inboundBytes = BufferAllocator.onHeapUnpooled().allocate(16);
+        final ByteBuffer inboundBytes = ByteBuffer.allocate(512);
 
-        inboundBytes.writeByte(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
-        inboundBytes.writeByte(EncodingCodes.SMALLULONG);
-        inboundBytes.writeByte(Data.DESCRIPTOR_CODE.byteValue());
-        inboundBytes.writeByte(EncodingCodes.VBIN32);
-        inboundBytes.writeInt(EXPECTED_SIZE);
-        inboundBytes.writeBytes(CHUNK);
-        inboundBytes.writeBytes(EXTRA);
+        inboundBytes.put(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
+        inboundBytes.put(EncodingCodes.SMALLULONG);
+        inboundBytes.put(Data.DESCRIPTOR_CODE.byteValue());
+        inboundBytes.put(EncodingCodes.VBIN32);
+        inboundBytes.putInt(EXPECTED_SIZE);
+        inboundBytes.put(CHUNK);
+        inboundBytes.put(EXTRA);
+        inboundBytes.flip();
 
         EncodedCompositingDataSectionMatcher matcher =
             new EncodedCompositingDataSectionMatcher(PAYLOAD);
@@ -297,22 +311,24 @@ class EncodedCompositingDataSectionMatcherTest {
         bytesGenerator.nextBytes(CHUNK2);
 
         // First half arrives with preamble
-        Buffer partial1 = BufferAllocator.onHeapUnpooled().allocate(16);
-        partial1.writeByte(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
-        partial1.writeByte(EncodingCodes.SMALLULONG);
-        partial1.writeByte(Data.DESCRIPTOR_CODE.byteValue());
-        partial1.writeByte(EncodingCodes.VBIN32);
-        partial1.writeInt(CHUNK1.length);
-        partial1.writeBytes(CHUNK1);
+        final ByteBuffer partial1 = ByteBuffer.allocate(256);
+        partial1.put(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
+        partial1.put(EncodingCodes.SMALLULONG);
+        partial1.put(Data.DESCRIPTOR_CODE.byteValue());
+        partial1.put(EncodingCodes.VBIN32);
+        partial1.putInt(CHUNK1.length);
+        partial1.put(CHUNK1);
+        partial1.flip();
 
         // Second half arrives without preamble as expected
-        Buffer partial2 = BufferAllocator.onHeapUnpooled().allocate(16);
-        partial2.writeByte(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
-        partial2.writeByte(EncodingCodes.SMALLULONG);
-        partial2.writeByte(Data.DESCRIPTOR_CODE.byteValue());
-        partial2.writeByte(EncodingCodes.VBIN32);
-        partial2.writeInt(CHUNK2.length);
-        partial2.writeBytes(CHUNK2);
+        final ByteBuffer partial2 = ByteBuffer.allocate(256);
+        partial2.put(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
+        partial2.put(EncodingCodes.SMALLULONG);
+        partial2.put(Data.DESCRIPTOR_CODE.byteValue());
+        partial2.put(EncodingCodes.VBIN32);
+        partial2.putInt(CHUNK2.length);
+        partial2.put(CHUNK2);
+        partial2.flip();
 
         EncodedCompositingDataSectionMatcher matcher =
             new EncodedCompositingDataSectionMatcher(PAYLOAD);
@@ -336,23 +352,25 @@ class EncodedCompositingDataSectionMatcherTest {
         bytesGenerator.nextBytes(CHUNK2);
 
         // First half arrives with preamble
-        Buffer partial1 = BufferAllocator.onHeapUnpooled().allocate(16);
-        partial1.writeByte(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
-        partial1.writeByte(EncodingCodes.SMALLULONG);
-        partial1.writeByte(Data.DESCRIPTOR_CODE.byteValue());
-        partial1.writeByte(EncodingCodes.VBIN32);
-        partial1.writeInt(CHUNK1.length);
-        partial1.writeBytes(CHUNK1);
+        final ByteBuffer partial1 = ByteBuffer.allocate(256);
+        partial1.put(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
+        partial1.put(EncodingCodes.SMALLULONG);
+        partial1.put(Data.DESCRIPTOR_CODE.byteValue());
+        partial1.put(EncodingCodes.VBIN32);
+        partial1.putInt(CHUNK1.length);
+        partial1.put(CHUNK1);
+        partial1.flip();
 
         // Second half arrives without preamble as expected
-        Buffer partial2 = BufferAllocator.onHeapUnpooled().allocate(16);
-        partial2.writeByte(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
-        partial2.writeByte(EncodingCodes.SMALLULONG);
-        partial2.writeByte(Data.DESCRIPTOR_CODE.byteValue());
-        partial2.writeByte(EncodingCodes.VBIN32);
-        partial2.writeInt(CHUNK2.length + 1);
-        partial2.writeBytes(CHUNK2);
-        partial2.writeByte((byte) 64);
+        final ByteBuffer partial2 = ByteBuffer.allocate(128 + 9);
+        partial2.put(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
+        partial2.put(EncodingCodes.SMALLULONG);
+        partial2.put(Data.DESCRIPTOR_CODE.byteValue());
+        partial2.put(EncodingCodes.VBIN32);
+        partial2.putInt(CHUNK2.length + 1);
+        partial2.put(CHUNK2);
+        partial2.put((byte) 64);
+        partial2.flip();
 
         EncodedCompositingDataSectionMatcher matcher =
             new EncodedCompositingDataSectionMatcher(PAYLOAD);
diff --git a/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/test/driver/matches/types/EncodedPartialDataSectionMatcherTest.java b/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/test/driver/matches/types/EncodedPartialDataSectionMatcherTest.java
index b3bc1713..edb163d1 100644
--- a/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/test/driver/matches/types/EncodedPartialDataSectionMatcherTest.java
+++ b/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/test/driver/matches/types/EncodedPartialDataSectionMatcherTest.java
@@ -19,15 +19,14 @@ package org.apache.qpid.protonj2.test.driver.matches.types;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 
+import java.nio.ByteBuffer;
+
 import org.apache.qpid.proton.codec.EncodingCodes;
 import org.apache.qpid.protonj2.test.driver.codec.messaging.AmqpValue;
 import org.apache.qpid.protonj2.test.driver.codec.messaging.Data;
 import org.apache.qpid.protonj2.test.driver.matchers.types.EncodedPartialDataSectionMatcher;
 import org.junit.jupiter.api.Test;
 
-import io.netty5.buffer.Buffer;
-import io.netty5.buffer.BufferAllocator;
-
 public class EncodedPartialDataSectionMatcherTest {
 
     @Test
@@ -35,19 +34,20 @@ public class EncodedPartialDataSectionMatcherTest {
         final byte[] PAYLOAD = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
         final int EXPECTED_SIZE = 256;
 
-        try (Buffer body = BufferAllocator.onHeapUnpooled().allocate(EXPECTED_SIZE)) {
-            body.writeByte(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
-            body.writeByte(EncodingCodes.SMALLULONG);
-            body.writeByte(Data.DESCRIPTOR_CODE.byteValue());
-            body.writeByte(EncodingCodes.VBIN32);
-            body.writeInt(EXPECTED_SIZE);
-            body.writeBytes(PAYLOAD);
+        final ByteBuffer body = ByteBuffer.allocate(EXPECTED_SIZE);
 
-            EncodedPartialDataSectionMatcher matcher =
-                new EncodedPartialDataSectionMatcher(EXPECTED_SIZE, PAYLOAD);
+        body.put(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
+        body.put(EncodingCodes.SMALLULONG);
+        body.put(Data.DESCRIPTOR_CODE.byteValue());
+        body.put(EncodingCodes.VBIN32);
+        body.putInt(EXPECTED_SIZE);
+        body.put(PAYLOAD);
+        body.flip();
 
-            assertThat(body, matcher);
-        }
+        EncodedPartialDataSectionMatcher matcher =
+            new EncodedPartialDataSectionMatcher(EXPECTED_SIZE, PAYLOAD);
+
+        assertThat(body, matcher);
     }
 
     @Test
@@ -55,19 +55,20 @@ public class EncodedPartialDataSectionMatcherTest {
         final byte[] PAYLOAD = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
         final int EXPECTED_SIZE = 256;
 
-        try (Buffer body = BufferAllocator.onHeapUnpooled().allocate(EXPECTED_SIZE)) {
-            body.writeByte(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
-            body.writeByte(EncodingCodes.SMALLULONG);
-            body.writeByte(Data.DESCRIPTOR_CODE.byteValue());
-            body.writeByte(EncodingCodes.VBIN32);
-            body.writeInt(EXPECTED_SIZE + 1);
-            body.writeBytes(PAYLOAD);
+        final ByteBuffer body = ByteBuffer.allocate(EXPECTED_SIZE);
+
+        body.put(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
+        body.put(EncodingCodes.SMALLULONG);
+        body.put(Data.DESCRIPTOR_CODE.byteValue());
+        body.put(EncodingCodes.VBIN32);
+        body.putInt(EXPECTED_SIZE + 1);
+        body.put(PAYLOAD);
+        body.flip();
 
-            EncodedPartialDataSectionMatcher matcher =
-                new EncodedPartialDataSectionMatcher(EXPECTED_SIZE, PAYLOAD);
+        EncodedPartialDataSectionMatcher matcher =
+            new EncodedPartialDataSectionMatcher(EXPECTED_SIZE, PAYLOAD);
 
-            assertFalse(matcher.matches(body));
-        }
+        assertFalse(matcher.matches(body));
     }
 
     @Test
@@ -75,19 +76,20 @@ public class EncodedPartialDataSectionMatcherTest {
         final byte[] PAYLOAD = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
         final int EXPECTED_SIZE = 256;
 
-        try (Buffer body = BufferAllocator.onHeapUnpooled().allocate(EXPECTED_SIZE)) {
-            body.writeByte(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
-            body.writeByte(EncodingCodes.SMALLULONG);
-            body.writeByte(AmqpValue.DESCRIPTOR_CODE.byteValue());
-            body.writeByte(EncodingCodes.VBIN32);
-            body.writeInt(EXPECTED_SIZE + 1);
-            body.writeBytes(PAYLOAD);
+        final ByteBuffer body = ByteBuffer.allocate(EXPECTED_SIZE);
 
-            EncodedPartialDataSectionMatcher matcher =
-                new EncodedPartialDataSectionMatcher(EXPECTED_SIZE, PAYLOAD);
+        body.put(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
+        body.put(EncodingCodes.SMALLULONG);
+        body.put(AmqpValue.DESCRIPTOR_CODE.byteValue());
+        body.put(EncodingCodes.VBIN32);
+        body.putInt(EXPECTED_SIZE + 1);
+        body.put(PAYLOAD);
+        body.flip();
 
-            assertFalse(matcher.matches(body));
-        }
+        EncodedPartialDataSectionMatcher matcher =
+            new EncodedPartialDataSectionMatcher(EXPECTED_SIZE, PAYLOAD);
+
+        assertFalse(matcher.matches(body));
     }
 
     @Test
@@ -95,19 +97,20 @@ public class EncodedPartialDataSectionMatcherTest {
         final byte[] PAYLOAD = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
         final int EXPECTED_SIZE = 256;
 
-        try (Buffer body = BufferAllocator.onHeapUnpooled().allocate(EXPECTED_SIZE)) {
-            body.writeByte(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
-            body.writeByte(EncodingCodes.SMALLULONG);
-            body.writeByte(Data.DESCRIPTOR_CODE.byteValue());
-            body.writeByte(EncodingCodes.SYM8);
-            body.writeInt(EXPECTED_SIZE + 1);
-            body.writeBytes(PAYLOAD);
+        final ByteBuffer body = ByteBuffer.allocate(EXPECTED_SIZE);
+
+        body.put(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
+        body.put(EncodingCodes.SMALLULONG);
+        body.put(Data.DESCRIPTOR_CODE.byteValue());
+        body.put(EncodingCodes.SYM8);
+        body.putInt(EXPECTED_SIZE + 1);
+        body.put(PAYLOAD);
+        body.flip();
 
-            EncodedPartialDataSectionMatcher matcher =
-                new EncodedPartialDataSectionMatcher(EXPECTED_SIZE, PAYLOAD);
+        EncodedPartialDataSectionMatcher matcher =
+            new EncodedPartialDataSectionMatcher(EXPECTED_SIZE, PAYLOAD);
 
-            assertFalse(matcher.matches(body));
-        }
+        assertFalse(matcher.matches(body));
     }
 
     @Test
@@ -116,18 +119,19 @@ public class EncodedPartialDataSectionMatcherTest {
         final byte[] ACTUAL_PAYLOAD = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
         final int EXPECTED_SIZE = 256;
 
-        try (Buffer body = BufferAllocator.onHeapUnpooled().allocate(EXPECTED_SIZE)) {
-            body.writeByte(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
-            body.writeByte(EncodingCodes.SMALLULONG);
-            body.writeByte(Data.DESCRIPTOR_CODE.byteValue());
-            body.writeByte(EncodingCodes.VBIN32);
-            body.writeInt(EXPECTED_SIZE + 1);
-            body.writeBytes(ACTUAL_PAYLOAD);
+        final ByteBuffer body = ByteBuffer.allocate(EXPECTED_SIZE);
+
+        body.put(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
+        body.put(EncodingCodes.SMALLULONG);
+        body.put(Data.DESCRIPTOR_CODE.byteValue());
+        body.put(EncodingCodes.VBIN32);
+        body.putInt(EXPECTED_SIZE + 1);
+        body.put(ACTUAL_PAYLOAD);
+        body.flip();
 
-            EncodedPartialDataSectionMatcher matcher =
-                new EncodedPartialDataSectionMatcher(EXPECTED_SIZE, PAYLOAD);
+        EncodedPartialDataSectionMatcher matcher =
+            new EncodedPartialDataSectionMatcher(EXPECTED_SIZE, PAYLOAD);
 
-            assertFalse(matcher.matches(body));
-        }
+        assertFalse(matcher.matches(body));
     }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org