You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@activemq.apache.org by ni...@apache.org on 2020/02/25 14:25:12 UTC

[activemq-artemis] branch master updated: ARTEMIS-1975 Real Large Message support into AMQP

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

nigrofranz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/activemq-artemis.git


The following commit(s) were added to refs/heads/master by this push:
     new ddd8ed4  ARTEMIS-1975 Real Large Message support into AMQP
     new 11a00be  This closes #2986
ddd8ed4 is described below

commit ddd8ed440226fa9099f894fa0dd5c1e03614b7da
Author: Clebert Suconic <cl...@apache.org>
AuthorDate: Wed Oct 16 11:41:00 2019 -0400

    ARTEMIS-1975 Real Large Message support into AMQP
    
    This is a Large commit where I am refactoring largeMessage Body out of CoreMessage
    which is now reused with AMQP.
    
    I had also to fix Reference Counting to fix how Large Messages are Acked
    
    And I also had to make sure Large Messages are transversing correctly when in cluster.
---
 .../cli/commands/tools/xml/XMLMessageExporter.java |  10 +-
 .../artemis/cli/commands/etc/amqp-acceptor.txt     |   2 +-
 .../activemq/artemis/cli/commands/etc/broker.xml   |   5 +-
 .../core/persistence}/CoreMessageObjectPools.java  |   2 +-
 .../artemis/core/persistence/Persister.java        |  21 +-
 .../{Persister.java => PersisterIDs.java}          |  33 +-
 .../activemq/artemis/api/core/ICoreMessage.java    |   4 +-
 .../apache/activemq/artemis/api/core/Message.java  |  40 +-
 .../activemq/artemis/api/core/RefCountMessage.java | 151 ++++-
 .../artemis/api/core/RefCountMessageListener.java  |  11 +-
 .../core/client/impl/ClientLargeMessageImpl.java   |   2 +-
 .../core/client/impl/ClientMessageImpl.java        |  22 +-
 .../core/client/impl/ClientProducerImpl.java       |   8 +-
 .../core/client/impl/ClientSessionImpl.java        |   2 +-
 ...{LargeBodyEncoder.java => LargeBodyReader.java} |  24 +-
 .../artemis/core/message/impl/CoreMessage.java     |  49 +-
 .../core/message/impl/CoreMessagePersister.java    |  11 +-
 .../core/message/impl/MessageInternalImpl.java     |  48 +-
 .../artemis/core/protocol/ClientPacketDecoder.java |   2 +-
 .../artemis/message/CoreMTMessageTest.java         |   2 +-
 .../jdbc/store/file/JDBCSequentialFile.java        |   5 +
 .../activemq/artemis/core/io/SequentialFile.java   |   2 +
 .../artemis/core/io/aio/AIOSequentialFile.java     |   5 +
 .../core/io/mapped/MappedSequentialFile.java       |   5 +
 .../core/io/mapped/TimedSequentialFile.java        |   5 +
 .../artemis/core/io/nio/NIOSequentialFile.java     |  56 ++
 .../artemis/core/journal/EncoderPersister.java     |  10 +-
 .../protocol/amqp/broker/AMQPLargeMessage.java     | 373 ++++++++++++
 .../amqp/broker/AMQPLargeMessagePersister.java     | 144 +++++
 .../artemis/protocol/amqp/broker/AMQPMessage.java  | 644 +++++++++------------
 .../protocol/amqp/broker/AMQPMessagePersister.java |   9 +-
 .../amqp/broker/AMQPMessagePersisterV2.java        |  10 +-
 .../protocol/amqp/broker/AMQPSessionCallback.java  |  29 +-
 .../protocol/amqp/broker/AMQPStandardMessage.java  | 262 +++++++++
 .../protocol/amqp/broker/AmqpReadableBuffer.java   | 227 ++++++++
 .../amqp/broker/ProtonProtocolManager.java         |  14 +
 .../amqp/broker/ProtonProtocolManagerFactory.java  |   5 +-
 .../protocol/amqp/converter/AMQPConverter.java     |   7 +-
 .../amqp/converter/AMQPMessageSupport.java         |   2 +-
 .../protocol/amqp/converter/AmqpCoreConverter.java |   2 +-
 .../protocol/amqp/converter/CoreAmqpConverter.java |  12 +-
 .../amqp/proton/AMQPConnectionContext.java         |   3 +
 .../amqp/proton/ProtonServerReceiverContext.java   |  91 ++-
 .../amqp/proton/ProtonServerSenderContext.java     | 176 +++++-
 .../amqp/proton/handler/ProtonHandler.java         |  25 +-
 .../protocol/amqp/broker/AMQPMessageTest.java      | 282 ++++-----
 .../protocol/amqp/converter/TestConversions.java   |  11 +-
 .../message/JMSMappingInboundTransformerTest.java  |   9 +-
 .../message/JMSMappingOutboundTransformerTest.java |  50 +-
 .../JMSTransformationSpeedComparisonTest.java      |  41 +-
 .../message/MessageTransformationTest.java         |  11 +-
 .../proton/ProtonServerReceiverContextTest.java    |  43 +-
 .../artemis/core/protocol/mqtt/MQTTSession.java    |   2 +-
 .../openwire/OpenWireMessageConverter.java         |   2 +-
 .../core/protocol/openwire/OpenwireMessage.java    |  39 +-
 .../core/protocol/openwire/amq/AMQSession.java     |   2 +-
 .../artemis/core/protocol/stomp/StompSession.java  |   6 +-
 .../core/paging/cursor/impl/LivePageCacheImpl.java |   5 +-
 .../activemq/artemis/core/paging/impl/Page.java    |  14 +-
 .../artemis/core/paging/impl/PagedMessageImpl.java |  14 +-
 .../artemis/core/paging/impl/PagingStoreImpl.java  |  73 +--
 .../artemis/core/persistence/StorageManager.java   |  17 +
 .../journal/AbstractJournalStorageManager.java     |  45 +-
 .../persistence/impl/journal/DescribeJournal.java  |   7 +-
 .../impl/journal/JournalStorageManager.java        |  49 +-
 .../core/persistence/impl/journal/LargeBody.java   | 450 ++++++++++++++
 .../journal/LargeMessageTXFailureCallback.java     |   2 +-
 .../impl/journal/LargeServerMessageImpl.java       | 409 +++----------
 .../impl/journal/LargeServerMessageInSync.java     |  18 +-
 .../impl/journal/codec/DeleteEncoding.java         |   2 +-
 .../impl/journal/codec/LargeMessagePersister.java  |  15 +-
 .../journal/codec/PendingLargeMessageEncoding.java |   2 +-
 .../impl/nullpm/NullStorageLargeServerMessage.java |  47 +-
 .../impl/nullpm/NullStorageManager.java            |  11 +
 .../artemis/core/postoffice/PostOffice.java        |  15 +-
 .../core/postoffice/impl/PostOfficeImpl.java       |  36 +-
 .../protocol/core/ServerSessionPacketHandler.java  |   7 +-
 .../core/impl/CoreProtocolManagerFactory.java      |   3 +-
 .../protocol/core/impl/CoreSessionCallback.java    |   2 +-
 .../core/replication/ReplicatedLargeMessage.java   |   4 +
 .../server/CoreLargeServerMessage.java}            |  14 +-
 .../artemis/core/server/LargeServerMessage.java    |  26 +-
 .../apache/activemq/artemis/core/server/Queue.java |  12 +
 .../activemq/artemis/core/server/QueueConfig.java  |  13 +-
 .../core/server/cluster/impl/BridgeImpl.java       |  15 +-
 .../artemis/core/server/impl/LastValueQueue.java   |   4 +-
 .../core/server/impl/PostOfficeJournalLoader.java  |   6 +-
 .../artemis/core/server/impl/QueueFactoryImpl.java |   8 +-
 .../artemis/core/server/impl/QueueImpl.java        | 104 +++-
 .../artemis/core/server/impl/RefsOperation.java    |  19 +-
 .../core/server/impl/ServerConsumerImpl.java       |  48 +-
 .../core/server/transformer/ServerMessageImpl.java |   8 +-
 .../spi/core/protocol/EmbedMessageUtil.java        |  88 ++-
 .../spi/core/protocol/MessageConverter.java        |   5 +-
 .../spi/core/protocol/MessagePersister.java        |  24 +-
 .../spi/core/protocol/ProtocolManagerFactory.java  |   3 +-
 .../artemis/core/server/impl/QueueImplTest.java    |   2 +-
 .../server/impl/ScheduledDeliveryHandlerTest.java  |  77 ++-
 .../core/transaction/impl/TransactionImplTest.java |  11 +
 .../artemis/tests/util/ActiveMQTestBase.java       |  18 +-
 docs/user-manual/en/large-messages.md              |  53 +-
 .../extras/byteman/RaceOnCursorIteratorTest.java   |   2 -
 .../amqp/AmqpBridgeClusterRedistributionTest.java  |  12 +-
 .../integration/amqp/AmqpClientTestSupport.java    |   4 +
 .../integration/amqp/AmqpLargeMessageTest.java     |  46 --
 .../integration/amqp/QueueAutoCreationTest.java    |   6 +-
 .../AmqpLargeMessageRedistributionTest.java        |  44 ++
 .../AmqpReplicatedLargeMessageTest.java            | 163 ++++++
 .../largemessages/AmqpReplicatedTestSupport.java   |  97 ++++
 .../SimpleStreamingLargeMessageTest.java           | 348 +++++++++++
 .../tests/integration/client/AcknowledgeTest.java  |  35 +-
 .../integration/client/ExpiryLargeMessageTest.java |  13 +-
 .../tests/integration/client/HangConsumerTest.java |   2 +-
 .../client/InterruptedLargeMessageTest.java        |   2 +-
 .../tests/integration/client/SendAckFailTest.java  |  12 +
 .../distribution/AMQPLargeMessageClusterTest.java  |  30 +-
 .../cluster/distribution/ClusterTestBase.java      |   6 +
 .../LargeMessageRedistributionTest.java            |   2 +-
 .../integration/journal/MessageJournalTest.java    |   8 +-
 .../largemessage/ServerLargeMessageTest.java       |  17 +-
 .../tests/integration/paging/PagingTest.java       |   4 +-
 .../replication/SharedNothingReplicationTest.java  |   8 +-
 .../storage/PersistMultiThreadTest.java            |   7 +-
 .../impl/fakes/FakeSequentialFileFactory.java      |   5 +
 .../unit/core/paging/impl/PagingStoreImplTest.java |   2 -
 .../tests/unit/core/postoffice/impl/FakeQueue.java |  26 +
 .../tests/unit/core/server/impl/QueueImplTest.java |   1 +
 .../core/server/impl/fakes/FakePostOffice.java     |   4 +-
 .../core/server/impl/fakes/FakeQueueFactory.java   |   4 +-
 129 files changed, 4148 insertions(+), 1637 deletions(-)

diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/tools/xml/XMLMessageExporter.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/tools/xml/XMLMessageExporter.java
index f8063d1..5e12473 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/tools/xml/XMLMessageExporter.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/tools/xml/XMLMessageExporter.java
@@ -25,7 +25,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQException;
 import org.apache.activemq.artemis.api.core.ICoreMessage;
 import org.apache.activemq.artemis.api.core.Message;
 import org.apache.activemq.artemis.api.core.SimpleString;
-import org.apache.activemq.artemis.core.message.LargeBodyEncoder;
+import org.apache.activemq.artemis.core.message.LargeBodyReader;
 import org.apache.activemq.artemis.core.server.LargeServerMessage;
 import org.apache.activemq.artemis.reader.TextMessageUtil;
 
@@ -80,14 +80,14 @@ public class XMLMessageExporter {
 
    public void printLargeMessageBody(LargeServerMessage message) throws XMLStreamException {
       xmlWriter.writeAttribute(XmlDataConstants.MESSAGE_IS_LARGE, Boolean.TRUE.toString());
-      LargeBodyEncoder encoder = null;
+      LargeBodyReader encoder = null;
 
       try {
-         encoder = message.toCore().getBodyEncoder();
+         encoder = message.toMessage().toCore().getLargeBodyReader();
          encoder.open();
          long totalBytesWritten = 0;
          int bufferSize;
-         long bodySize = encoder.getLargeBodySize();
+         long bodySize = encoder.getSize();
          ByteBuffer buffer = null;
          for (long i = 0; i < bodySize; i += LARGE_MESSAGE_CHUNK_SIZE) {
             long remainder = bodySize - totalBytesWritten;
@@ -97,7 +97,7 @@ public class XMLMessageExporter {
                bufferSize = (int) remainder;
             }
             buffer = acquireHeapBodyBuffer(buffer, bufferSize);
-            encoder.encode(buffer);
+            encoder.readInto(buffer);
             xmlWriter.writeCData(XmlDataExporterUtil.encode(buffer.array()));
             totalBytesWritten += bufferSize;
          }
diff --git a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/amqp-acceptor.txt b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/amqp-acceptor.txt
index e241ade..33153f8 100644
--- a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/amqp-acceptor.txt
+++ b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/amqp-acceptor.txt
@@ -1,3 +1,3 @@
 
          <!-- AMQP Acceptor.  Listens on default AMQP port for AMQP traffic.-->
-         <acceptor name="amqp">tcp://${host}:${amqp.port}?tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;protocols=AMQP;useEpoll=true;amqpCredits=1000;amqpLowCredits=300;amqpDuplicateDetection=true</acceptor>
+         <acceptor name="amqp">tcp://${host}:${amqp.port}?tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;protocols=AMQP;useEpoll=true;amqpCredits=1000;amqpLowCredits=300;amqpMinLargeMessageSize=102400;amqpDuplicateDetection=true</acceptor>
diff --git a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/broker.xml b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/broker.xml
index b34827e..dabfc44 100644
--- a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/broker.xml
+++ b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/broker.xml
@@ -83,13 +83,16 @@ ${jdbc}
          <!-- amqpLowCredits: The server will send the # credits specified at amqpCredits at this low mark -->
          <!-- amqpDuplicateDetection: If you are not using duplicate detection, set this to false
                                       as duplicate detection requires applicationProperties to be parsed on the server. -->
+         <!-- amqpMinLargeMessageSize: Determines how many bytes are considered large, so we start using files to hold their data.
+                                       default: 102400, -1 would mean to disable large mesasge control -->
 
          <!-- Note: If an acceptor needs to be compatible with HornetQ and/or Artemis 1.x clients add
                     "anycastPrefix=jms.queue.;multicastPrefix=jms.topic." to the acceptor url.
                     See https://issues.apache.org/jira/browse/ARTEMIS-1644 for more information. -->
 
+
          <!-- Acceptor for every supported protocol -->
-         <acceptor name="artemis">tcp://${host}:${default.port}?tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;protocols=CORE,AMQP,STOMP,HORNETQ,MQTT,OPENWIRE;useEpoll=true;amqpCredits=1000;amqpLowCredits=300;amqpDuplicateDetection=true</acceptor>
+         <acceptor name="artemis">tcp://${host}:${default.port}?tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;amqpMinLargeMessageSize=102400;protocols=CORE,AMQP,STOMP,HORNETQ,MQTT,OPENWIRE;useEpoll=true;amqpCredits=1000;amqpLowCredits=300;amqpDuplicateDetection=true</acceptor>
 ${amqp-acceptor}${stomp-acceptor}${hornetq-acceptor}${mqtt-acceptor}
       </acceptors>
 
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/impl/CoreMessageObjectPools.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/core/persistence/CoreMessageObjectPools.java
similarity index 98%
rename from artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/impl/CoreMessageObjectPools.java
rename to artemis-commons/src/main/java/org/apache/activemq/artemis/core/persistence/CoreMessageObjectPools.java
index 62ee5ed..da0d1a0 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/impl/CoreMessageObjectPools.java
+++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/core/persistence/CoreMessageObjectPools.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.activemq.artemis.core.message.impl;
+package org.apache.activemq.artemis.core.persistence;
 
 
 import org.apache.activemq.artemis.api.core.SimpleString;
diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/core/persistence/Persister.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/core/persistence/Persister.java
index bf8df3f..a8bde4f 100644
--- a/artemis-commons/src/main/java/org/apache/activemq/artemis/core/persistence/Persister.java
+++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/core/persistence/Persister.java
@@ -19,22 +19,19 @@ package org.apache.activemq.artemis.core.persistence;
 
 import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
 
-public interface Persister<T extends Object, A> {
-
-   /**
-    * This is to be used to store the protocol-id on Messages.
-    * Messages are stored on their bare format.
-    * The protocol manager will be responsible to code or decode messages.
-    * The caveat here is that the first short-sized bytes need to be this constant.
-    */
-   default byte getID() {
-      return (byte) 0;
-   }
+public interface Persister<T extends Object> {
+
+
+   /** This is to be used to store the protocol-id on Messages.
+    *  Messages are stored on their bare format.
+    *  The protocol manager will be responsible to code or decode messages.
+    *  The caveat here is that the first short-sized bytes need to be this constant. */
+   byte getID();
 
    int getEncodeSize(T record);
 
    void encode(ActiveMQBuffer buffer, T record);
 
-   T decode(ActiveMQBuffer buffer, A arg);
+   T decode(ActiveMQBuffer buffer, T record, CoreMessageObjectPools pool);
 
 }
diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/core/persistence/Persister.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/core/persistence/PersisterIDs.java
similarity index 56%
copy from artemis-commons/src/main/java/org/apache/activemq/artemis/core/persistence/Persister.java
copy to artemis-commons/src/main/java/org/apache/activemq/artemis/core/persistence/PersisterIDs.java
index bf8df3f..d51accb 100644
--- a/artemis-commons/src/main/java/org/apache/activemq/artemis/core/persistence/Persister.java
+++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/core/persistence/PersisterIDs.java
@@ -1,4 +1,4 @@
-/**
+/*
  * 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.
@@ -6,7 +6,7 @@
  * (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
+ *     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,
@@ -17,24 +17,25 @@
 
 package org.apache.activemq.artemis.core.persistence;
 
-import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
 
-public interface Persister<T extends Object, A> {
 
-   /**
-    * This is to be used to store the protocol-id on Messages.
-    * Messages are stored on their bare format.
-    * The protocol manager will be responsible to code or decode messages.
-    * The caveat here is that the first short-sized bytes need to be this constant.
-    */
-   default byte getID() {
-      return (byte) 0;
-   }
+/** this is a list for all the persisters
+    The sole purpose of this is to make sure these IDs will not be duplicate
+     so we know where to find IDs.
+*/
 
-   int getEncodeSize(T record);
+public class PersisterIDs {
 
-   void encode(ActiveMQBuffer buffer, T record);
+   public static final int MAX_PERSISTERS = 4;
 
-   T decode(ActiveMQBuffer buffer, A arg);
+   public static final byte CoreLargeMessagePersister_ID = (byte)0;
+
+   public static final byte CoreMessagePersister_ID = (byte)1;
+
+   public static final byte AMQPMessagePersister_ID = (byte)2;
+
+   public static final byte AMQPMessagePersisterV2_ID = (byte)3;
+
+   public static final byte AMQPLargeMessagePersister_ID = (byte)4;
 
 }
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/ICoreMessage.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/ICoreMessage.java
index b76764e..b9bcc22 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/ICoreMessage.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/ICoreMessage.java
@@ -21,7 +21,7 @@ import java.io.InputStream;
 import java.util.Map;
 
 import io.netty.buffer.ByteBuf;
-import org.apache.activemq.artemis.core.message.LargeBodyEncoder;
+import org.apache.activemq.artemis.core.message.LargeBodyReader;
 import org.apache.activemq.artemis.core.message.impl.CoreMessage;
 
 /**
@@ -29,7 +29,7 @@ import org.apache.activemq.artemis.core.message.impl.CoreMessage;
  */
 public interface ICoreMessage extends Message {
 
-   LargeBodyEncoder getBodyEncoder() throws ActiveMQException;
+   LargeBodyReader getLargeBodyReader() throws ActiveMQException;
 
    int getHeadersAndPropertiesEncodeSize();
 
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/Message.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/Message.java
index 09c2d39..a8b54c7 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/Message.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/Message.java
@@ -23,7 +23,7 @@ import java.util.Set;
 import java.util.function.Predicate;
 
 import io.netty.buffer.ByteBuf;
-import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
 import org.apache.activemq.artemis.core.persistence.Persister;
 
 /**
@@ -182,6 +182,9 @@ public interface Message {
    /** The message will contain another message persisted through {@link org.apache.activemq.artemis.spi.core.protocol.EmbedMessageUtil}*/
    byte EMBEDDED_TYPE = 7;
 
+   /** This is to embedd Large Messages from other protocol */
+   byte LARGE_EMBEDDED_TYPE = 8;
+
    default void clearInternalProperties() {
       // only on core
    }
@@ -255,13 +258,6 @@ public interface Message {
       return this;
    }
 
-   /** Context can be used by the application server to inject extra control, like a protocol specific on the server.
-    * There is only one per Object, use it wisely!
-    *
-    * Note: the intent of this was to replace PageStore reference on Message, but it will be later increased by adidn a ServerPojo
-    * */
-   RefCountMessageListener getContext();
-
    default SimpleString getGroupID() {
       return null;
    }
@@ -295,8 +291,6 @@ public interface Message {
 
    Message setReplyTo(SimpleString address);
 
-   Message setContext(RefCountMessageListener context);
-
    /** The buffer will belong to this message, until release is called. */
    Message setBuffer(ByteBuf buffer);
 
@@ -394,7 +388,7 @@ public interface Message {
     */
    Message setDurable(boolean durable);
 
-   Persister<Message, CoreMessageObjectPools> getPersister();
+   Persister<Message> getPersister();
 
    String getAddress();
 
@@ -671,13 +665,29 @@ public interface Message {
 
    int getRefCount();
 
-   int incrementRefCount() throws Exception;
+   int getUsage();
+
+   int getDurableCount();
+
+   /** this method indicates usage by components such as large message or page cache.
+    *  This method will cause large messages to be held longer after the ack happened for instance.
+    */
+   int usageUp();
+
+   /**
+    * @see #usageUp()
+    * @return
+    * @throws Exception
+    */
+   int usageDown();
+
+   int refUp();
 
-   int decrementRefCount() throws Exception;
+   int refDown();
 
-   int incrementDurableRefCount();
+   int durableUp();
 
-   int decrementDurableRefCount();
+   int durableDown();
 
    /**
     * @return Returns the message in Map form, useful when encoding to JSON
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/RefCountMessage.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/RefCountMessage.java
index 5754bcc..35d591c 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/RefCountMessage.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/RefCountMessage.java
@@ -19,66 +19,155 @@ package org.apache.activemq.artemis.api.core;
 
 import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
 
-public abstract class RefCountMessage implements Message {
+// import org.apache.activemq.artemis.utils.collections.ConcurrentHashSet; -- #ifdef DEBUG
+
+public class RefCountMessage {
 
    private static final AtomicIntegerFieldUpdater<RefCountMessage> DURABLE_REF_COUNT_UPDATER = AtomicIntegerFieldUpdater.newUpdater(RefCountMessage.class, "durableRefCount");
    private static final AtomicIntegerFieldUpdater<RefCountMessage> REF_COUNT_UPDATER = AtomicIntegerFieldUpdater.newUpdater(RefCountMessage.class, "refCount");
+   private static final AtomicIntegerFieldUpdater<RefCountMessage> REF_USAGE_UPDATER = AtomicIntegerFieldUpdater.newUpdater(RefCountMessage.class, "usageCount");
 
    private volatile int durableRefCount = 0;
 
    private volatile int refCount = 0;
 
-   private RefCountMessageListener context;
+   private volatile int usageCount = 0;
+
+   private volatile boolean fired = false;
 
-   @Override
-   public Message setContext(RefCountMessageListener context) {
-      this.context = context;
-      return this;
+   public int getRefCount() {
+      return REF_COUNT_UPDATER.get(this);
    }
 
-   @Override
-   public RefCountMessageListener getContext() {
-      return context;
+   public int getUsage() {
+      return REF_USAGE_UPDATER.get(this);
    }
 
-   @Override
-   public int getRefCount() {
-      return refCount;
+   public int getDurableCount() {
+      return DURABLE_REF_COUNT_UPDATER.get(this);
    }
 
-   @Override
-   public int incrementRefCount() throws Exception {
-      int count = REF_COUNT_UPDATER.incrementAndGet(this);
-      if (context != null) {
-         context.nonDurableUp(this, count);
+   /**
+    * in certain cases the large message is copied from another LargeServerMessage
+    * and the ref count needs to be on that place.
+    */
+   private volatile RefCountMessage parentRef;
+
+   public RefCountMessage getParentRef() {
+      return parentRef;
+   }
+   // I am usually against keeping commented out code
+   // However this is very useful for me to debug referencing counting.
+   // Uncomment out anything between #ifdef DEBUG and #endif
+
+   // #ifdef DEBUG -- comment out anything before endif if you want to debug REFERENCE COUNTS
+   //final ConcurrentHashSet<Exception> upSet = new ConcurrentHashSet<>();
+   // #endif
+
+   private void onUp() {
+      // #ifdef DEBUG -- comment out anything before endif if you want to debug REFERENCE COUNTS
+      // upSet.add(new Exception("upEvent(" + debugString() + ")"));
+      // #endif
+   }
+
+   private void onDown() {
+      // #ifdef DEBUG -- comment out anything before endif if you want to debug REFERENCE COUNTS
+      // upSet.add(new Exception("upEvent(" + debugString() + ")"));
+      // #endif
+      if (getRefCount() <= 0 && getUsage() <= 0 && getDurableCount() <= 0 && !fired) {
+
+         debugRefs();
+         fired = true;
+         releaseComplete();
+      }
+   }
+   /**
+    *
+    * This method will be useful if you remove commented out code around #ifdef AND #endif COMMENTS
+    * */
+   public final void debugRefs() {
+      // #ifdef DEBUG -- comment out anything before endif if you want to debug REFERENCE COUNTS
+      //   try {
+      //      System.err.println("************************************************************************************************************************");
+      //      System.err.println("Printing refcounts for " + debugString() + " this = " + this);
+      //      for (Exception e : upSet) {
+      //         e.printStackTrace();
+      //      }
+      //      System.err.println("************************************************************************************************************************");
+      //   } catch (Throwable e) {
+      //      e.printStackTrace();
+      //   }
+      // #ifdef DEBUG -- comment out anything before endif if you want to debug REFERENCE COUNTS
+   }
+
+
+
+   public String debugString() {
+      return "refCount=" + getRefCount() + ", durableRefCount=" + getDurableCount() + ", usageCount=" + getUsage() + ", parentRef=" + this.parentRef;
+   }
+   public void setParentRef(RefCountMessage origin) {
+      // if copy of a copy.. just go to the parent:
+      if (origin.getParentRef() != null) {
+         this.parentRef = origin.getParentRef();
+      } else {
+         this.parentRef = origin;
       }
+   }
+
+   protected void releaseComplete() {
+   }
+
+   public int usageUp() {
+      if (parentRef != null) {
+         return parentRef.usageUp();
+      }
+      int count = REF_USAGE_UPDATER.incrementAndGet(this);
+      onUp();
+      return count;
+   }
+   public int usageDown() {
+      if (parentRef != null) {
+         return parentRef.usageDown();
+      }
+      int count = REF_USAGE_UPDATER.decrementAndGet(this);
+      onDown();
       return count;
    }
 
-   @Override
-   public int incrementDurableRefCount() {
-      int count = DURABLE_REF_COUNT_UPDATER.incrementAndGet(this);
-      if (context != null) {
-         context.durableUp(this, count);
+   public int durableUp() {
+      if (parentRef != null) {
+         return parentRef.durableUp();
       }
+      int count = DURABLE_REF_COUNT_UPDATER.incrementAndGet(this);
+      onUp();
       return count;
    }
 
-   @Override
-   public int decrementDurableRefCount() {
-      int count = DURABLE_REF_COUNT_UPDATER.decrementAndGet(this);
-      if (context != null) {
-         context.durableDown(this, count);
+   public int durableDown() {
+      if (parentRef != null) {
+         return parentRef.durableDown();
       }
+      int count = DURABLE_REF_COUNT_UPDATER.decrementAndGet(this);
+      onDown();
       return count;
    }
 
-   @Override
-   public int decrementRefCount() throws Exception {
+   public int refDown() {
+      if (parentRef != null) {
+         return parentRef.refDown();
+      }
       int count = REF_COUNT_UPDATER.decrementAndGet(this);
-      if (context != null) {
-         context.nonDurableDown(this, count);
+      onDown();
+      return count;
+   }
+
+   public int refUp() {
+      if (parentRef != null) {
+         return parentRef.refUp();
       }
+      int count = REF_COUNT_UPDATER.incrementAndGet(this);
+      onUp();
       return count;
    }
+
 }
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/RefCountMessageListener.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/RefCountMessageListener.java
index e68dffd..91fa7f8 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/RefCountMessageListener.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/RefCountMessageListener.java
@@ -25,7 +25,14 @@ public interface RefCountMessageListener {
 
    void durableDown(Message message, int durableCount);
 
-   void nonDurableUp(Message message, int nonDurableCoun);
+   void refUp(Message message, int nonDurableCoun);
 
-   void nonDurableDown(Message message, int nonDurableCoun);
+   void refDown(Message message, int nonDurableCoun);
+
+   default void usageUp(Message message, int usageCount) {
+   }
+
+   default void usageDown(Message message, int usageCount) {
+
+   }
 }
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientLargeMessageImpl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientLargeMessageImpl.java
index cbfaf6f..92a808a 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientLargeMessageImpl.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientLargeMessageImpl.java
@@ -177,7 +177,7 @@ public final class ClientLargeMessageImpl extends ClientMessageImpl implements C
    }
 
    public void retrieveExistingData(ClientMessageInternal clMessage) {
-      this.messageID = clMessage.getMessageID();
+      this.internalSetMessageID(clMessage.getMessageID());
       this.address = clMessage.getAddressSimpleString();
       this.setUserID(clMessage.getUserID());
       this.setFlowControlSize(clMessage.getFlowControlSize());
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientMessageImpl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientMessageImpl.java
index ae9093d..c1739a1 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientMessageImpl.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientMessageImpl.java
@@ -26,9 +26,9 @@ import org.apache.activemq.artemis.api.core.ActiveMQPropertyConversionException;
 import org.apache.activemq.artemis.api.core.Message;
 import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.core.client.ActiveMQClientMessageBundle;
-import org.apache.activemq.artemis.core.message.LargeBodyEncoder;
+import org.apache.activemq.artemis.core.message.LargeBodyReader;
 import org.apache.activemq.artemis.core.message.impl.CoreMessage;
-import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
 import org.apache.activemq.artemis.reader.MessageUtil;
 import org.apache.activemq.artemis.utils.UUID;
 
@@ -235,7 +235,7 @@ public class ClientMessageImpl extends CoreMessage implements ClientMessageInter
    }
 
    @Override
-   public LargeBodyEncoder getBodyEncoder() throws ActiveMQException {
+   public LargeBodyReader getLargeBodyReader() throws ActiveMQException {
       return new DecodingContext();
    }
 
@@ -368,7 +368,7 @@ public class ClientMessageImpl extends CoreMessage implements ClientMessageInter
       return this;
    }
 
-   private final class DecodingContext implements LargeBodyEncoder {
+   private final class DecodingContext implements LargeBodyReader {
 
       private DecodingContext() {
       }
@@ -383,7 +383,7 @@ public class ClientMessageImpl extends CoreMessage implements ClientMessageInter
       }
 
       @Override
-      public long getLargeBodySize() {
+      public long getSize() {
          if (isLargeMessage()) {
             return getBodyBuffer().writerIndex();
          } else {
@@ -392,7 +392,17 @@ public class ClientMessageImpl extends CoreMessage implements ClientMessageInter
       }
 
       @Override
-      public int encode(final ByteBuffer bufferRead) {
+      public void position(long position) {
+         buffer.readerIndex((int)position);
+      }
+
+      @Override
+      public long position() {
+         return buffer.readerIndex();
+      }
+
+      @Override
+      public int readInto(final ByteBuffer bufferRead) {
          final int remaining = bufferRead.remaining();
          buffer.readBytes(bufferRead);
          return remaining;
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientProducerImpl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientProducerImpl.java
index a55a5b1..06cacd2 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientProducerImpl.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientProducerImpl.java
@@ -27,7 +27,7 @@ import org.apache.activemq.artemis.api.core.Message;
 import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.api.core.client.SendAcknowledgementHandler;
 import org.apache.activemq.artemis.core.client.ActiveMQClientMessageBundle;
-import org.apache.activemq.artemis.core.message.LargeBodyEncoder;
+import org.apache.activemq.artemis.core.message.LargeBodyReader;
 import org.apache.activemq.artemis.spi.core.remoting.SessionContext;
 import org.apache.activemq.artemis.utils.ActiveMQBufferInputStream;
 import org.apache.activemq.artemis.utils.DeflaterReader;
@@ -362,9 +362,9 @@ public class ClientProducerImpl implements ClientProducerInternal {
                                        SendAcknowledgementHandler handler) throws ActiveMQException {
       sendInitialLargeMessageHeader(msgI, credits);
 
-      LargeBodyEncoder context = msgI.getBodyEncoder();
+      LargeBodyReader context = msgI.getLargeBodyReader();
 
-      final long bodySize = context.getLargeBodySize();
+      final long bodySize = context.getSize();
       context.open();
       try {
 
@@ -375,7 +375,7 @@ public class ClientProducerImpl implements ClientProducerInternal {
 
             final ByteBuffer bodyBuffer = ByteBuffer.allocate(chunkLength);
 
-            final int encodedSize = context.encode(bodyBuffer);
+            final int encodedSize = context.readInto(bodyBuffer);
 
             assert encodedSize == chunkLength;
 
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientSessionImpl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientSessionImpl.java
index ec2dc79..6684923 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientSessionImpl.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientSessionImpl.java
@@ -46,7 +46,7 @@ import org.apache.activemq.artemis.api.core.client.SendAcknowledgementHandler;
 import org.apache.activemq.artemis.api.core.client.SessionFailureListener;
 import org.apache.activemq.artemis.core.client.ActiveMQClientLogger;
 import org.apache.activemq.artemis.core.client.ActiveMQClientMessageBundle;
-import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
 import org.apache.activemq.artemis.core.remoting.FailureListener;
 import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
 import org.apache.activemq.artemis.spi.core.remoting.ConsumerContext;
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/LargeBodyEncoder.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/LargeBodyReader.java
similarity index 70%
rename from artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/LargeBodyEncoder.java
rename to artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/LargeBodyReader.java
index 87e5ba6..42de11b 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/LargeBodyEncoder.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/LargeBodyReader.java
@@ -21,11 +21,13 @@ import java.nio.ByteBuffer;
 import org.apache.activemq.artemis.api.core.ActiveMQException;
 
 /**
- * Class used to encode message body into buffers.
+ * Class used to readInto message body into buffers.
  * <br>
  * Used to send large streams over the wire
+ *
+ * None of these methods should be caleld from Clients
  */
-public interface LargeBodyEncoder {
+public interface LargeBodyReader {
 
    /**
     * This method must not be called directly by ActiveMQ Artemis clients.
@@ -34,16 +36,30 @@ public interface LargeBodyEncoder {
 
    /**
     * This method must not be called directly by ActiveMQ Artemis clients.
+    *
+    * This is the reading position.
+    */
+   void position(long position) throws ActiveMQException;
+
+   /**
+    * This method must not be called directly by ActiveMQ Artemis clients.
+    *
+    * This is the reading position.
+    */
+   long position();
+
+   /**
+    * This method must not be called directly by ActiveMQ Artemis clients.
     */
    void close() throws ActiveMQException;
 
    /**
     * This method must not be called directly by ActiveMQ Artemis clients.
     */
-   int encode(ByteBuffer bufferRead) throws ActiveMQException;
+   int readInto(ByteBuffer bufferRead) throws ActiveMQException;
 
    /**
     * This method must not be called directly by ActiveMQ Artemis clients.
     */
-   long getLargeBodySize() throws ActiveMQException;
+   long getSize() throws ActiveMQException;
 }
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/impl/CoreMessage.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/impl/CoreMessage.java
index b61b27e..86ca28a 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/impl/CoreMessage.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/impl/CoreMessage.java
@@ -39,7 +39,8 @@ import org.apache.activemq.artemis.api.core.RoutingType;
 import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.core.buffers.impl.ChannelBufferWrapper;
 import org.apache.activemq.artemis.core.buffers.impl.ResetLimitWrappedActiveMQBuffer;
-import org.apache.activemq.artemis.core.message.LargeBodyEncoder;
+import org.apache.activemq.artemis.core.message.LargeBodyReader;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
 import org.apache.activemq.artemis.core.persistence.Persister;
 import org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl;
 import org.apache.activemq.artemis.reader.MessageUtil;
@@ -62,7 +63,7 @@ public class CoreMessage extends RefCountMessage implements ICoreMessage {
    // There's an integer with the number of bytes for the body
    public static final int BODY_OFFSET = DataConstants.SIZE_INT;
 
-   /** That is the encode for the whole message, including properties..
+   /** That is the readInto for the whole message, including properties..
        it does not include the buffer for the Packet send and receive header on core protocol */
    protected ByteBuf buffer;
 
@@ -130,7 +131,7 @@ public class CoreMessage extends RefCountMessage implements ICoreMessage {
    }
 
    @Override
-   public Persister<Message, CoreMessageObjectPools> getPersister() {
+   public Persister<Message> getPersister() {
       return CoreMessagePersister.getInstance();
    }
 
@@ -237,13 +238,13 @@ public class CoreMessage extends RefCountMessage implements ICoreMessage {
    }
 
    private ActiveMQBuffer getLargeMessageBuffer() throws ActiveMQException {
-      LargeBodyEncoder encoder = getBodyEncoder();
+      LargeBodyReader encoder = getLargeBodyReader();
       encoder.open();
-      int bodySize = (int) encoder.getLargeBodySize();
+      int bodySize = (int) encoder.getSize();
       final ActiveMQBuffer buffer = new ChannelBufferWrapper(UnpooledByteBufAllocator.DEFAULT.heapBuffer(bodySize));
       buffer.byteBuf().ensureWritable(bodySize);
       final ByteBuffer nioBuffer = buffer.byteBuf().internalNioBuffer(0, bodySize);
-      encoder.encode(nioBuffer);
+      encoder.readInto(nioBuffer);
       buffer.writerIndex(bodySize);
       encoder.close();
       return buffer;
@@ -427,7 +428,7 @@ public class CoreMessage extends RefCountMessage implements ICoreMessage {
       synchronized (other) {
          this.body = other.body;
          this.endOfBodyPosition = other.endOfBodyPosition;
-         this.messageID = other.messageID;
+         internalSetMessageID(other.messageID);
          this.address = other.address;
          this.type = other.type;
          this.durable = other.durable;
@@ -445,9 +446,15 @@ public class CoreMessage extends RefCountMessage implements ICoreMessage {
       }
    }
 
+   /** This method serves as a purpose of extension.
+    *   Large Message on a Core Message will have to set the messageID on the attached NewLargeMessage */
+   protected void internalSetMessageID(final long messageID) {
+      this.messageID = messageID;
+   }
+
    @Override
    public void moveHeadersAndProperties(final Message msg) {
-      messageID = msg.getMessageID();
+      internalSetMessageID(msg.getMessageID());
       address = msg.getAddressSimpleString();
       userID = (UUID) msg.getUserID();
       type = msg.toCore().getType();
@@ -523,7 +530,7 @@ public class CoreMessage extends RefCountMessage implements ICoreMessage {
 
    @Override
    public CoreMessage setMessageID(long messageID) {
-      this.messageID = messageID;
+      internalSetMessageID(messageID);
       if (messageIDPosition >= 0 && validBuffer) {
          buffer.setLong(messageIDPosition, messageID);
       }
@@ -671,7 +678,7 @@ public class CoreMessage extends RefCountMessage implements ICoreMessage {
 
    private void decodeHeadersAndProperties(final ByteBuf buffer, boolean lazyProperties, CoreMessageObjectPools pools) {
       messageIDPosition = buffer.readerIndex();
-      messageID = buffer.readLong();
+      internalSetMessageID(buffer.readLong());
 
       address = SimpleString.readNullableSimpleString(buffer, pools == null ? null : pools.getAddressDecoderPool());
       if (buffer.readByte() == DataConstants.NOT_NULL) {
@@ -1137,15 +1144,15 @@ public class CoreMessage extends RefCountMessage implements ICoreMessage {
    }
 
    @Override
-   public LargeBodyEncoder getBodyEncoder() throws ActiveMQException {
-      return new DecodingContext();
+   public LargeBodyReader getLargeBodyReader() throws ActiveMQException {
+      return new CoreLargeBodyReaderImpl();
    }
 
-   private final class DecodingContext implements LargeBodyEncoder {
+   private final class CoreLargeBodyReaderImpl implements LargeBodyReader {
 
       private int lastPos = 0;
 
-      private DecodingContext() {
+      private CoreLargeBodyReaderImpl() {
       }
 
       @Override
@@ -1153,16 +1160,26 @@ public class CoreMessage extends RefCountMessage implements ICoreMessage {
       }
 
       @Override
+      public void position(long position) throws ActiveMQException {
+         lastPos = (int)position;
+      }
+
+      @Override
+      public long position() {
+         return lastPos;
+      }
+
+      @Override
       public void close() {
       }
 
       @Override
-      public long getLargeBodySize() {
+      public long getSize() {
          return buffer.writerIndex();
       }
 
       @Override
-      public int encode(final ByteBuffer bufferRead) {
+      public int readInto(final ByteBuffer bufferRead) {
          final int remaining = bufferRead.remaining();
          buffer.getBytes(lastPos, bufferRead);
          lastPos += remaining;
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/impl/CoreMessagePersister.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/impl/CoreMessagePersister.java
index 3861d67..d49c19c 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/impl/CoreMessagePersister.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/impl/CoreMessagePersister.java
@@ -20,11 +20,13 @@ package org.apache.activemq.artemis.core.message.impl;
 import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
 import org.apache.activemq.artemis.api.core.Message;
 import org.apache.activemq.artemis.api.core.SimpleString;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
 import org.apache.activemq.artemis.core.persistence.Persister;
 import org.apache.activemq.artemis.utils.DataConstants;
 
-public class CoreMessagePersister implements Persister<Message, CoreMessageObjectPools> {
-   public static final byte ID = 1;
+import static org.apache.activemq.artemis.core.persistence.PersisterIDs.CoreMessagePersister_ID;
+public class CoreMessagePersister implements Persister<Message> {
+   public static final byte ID = CoreMessagePersister_ID;
 
    private static CoreMessagePersister theInstance;
 
@@ -68,8 +70,9 @@ public class CoreMessagePersister implements Persister<Message, CoreMessageObjec
       record.persist(buffer);
    }
 
+
    @Override
-   public Message decode(ActiveMQBuffer buffer, CoreMessageObjectPools pool) {
+   public Message decode(ActiveMQBuffer buffer, Message record, CoreMessageObjectPools pool) {
       // the caller must consume the first byte already, as that will be used to decide what persister (protocol) to use
       long id = buffer.readLong();
       final SimpleString address;
@@ -78,7 +81,7 @@ public class CoreMessagePersister implements Persister<Message, CoreMessageObjec
       } else {
          address = SimpleString.readNullableSimpleString(buffer.byteBuf(), pool.getAddressDecoderPool());
       }
-      CoreMessage record = new CoreMessage();
+      record = new CoreMessage();
       record.reloadPersistence(buffer, pool);
       record.setMessageID(id);
       record.setAddress(address);
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/impl/MessageInternalImpl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/impl/MessageInternalImpl.java
index 0f809ad..92e2328 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/impl/MessageInternalImpl.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/impl/MessageInternalImpl.java
@@ -25,9 +25,9 @@ import org.apache.activemq.artemis.api.core.ActiveMQException;
 import org.apache.activemq.artemis.api.core.ActiveMQPropertyConversionException;
 import org.apache.activemq.artemis.api.core.ICoreMessage;
 import org.apache.activemq.artemis.api.core.Message;
-import org.apache.activemq.artemis.api.core.RefCountMessageListener;
 import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.core.message.BodyEncoder;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
 import org.apache.activemq.artemis.core.persistence.Persister;
 import org.apache.activemq.artemis.utils.TypedProperties;
 
@@ -114,17 +114,6 @@ public class MessageInternalImpl implements MessageInternal {
       return message.getScheduledDeliveryTime();
    }
 
-   /**
-    * Context can be used by the application server to inject extra control, like a protocol specific on the server.
-    * There is only one per Object, use it wisely!
-    *
-    * Note: the intent of this was to replace PageStore reference on Message, but it will be later increased by adidn a ServerPojo
-    */
-   @Override
-   public RefCountMessageListener getContext() {
-      throw new UnsupportedOperationException();
-   }
-
    @Override
    public SimpleString getReplyTo() {
       return message.getReplyTo();
@@ -136,11 +125,6 @@ public class MessageInternalImpl implements MessageInternal {
       return this;
    }
 
-   @Override
-   public Message setContext(RefCountMessageListener context) {
-      throw new UnsupportedOperationException();
-   }
-
    /**
     * The buffer will belong to this message, until release is called.
     *
@@ -248,7 +232,7 @@ public class MessageInternalImpl implements MessageInternal {
    }
 
    @Override
-   public Persister<Message, CoreMessageObjectPools> getPersister() {
+   public Persister<Message> getPersister() {
       throw new UnsupportedOperationException();
    }
 
@@ -643,22 +627,42 @@ public class MessageInternalImpl implements MessageInternal {
    }
 
    @Override
-   public int incrementRefCount() throws Exception {
+   public int getDurableCount() {
+      return 0;
+   }
+
+   @Override
+   public int refUp() {
+      throw new UnsupportedOperationException();
+   }
+
+   @Override
+   public int refDown() {
       throw new UnsupportedOperationException();
    }
 
    @Override
-   public int decrementRefCount() throws Exception {
+   public int usageUp() {
       throw new UnsupportedOperationException();
    }
 
    @Override
-   public int incrementDurableRefCount() {
+   public int usageDown() {
+      return 0;
+   }
+
+   @Override
+   public int getUsage() {
+      return 0;
+   }
+
+   @Override
+   public int durableUp() {
       throw new UnsupportedOperationException();
    }
 
    @Override
-   public int decrementDurableRefCount() {
+   public int durableDown() {
       throw new UnsupportedOperationException();
    }
 
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/ClientPacketDecoder.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/ClientPacketDecoder.java
index ad8c7a9..3f79ab5 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/ClientPacketDecoder.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/ClientPacketDecoder.java
@@ -19,7 +19,7 @@ package org.apache.activemq.artemis.core.protocol;
 import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
 import org.apache.activemq.artemis.core.client.impl.ClientLargeMessageImpl;
 import org.apache.activemq.artemis.core.client.impl.ClientMessageImpl;
-import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
 import org.apache.activemq.artemis.core.protocol.core.CoreRemotingConnection;
 import org.apache.activemq.artemis.core.protocol.core.Packet;
 import org.apache.activemq.artemis.core.protocol.core.impl.PacketDecoder;
diff --git a/artemis-core-client/src/test/java/org/apache/activemq/artemis/message/CoreMTMessageTest.java b/artemis-core-client/src/test/java/org/apache/activemq/artemis/message/CoreMTMessageTest.java
index 8553d20..e1abd4e 100644
--- a/artemis-core-client/src/test/java/org/apache/activemq/artemis/message/CoreMTMessageTest.java
+++ b/artemis-core-client/src/test/java/org/apache/activemq/artemis/message/CoreMTMessageTest.java
@@ -27,7 +27,7 @@ import org.apache.activemq.artemis.api.core.Message;
 import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.core.client.impl.ClientMessageImpl;
 import org.apache.activemq.artemis.core.message.impl.CoreMessage;
-import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
 import org.apache.activemq.artemis.reader.TextMessageUtil;
 import org.apache.activemq.artemis.utils.UUID;
 import org.apache.activemq.artemis.utils.UUIDGenerator;
diff --git a/artemis-jdbc-store/src/main/java/org/apache/activemq/artemis/jdbc/store/file/JDBCSequentialFile.java b/artemis-jdbc-store/src/main/java/org/apache/activemq/artemis/jdbc/store/file/JDBCSequentialFile.java
index 8bbf7b9..83dee08 100644
--- a/artemis-jdbc-store/src/main/java/org/apache/activemq/artemis/jdbc/store/file/JDBCSequentialFile.java
+++ b/artemis-jdbc-store/src/main/java/org/apache/activemq/artemis/jdbc/store/file/JDBCSequentialFile.java
@@ -143,6 +143,11 @@ public class JDBCSequentialFile implements SequentialFile {
    }
 
    @Override
+   public ByteBuffer map(int position, long size) throws IOException {
+      return null;
+   }
+
+   @Override
    public void delete() throws IOException, InterruptedException, ActiveMQException {
       try {
          synchronized (writeLock) {
diff --git a/artemis-journal/src/main/java/org/apache/activemq/artemis/core/io/SequentialFile.java b/artemis-journal/src/main/java/org/apache/activemq/artemis/core/io/SequentialFile.java
index afad902..34425ab 100644
--- a/artemis-journal/src/main/java/org/apache/activemq/artemis/core/io/SequentialFile.java
+++ b/artemis-journal/src/main/java/org/apache/activemq/artemis/core/io/SequentialFile.java
@@ -45,6 +45,8 @@ public interface SequentialFile {
     */
    void open(int maxIO, boolean useExecutor) throws Exception;
 
+   ByteBuffer map(int position, long size) throws IOException;
+
    boolean fits(int size);
 
    int calculateBlockStart(int position) throws Exception;
diff --git a/artemis-journal/src/main/java/org/apache/activemq/artemis/core/io/aio/AIOSequentialFile.java b/artemis-journal/src/main/java/org/apache/activemq/artemis/core/io/aio/AIOSequentialFile.java
index 86aee94..015510c 100644
--- a/artemis-journal/src/main/java/org/apache/activemq/artemis/core/io/aio/AIOSequentialFile.java
+++ b/artemis-journal/src/main/java/org/apache/activemq/artemis/core/io/aio/AIOSequentialFile.java
@@ -79,6 +79,11 @@ public class AIOSequentialFile extends AbstractSequentialFile {
    }
 
    @Override
+   public ByteBuffer map(int position, long size) throws IOException {
+      return null;
+   }
+
+   @Override
    public boolean isOpen() {
       return opened;
    }
diff --git a/artemis-journal/src/main/java/org/apache/activemq/artemis/core/io/mapped/MappedSequentialFile.java b/artemis-journal/src/main/java/org/apache/activemq/artemis/core/io/mapped/MappedSequentialFile.java
index 8806857..f1e2d47 100644
--- a/artemis-journal/src/main/java/org/apache/activemq/artemis/core/io/mapped/MappedSequentialFile.java
+++ b/artemis-journal/src/main/java/org/apache/activemq/artemis/core/io/mapped/MappedSequentialFile.java
@@ -419,6 +419,11 @@ final class MappedSequentialFile implements SequentialFile {
    }
 
    @Override
+   public ByteBuffer map(int position, long size) throws IOException {
+      return null;
+   }
+
+   @Override
    @Deprecated
    public void setTimedBuffer(TimedBuffer buffer) {
       throw new UnsupportedOperationException("the timed buffer is not currently supported");
diff --git a/artemis-journal/src/main/java/org/apache/activemq/artemis/core/io/mapped/TimedSequentialFile.java b/artemis-journal/src/main/java/org/apache/activemq/artemis/core/io/mapped/TimedSequentialFile.java
index cccbb1d..0d644cc 100644
--- a/artemis-journal/src/main/java/org/apache/activemq/artemis/core/io/mapped/TimedSequentialFile.java
+++ b/artemis-journal/src/main/java/org/apache/activemq/artemis/core/io/mapped/TimedSequentialFile.java
@@ -79,6 +79,11 @@ final class TimedSequentialFile implements SequentialFile {
    }
 
    @Override
+   public ByteBuffer map(int position, long size) throws IOException {
+      return null;
+   }
+
+   @Override
    public int calculateBlockStart(int position) throws Exception {
       return this.sequentialFile.calculateBlockStart(position);
    }
diff --git a/artemis-journal/src/main/java/org/apache/activemq/artemis/core/io/nio/NIOSequentialFile.java b/artemis-journal/src/main/java/org/apache/activemq/artemis/core/io/nio/NIOSequentialFile.java
index e5857ba..4ec79f2 100644
--- a/artemis-journal/src/main/java/org/apache/activemq/artemis/core/io/nio/NIOSequentialFile.java
+++ b/artemis-journal/src/main/java/org/apache/activemq/artemis/core/io/nio/NIOSequentialFile.java
@@ -18,12 +18,17 @@ package org.apache.activemq.artemis.core.io.nio;
 
 import java.io.File;
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.io.RandomAccessFile;
+import java.io.StringWriter;
 import java.nio.ByteBuffer;
 import java.nio.channels.ClosedChannelException;
 import java.nio.channels.FileChannel;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import io.netty.buffer.ByteBuf;
 import org.apache.activemq.artemis.api.core.ActiveMQException;
@@ -42,6 +47,8 @@ import org.apache.activemq.artemis.utils.Env;
 
 public class NIOSequentialFile extends AbstractSequentialFile {
 
+   private static final boolean DEBUG_OPENS = false;
+
    /* This value has been tuned just to reduce the memory footprint
       of read/write of the whole file size: given that this value
       is > 8192, RandomAccessFile JNI code will use malloc/free instead
@@ -92,6 +99,45 @@ public class NIOSequentialFile extends AbstractSequentialFile {
    }
 
    @Override
+   public ByteBuffer map(int position, long size) throws IOException {
+      return channel.map(FileChannel.MapMode.READ_ONLY, 0, size);
+   }
+
+   public static void clearDebug() {
+      counters.clear();
+   }
+
+   public static void printDebug() {
+      for (Map.Entry<String, AtomicInteger> entry : counters.entrySet()) {
+         System.out.println(entry.getValue() + " " + entry.getKey());
+      }
+   }
+
+   public static AtomicInteger getDebugCounter(Exception location) {
+      StringWriter writer = new StringWriter();
+      PrintWriter printWriter = new PrintWriter(writer);
+      location.printStackTrace(printWriter);
+
+      String strLocation = writer.toString();
+
+      return getDebugCounter(strLocation);
+   }
+
+   public static AtomicInteger getDebugCounter(String strLocation) {
+      AtomicInteger value = counters.get(strLocation);
+      if (value == null) {
+         value = new AtomicInteger(0);
+         AtomicInteger oldvalue = counters.putIfAbsent(strLocation, value);
+         if (oldvalue != null) {
+            value = oldvalue;
+         }
+      }
+
+      return value;
+   }
+
+   private static Map<String, AtomicInteger> counters = new ConcurrentHashMap<>();
+   @Override
    public void open(final int maxIO, final boolean useExecutor) throws IOException {
       try {
          rfile = new RandomAccessFile(getFile(), "rw");
@@ -99,6 +145,11 @@ public class NIOSequentialFile extends AbstractSequentialFile {
          channel = rfile.getChannel();
 
          fileSize = channel.size();
+
+         if (DEBUG_OPENS) {
+            getDebugCounter(new Exception("open")).incrementAndGet();
+            getDebugCounter("open").incrementAndGet();
+         }
       } catch (ClosedChannelException e) {
          throw e;
       } catch (IOException e) {
@@ -152,6 +203,11 @@ public class NIOSequentialFile extends AbstractSequentialFile {
    public synchronized void close(boolean waitSync) throws IOException, InterruptedException, ActiveMQException {
       super.close();
 
+      if (DEBUG_OPENS) {
+         getDebugCounter(new Exception("Close")).incrementAndGet();
+         getDebugCounter("close").incrementAndGet();
+      }
+
       try {
          try {
             if (channel != null) {
diff --git a/artemis-journal/src/main/java/org/apache/activemq/artemis/core/journal/EncoderPersister.java b/artemis-journal/src/main/java/org/apache/activemq/artemis/core/journal/EncoderPersister.java
index 1e734d3..ed117a7 100644
--- a/artemis-journal/src/main/java/org/apache/activemq/artemis/core/journal/EncoderPersister.java
+++ b/artemis-journal/src/main/java/org/apache/activemq/artemis/core/journal/EncoderPersister.java
@@ -18,17 +18,23 @@
 package org.apache.activemq.artemis.core.journal;
 
 import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
 import org.apache.activemq.artemis.core.persistence.Persister;
 
 /** This is a facade between the new Persister and the former EncodingSupport.
  *  Methods using the old interface will use this as a facade to provide the previous semantic. */
-public class EncoderPersister implements Persister<EncodingSupport, EncodingSupport> {
+public class EncoderPersister implements Persister<EncodingSupport> {
 
    private static final EncoderPersister theInstance = new EncoderPersister();
 
    private EncoderPersister() {
    }
 
+   @Override
+   public byte getID() {
+      return 0;
+   }
+
    public static EncoderPersister getInstance() {
       return theInstance;
    }
@@ -44,7 +50,7 @@ public class EncoderPersister implements Persister<EncodingSupport, EncodingSupp
    }
 
    @Override
-   public EncodingSupport decode(ActiveMQBuffer buffer, EncodingSupport record) {
+   public EncodingSupport decode(ActiveMQBuffer buffer, EncodingSupport record, CoreMessageObjectPools pools) {
       record.decode(buffer);
       return record;
    }
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPLargeMessage.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPLargeMessage.java
new file mode 100644
index 0000000..d6daac6
--- /dev/null
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPLargeMessage.java
@@ -0,0 +1,373 @@
+/*
+ * 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.activemq.artemis.protocol.amqp.broker;
+
+import java.nio.ByteBuffer;
+
+import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
+import org.apache.activemq.artemis.api.core.ActiveMQException;
+import org.apache.activemq.artemis.api.core.ICoreMessage;
+import org.apache.activemq.artemis.api.core.Message;
+import org.apache.activemq.artemis.core.io.SequentialFile;
+import org.apache.activemq.artemis.core.message.LargeBodyReader;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.Persister;
+import org.apache.activemq.artemis.core.persistence.StorageManager;
+import org.apache.activemq.artemis.core.persistence.impl.journal.LargeBody;
+import org.apache.activemq.artemis.core.persistence.impl.journal.LargeServerMessageImpl;
+import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
+import org.apache.activemq.artemis.core.server.LargeServerMessage;
+import org.apache.activemq.artemis.protocol.amqp.util.TLSEncode;
+import org.apache.activemq.artemis.utils.collections.TypedProperties;
+import org.apache.qpid.proton.amqp.messaging.Header;
+import org.apache.qpid.proton.codec.DecoderImpl;
+import org.apache.qpid.proton.codec.ReadableBuffer;
+import org.apache.qpid.proton.codec.TypeConstructor;
+
+public class AMQPLargeMessage extends AMQPMessage implements LargeServerMessage {
+
+   @Override
+   public ICoreMessage toCore(CoreMessageObjectPools coreMessageObjectPools) {
+      LargeBodyReader reader = largeBody.getLargeBodyReader();
+
+      try {
+         long size = reader.getSize();
+         if (size > Integer.MAX_VALUE) {
+            throw new RuntimeException("AMQP Large Message Body is too large to be converted into core");
+         }
+         byte[] buffer = new byte[(int)size];
+         ByteBuffer wrapbuffer = ByteBuffer.wrap(buffer);
+
+         reader.open();
+         reader.readInto(wrapbuffer);
+
+         AMQPStandardMessage standardMessage = new AMQPStandardMessage(messageFormat, buffer, extraProperties, coreMessageObjectPools);
+         standardMessage.setMessageID(messageID);
+         return standardMessage.toCore();
+      } catch (Exception e) {
+         logger.warn(e.getMessage(), e);
+         throw new RuntimeException(e.getMessage(), e);
+      } finally {
+         try {
+            reader.close();
+         } catch (Exception e) {
+            // unexpected to happen, but possible, nothing else we can do beyond logging at this point
+            // if we wanted to add anything it would be a critical failure but it would be a heavy refactoring
+            // to bring the bits and listeners here for little benefit
+            // the possibility of this happening involves losing the storage device which will lead to other errors anyway
+            logger.warn(e.getMessage(), e);
+         }
+      }
+   }
+   private final LargeBody largeBody;
+   /**
+    * We control durability on a separate property here, as we need to know if it's durable ahead of the header parsing.
+    * This will be the case when restarting a server
+    */
+   private Boolean fileDurable;
+
+   private volatile AmqpReadableBuffer parsingData;
+
+   private final StorageManager storageManager;
+
+   public AMQPLargeMessage(long id,
+                           long messageFormat,
+                           TypedProperties extraProperties,
+                           CoreMessageObjectPools coreMessageObjectPools,
+                           StorageManager storageManager) {
+      super(messageFormat, extraProperties, coreMessageObjectPools);
+      this.setMessageID(id);
+      largeBody = new LargeBody(this, storageManager);
+      this.storageManager = storageManager;
+   }
+
+   /**
+    * Copy constructor
+    */
+   private AMQPLargeMessage(final AMQPLargeMessage copy,
+                                  final SequentialFile fileCopy,
+                                  final long newID) {
+      super(copy);
+      largeBody = new LargeBody(this, copy.largeBody.getStorageManager(), fileCopy);
+      largeBody.setBodySize(copy.largeBody.getStoredBodySize());
+      this.storageManager = copy.largeBody.getStorageManager();
+      setMessageID(newID);
+   }
+
+   public void openLargeMessage() throws Exception {
+      this.parsingData = new AmqpReadableBuffer(largeBody.map());
+   }
+
+   public void closeLargeMessage() throws Exception {
+      largeBody.releaseResources(false);
+      parsingData.freeDirectBuffer();
+      parsingData = null;
+   }
+
+   @Override
+   public void finishParse() throws Exception {
+      openLargeMessage();
+      try {
+         this.ensureMessageDataScanned();
+         parsingData.rewind();
+         lazyDecodeApplicationProperties();
+      } finally {
+         closeLargeMessage();
+      }
+   }
+
+   public void setFileDurable(boolean value) {
+      this.fileDurable = value;
+   }
+
+   @Override
+   public StorageManager getStorageManager() {
+      return largeBody.getStorageManager();
+   }
+
+   @Override
+   public final boolean isDurable() {
+      if (fileDurable != null) {
+         return fileDurable.booleanValue();
+      } else {
+         return super.isDurable();
+      }
+   }
+
+   @Override
+   public ReadableBuffer getData() {
+      if (parsingData == null) {
+         throw new RuntimeException("AMQP Large Message is not open");
+      }
+
+      return parsingData;
+   }
+
+   protected void parseHeader(ReadableBuffer buffer) {
+
+      DecoderImpl decoder = TLSEncode.getDecoder();
+      decoder.setBuffer(buffer);
+
+      try {
+         int constructorPos = buffer.position();
+         TypeConstructor<?> constructor = decoder.readConstructor();
+         if (Header.class.equals(constructor.getTypeClass())) {
+            header = (Header) constructor.readValue();
+            if (header.getTtl() != null) {
+               expiration = System.currentTimeMillis() + header.getTtl().intValue();
+            }
+         }
+      } finally {
+         decoder.setBuffer(null);
+         buffer.rewind();
+      }
+   }
+
+   public void addBytes(ReadableBuffer data) throws Exception {
+
+      // We need to parse the header on the first add,
+      // as it will contain information if the message is durable or not
+      if (header == null && largeBody.getStoredBodySize() <= 0) {
+         parseHeader(data);
+      }
+
+      if (data.hasArray() && data.remaining() == data.array().length) {
+         //System.out.println("Received " + data.array().length + "::" + ByteUtil.formatGroup(ByteUtil.bytesToHex(data.array()), 8, 16));
+         largeBody.addBytes(data.array());
+      } else {
+         byte[] bytes = new byte[data.remaining()];
+         data.get(bytes);
+         //System.out.println("Finishing " + bytes.length + ByteUtil.formatGroup(ByteUtil.bytesToHex(bytes), 8, 16));
+         largeBody.addBytes(bytes);
+      }
+   }
+
+   @Override
+   public ReadableBuffer getSendBuffer(int deliveryCount) {
+      return getData().rewind();
+   }
+
+   @Override
+   public Message toMessage() {
+      return this;
+   }
+
+   @Override
+   public void addBytes(byte[] bytes) throws Exception {
+      largeBody.addBytes(bytes);
+   }
+
+   @Override
+   public void addBytes(ActiveMQBuffer bytes) throws Exception {
+      largeBody.addBytes(bytes);
+
+   }
+
+   @Override
+   public void setPaged() {
+      largeBody.setPaged();
+   }
+
+   @Override
+   public void releaseResources(boolean sync) {
+      largeBody.releaseResources(sync);
+
+   }
+
+   @Override
+   public void deleteFile() throws Exception {
+      largeBody.deleteFile();
+   }
+
+   @Override
+   public SequentialFile getAppendFile() throws ActiveMQException {
+      return largeBody.getAppendFile();
+   }
+
+   @Override
+   public boolean isLargeMessage() {
+      return true;
+   }
+
+   @Override
+   public LargeBodyReader getLargeBodyReader() {
+      return largeBody.getLargeBodyReader();
+   }
+
+   @Override
+   public LargeBody getLargeBody() {
+      return largeBody;
+   }
+
+   @Override
+   public void clearPendingRecordID() {
+      largeBody.clearPendingRecordID();
+   }
+
+   @Override
+   public boolean hasPendingRecord() {
+      return largeBody.hasPendingRecord();
+   }
+
+   @Override
+   public void setPendingRecordID(long pendingRecordID) {
+      largeBody.setPendingRecordID(pendingRecordID);
+   }
+
+   @Override
+   public long getPendingRecordID() {
+      return largeBody.getPendingRecordID();
+   }
+
+   @Override
+   protected void releaseComplete() {
+      largeBody.deleteFile();
+   }
+
+   @Override
+   public Message copy() {
+      SequentialFile newfile = largeBody.createFile();
+      AMQPLargeMessage newMessage = new AMQPLargeMessage(this, newfile, messageID);
+      newMessage.setParentRef(this);
+      newMessage.setFileDurable(this.isDurable());
+      return newMessage;
+   }
+
+   @Override
+   public Message copy(final long newID) {
+      try {
+         AMQPLargeMessage copy = new AMQPLargeMessage(newID, messageFormat, null, coreMessageObjectPools, storageManager);
+         copy.setDurable(this.isDurable());
+         largeBody.copyInto(copy);
+         copy.finishParse();
+         copy.releaseResources(true);
+         return copy;
+
+      } catch (Exception e) {
+         ActiveMQServerLogger.LOGGER.lareMessageErrorCopying(e, this);
+         return null;
+      }
+   }
+
+
+
+   @Override
+   public void messageChanged() {
+
+   }
+
+   @Override
+   public int getEncodeSize() {
+      return 0;
+   }
+
+   @Override
+   public int getMemoryEstimate() {
+      return 0;
+   }
+
+   @Override
+   public void persist(ActiveMQBuffer targetRecord) {
+
+   }
+
+   @Override
+   public int getPersistSize() {
+      return 0;
+   }
+
+   @Override
+   public void reloadPersistence(ActiveMQBuffer record, CoreMessageObjectPools pools) {
+
+   }
+
+   @Override
+   public long getPersistentSize() throws ActiveMQException {
+      return 0;
+   }
+
+   @Override
+   public Persister<Message> getPersister() {
+      return AMQPLargeMessagePersister.getInstance();
+   }
+
+   @Override
+   public void reencode() {
+
+   }
+
+   @Override
+   protected void ensureDataIsValid() {
+
+   }
+
+   @Override
+   protected void encodeMessage() {
+
+   }
+
+   @Override
+   public void referenceOriginalMessage(final Message original, String originalQueue) {
+
+      super.referenceOriginalMessage(original, originalQueue);
+
+      if (original instanceof LargeServerMessageImpl) {
+         this.largeBody.referenceOriginalMessage(((AMQPLargeMessage) original).largeBody);
+      }
+   }
+}
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPLargeMessagePersister.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPLargeMessagePersister.java
new file mode 100644
index 0000000..bdafcba
--- /dev/null
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPLargeMessagePersister.java
@@ -0,0 +1,144 @@
+/**
+ * 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.activemq.artemis.protocol.amqp.broker;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.PooledByteBufAllocator;
+import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
+import org.apache.activemq.artemis.api.core.Message;
+import org.apache.activemq.artemis.api.core.SimpleString;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.impl.journal.AbstractJournalStorageManager;
+import org.apache.activemq.artemis.spi.core.protocol.MessagePersister;
+import org.apache.activemq.artemis.utils.DataConstants;
+import org.apache.activemq.artemis.utils.collections.TypedProperties;
+import org.jboss.logging.Logger;
+
+import static org.apache.activemq.artemis.core.persistence.PersisterIDs.AMQPLargeMessagePersister_ID;
+
+public class AMQPLargeMessagePersister extends MessagePersister {
+   private static final Logger log = Logger.getLogger(AMQPLargeMessagePersister.class);
+
+   // We need to save the encoder ahead of time
+   // as we need to know the exact size of the Encoding
+   // so we store the savedBuffer on the getEncodeSize before we actually store it
+   private static final ThreadLocal<ByteBuf> savedBuffer = new ThreadLocal<>();
+
+   public static final byte ID = AMQPLargeMessagePersister_ID;
+
+   public static AMQPLargeMessagePersister theInstance;
+
+   public static AMQPLargeMessagePersister getInstance() {
+      if (theInstance == null) {
+         theInstance = new AMQPLargeMessagePersister();
+      }
+      return theInstance;
+   }
+
+   @Override
+   public byte getID() {
+      return ID;
+   }
+
+   public AMQPLargeMessagePersister() {
+      super();
+   }
+
+
+   @Override
+   public int getEncodeSize(Message record) {
+      ByteBuf buf = getSavedEncodeBuffer(record);
+
+      int encodeSize = DataConstants.SIZE_BYTE + DataConstants.SIZE_INT + DataConstants.SIZE_LONG + DataConstants.SIZE_LONG + SimpleString.sizeofNullableString(record.getAddressSimpleString()) + DataConstants.SIZE_BOOLEAN + buf.writerIndex();
+
+      TypedProperties properties = ((AMQPMessage) record).getExtraProperties();
+
+      return encodeSize + (properties != null ? properties.getEncodeSize() : 0);
+   }
+
+   private ByteBuf getSavedEncodeBuffer(Message record) {
+      ByteBuf buf = savedBuffer.get();
+      if (buf == null) {
+         AMQPLargeMessage largeMessage = (AMQPLargeMessage)record;
+         buf = PooledByteBufAllocator.DEFAULT.buffer(largeMessage.getEstimateSavedEncode());
+         largeMessage.saveEncoding(buf);
+         savedBuffer.set(buf);
+      }
+      return buf;
+   }
+
+   /**
+    * Sub classes must add the first short as the protocol-id
+    */
+   @Override
+   public void encode(ActiveMQBuffer buffer, Message record) {
+      super.encode(buffer, record);
+
+      AMQPMessage msgEncode = (AMQPMessage) record;
+
+      buffer.writeLong(record.getMessageID());
+      buffer.writeBoolean(record.isDurable());
+      buffer.writeLong(msgEncode.getMessageFormat());
+      buffer.writeNullableSimpleString(record.getAddressSimpleString());
+      TypedProperties properties = ((AMQPMessage) record).getExtraProperties();
+      if (properties == null) {
+         buffer.writeInt(0);
+      } else {
+         buffer.writeInt(properties.getEncodeSize());
+         properties.encode(buffer.byteBuf());
+      }
+
+      ByteBuf savedEncodeBuffer = getSavedEncodeBuffer(record);
+      buffer.writeBytes(savedEncodeBuffer, 0, savedEncodeBuffer.writerIndex());
+      savedEncodeBuffer.release();
+
+      savedBuffer.set(null);
+   }
+
+   @Override
+   public Message decode(ActiveMQBuffer buffer, Message record, CoreMessageObjectPools pools) {
+
+      long id = buffer.readLong();
+      boolean durable = buffer.readBoolean();
+      long format = buffer.readLong();
+      SimpleString address = buffer.readNullableSimpleString();
+
+      int size = buffer.readInt();
+
+      TypedProperties properties;
+
+      if (size != 0) {
+         properties = new TypedProperties(Message.INTERNAL_PROPERTY_NAMES_PREDICATE);
+         properties.decode(buffer.byteBuf());
+      } else {
+         properties = null;
+      }
+
+      AMQPLargeMessage largeMessage = new AMQPLargeMessage(id, format, properties, null, AbstractJournalStorageManager.getThreadLocal());
+
+      largeMessage.setFileDurable(durable);
+      if (address != null) {
+         largeMessage.setAddress(address);
+      }
+
+      largeMessage.readSavedEncoding(buffer.byteBuf());
+
+      return largeMessage;
+   }
+
+}
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPMessage.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPMessage.java
index fa96a5c..077d9dd 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPMessage.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPMessage.java
@@ -16,7 +16,6 @@
  */
 package org.apache.activemq.artemis.protocol.amqp.broker;
 
-import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.Collections;
@@ -26,6 +25,9 @@ import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.PooledByteBufAllocator;
+import io.netty.buffer.Unpooled;
 import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
 import org.apache.activemq.artemis.api.core.ActiveMQException;
 import org.apache.activemq.artemis.api.core.ActiveMQPropertyConversionException;
@@ -33,7 +35,7 @@ import org.apache.activemq.artemis.api.core.ICoreMessage;
 import org.apache.activemq.artemis.api.core.RefCountMessage;
 import org.apache.activemq.artemis.api.core.RoutingType;
 import org.apache.activemq.artemis.api.core.SimpleString;
-import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
 import org.apache.activemq.artemis.core.persistence.Persister;
 import org.apache.activemq.artemis.protocol.amqp.converter.AMQPMessageIdHelper;
 import org.apache.activemq.artemis.protocol.amqp.converter.AMQPMessageSupport;
@@ -43,7 +45,6 @@ import org.apache.activemq.artemis.protocol.amqp.util.NettyWritable;
 import org.apache.activemq.artemis.protocol.amqp.util.TLSEncode;
 import org.apache.activemq.artemis.reader.MessageUtil;
 import org.apache.activemq.artemis.utils.ByteUtil;
-import org.apache.activemq.artemis.utils.DataConstants;
 import org.apache.activemq.artemis.utils.algo.KMPNeedle;
 import org.apache.activemq.artemis.utils.collections.TypedProperties;
 import org.apache.qpid.proton.amqp.Binary;
@@ -64,16 +65,11 @@ import org.apache.qpid.proton.amqp.messaging.Properties;
 import org.apache.qpid.proton.amqp.messaging.Section;
 import org.apache.qpid.proton.codec.DecoderImpl;
 import org.apache.qpid.proton.codec.DroppingWritableBuffer;
-import org.apache.qpid.proton.codec.EncoderImpl;
 import org.apache.qpid.proton.codec.ReadableBuffer;
 import org.apache.qpid.proton.codec.TypeConstructor;
 import org.apache.qpid.proton.codec.WritableBuffer;
 import org.apache.qpid.proton.message.Message;
 import org.apache.qpid.proton.message.impl.MessageImpl;
-
-import io.netty.buffer.ByteBuf;
-import io.netty.buffer.PooledByteBufAllocator;
-import io.netty.buffer.Unpooled;
 import org.jboss.logging.Logger;
 
 /**
@@ -108,9 +104,9 @@ import org.jboss.logging.Logger;
  *    <li>Zero or one footer sections.
  * </ul>
  */
-public class AMQPMessage extends RefCountMessage {
+public abstract class AMQPMessage extends RefCountMessage implements org.apache.activemq.artemis.api.core.Message {
 
-   private static final Logger logger = Logger.getLogger(AMQPMessage.class);
+   protected static final Logger logger = Logger.getLogger(AMQPMessage.class);
 
    public static final SimpleString ADDRESS_PROPERTY = SimpleString.toSimpleString("_AMQ_AD");
    // used to perform quick search
@@ -124,117 +120,74 @@ public class AMQPMessage extends RefCountMessage {
    public static final int DEFAULT_MESSAGE_PRIORITY = 4;
    public static final int MAX_MESSAGE_PRIORITY = 9;
 
-   private static final int VALUE_NOT_PRESENT = -1;
+   protected static final int VALUE_NOT_PRESENT = -1;
 
    // Buffer and state for the data backing this message.
-   private ReadableBuffer data;
-   private static final byte NOT_SCANNED = 0;
-   private static final byte RELOAD_PERSISTENCE = 1;
-   private static final byte SCANNED = 2;
-   private byte messageDataScanned;
+   protected static final byte NOT_SCANNED = 0;
+   protected static final byte RELOAD_PERSISTENCE = 1;
+   protected static final byte SCANNED = 2;
+   protected byte messageDataScanned;
 
    // Marks the message as needed to be re-encoded to update the backing buffer
-   private boolean modified;
+   protected boolean modified;
 
    // Track locations of the message sections for later use and track the size
    // of the header and delivery annotations if present so we can easily exclude
    // the delivery annotations later and perform efficient encodes or copies.
-   private int headerPosition = VALUE_NOT_PRESENT;
-   private int encodedHeaderSize;
-   private int deliveryAnnotationsPosition = VALUE_NOT_PRESENT;
-   private int encodedDeliveryAnnotationsSize;
-   private int messageAnnotationsPosition = VALUE_NOT_PRESENT;
-   private int propertiesPosition = VALUE_NOT_PRESENT;
-   private int applicationPropertiesPosition = VALUE_NOT_PRESENT;
-   private int remainingBodyPosition = VALUE_NOT_PRESENT;
+   protected int headerPosition = VALUE_NOT_PRESENT;
+   protected int encodedHeaderSize;
+   protected int deliveryAnnotationsPosition = VALUE_NOT_PRESENT;
+   protected int encodedDeliveryAnnotationsSize;
+   protected int messageAnnotationsPosition = VALUE_NOT_PRESENT;
+   protected int propertiesPosition = VALUE_NOT_PRESENT;
+   protected int applicationPropertiesPosition = VALUE_NOT_PRESENT;
+   protected int remainingBodyPosition = VALUE_NOT_PRESENT;
 
    // Message level meta data
-   private final long messageFormat;
-   private long messageID;
-   private SimpleString address;
-   private volatile int memoryEstimate = -1;
-   private long expiration;
-   private long scheduledTime = -1;
+   protected final long messageFormat;
+   protected long messageID;
+   protected SimpleString address;
+   protected volatile int memoryEstimate = -1;
+   protected long expiration;
+   protected long scheduledTime = -1;
 
    // The Proton based AMQP message section that are retained in memory, these are the
    // mutable portions of the Message as the broker sees it, although AMQP defines that
    // the Properties and ApplicationProperties are immutable so care should be taken
    // here when making changes to those Sections.
-   private Header header;
-   private MessageAnnotations messageAnnotations;
-   private Properties properties;
-   private ApplicationProperties applicationProperties;
+   protected Header header;
+   protected MessageAnnotations messageAnnotations;
+   protected Properties properties;
+   protected ApplicationProperties applicationProperties;
 
-   private String connectionID;
-   private final CoreMessageObjectPools coreMessageObjectPools;
-   private Set<Object> rejectedConsumers;
-   private DeliveryAnnotations deliveryAnnotationsForSendBuffer;
+   protected String connectionID;
+   protected final CoreMessageObjectPools coreMessageObjectPools;
+   protected Set<Object> rejectedConsumers;
+   protected DeliveryAnnotations deliveryAnnotationsForSendBuffer;
 
    // These are properties set at the broker level and carried only internally by broker storage.
-   private volatile TypedProperties extraProperties;
-
-   /**
-    * Creates a new {@link AMQPMessage} instance from binary encoded message data.
-    *
-    * @param messageFormat
-    *       The Message format tag given the in Transfer that carried this message
-    * @param data
-    *       The encoded AMQP message
-    * @param extraProperties
-    *       Broker specific extra properties that should be carried with this message
-    */
-   public AMQPMessage(long messageFormat, byte[] data, TypedProperties extraProperties) {
-      this(messageFormat, data, extraProperties, null);
-   }
-
-   /**
-    * Creates a new {@link AMQPMessage} instance from binary encoded message data.
-    *
-    * @param messageFormat
-    *       The Message format tag given the in Transfer that carried this message
-    * @param data
-    *       The encoded AMQP message
-    * @param extraProperties
-    *       Broker specific extra properties that should be carried with this message
-    * @param coreMessageObjectPools
-    *       Object pool used to accelerate some String operations.
-    */
-   public AMQPMessage(long messageFormat, byte[] data, TypedProperties extraProperties, CoreMessageObjectPools coreMessageObjectPools) {
-      this(messageFormat, ReadableBuffer.ByteBufferReader.wrap(data), extraProperties, coreMessageObjectPools);
-   }
+   protected volatile TypedProperties extraProperties;
 
    /**
     * Creates a new {@link AMQPMessage} instance from binary encoded message data.
     *
     * @param messageFormat
     *       The Message format tag given the in Transfer that carried this message
-    * @param data
-    *       The encoded AMQP message in an {@link ReadableBuffer} wrapper.
     * @param extraProperties
     *       Broker specific extra properties that should be carried with this message
-    * @param coreMessageObjectPools
-    *       Object pool used to accelerate some String operations.
     */
-   public AMQPMessage(long messageFormat, ReadableBuffer data, TypedProperties extraProperties, CoreMessageObjectPools coreMessageObjectPools) {
-      this.data = data;
+   public AMQPMessage(long messageFormat, TypedProperties extraProperties, CoreMessageObjectPools coreMessageObjectPools) {
       this.messageFormat = messageFormat;
       this.coreMessageObjectPools = coreMessageObjectPools;
       this.extraProperties = extraProperties == null ? null : new TypedProperties(extraProperties);
-      ensureMessageDataScanned();
    }
 
-   /**
-    * Internal constructor used for persistence reload of the message.
-    * <p>
-    * The message will not be usable until the persistence mechanism populates the message
-    * data and triggers a parse of the message contents to fill in the message state.
-    *
-    * @param messageFormat
-    *       The Message format tag given the in Transfer that carried this message
-    */
-   AMQPMessage(long messageFormat) {
+   protected AMQPMessage(AMQPMessage copy) {
+      this(copy.messageFormat, copy.extraProperties, copy.coreMessageObjectPools);
+   }
+
+   protected AMQPMessage(long messageFormat) {
       this.messageFormat = messageFormat;
-      this.modified = true;  // No buffer yet so this indicates invalid state.
       this.coreMessageObjectPools = null;
    }
 
@@ -245,6 +198,8 @@ public class AMQPMessage extends RefCountMessage {
       return applicationProperties;
    }
 
+   protected abstract ReadableBuffer getData();
+
    // Access to the AMQP message data using safe copies freshly decoded from the current
    // AMQP message data stored in this message wrapper.  Changes to these values cannot
    // be used to influence the underlying AMQP message data, the standard AMQPMessage API
@@ -256,17 +211,18 @@ public class AMQPMessage extends RefCountMessage {
     *
     * @return a MessageImpl that wraps the AMQP message data in this {@link AMQPMessage}
     */
-   public MessageImpl getProtonMessage() {
-      if (data == null) {
+   public final MessageImpl getProtonMessage() {
+
+      if (getData() == null) {
          throw new NullPointerException("Data is not initialized");
       }
       ensureScanning();
 
       MessageImpl protonMessage = null;
-      if (data != null) {
+      if (getData() != null) {
          protonMessage = (MessageImpl) Message.Factory.create();
-         data.rewind();
-         protonMessage.decode(data.duplicate());
+         getData().rewind();
+         protonMessage.decode(getData().duplicate());
       }
 
       return protonMessage;
@@ -278,12 +234,12 @@ public class AMQPMessage extends RefCountMessage {
     *
     * @return a copy of the Message Header if one exists or null if none present.
     */
-   public Header getHeader() {
+   public final Header getHeader() {
       ensureScanning();
       return scanForMessageSection(headerPosition, Header.class);
    }
 
-   private void ensureScanning() {
+   protected void ensureScanning() {
       ensureDataIsValid();
       ensureMessageDataScanned();
    }
@@ -294,7 +250,7 @@ public class AMQPMessage extends RefCountMessage {
     *
     * @return a copy of the {@link DeliveryAnnotations} present in the message or null if non present.
     */
-   public DeliveryAnnotations getDeliveryAnnotations() {
+   public final DeliveryAnnotations getDeliveryAnnotations() {
       ensureScanning();
       return scanForMessageSection(deliveryAnnotationsPosition, DeliveryAnnotations.class);
    }
@@ -310,7 +266,7 @@ public class AMQPMessage extends RefCountMessage {
     *
     * @param deliveryAnnotations delivery annotations used in the sendBuffer() method
     */
-   public void setDeliveryAnnotationsForSendBuffer(DeliveryAnnotations deliveryAnnotations) {
+   public final void setDeliveryAnnotationsForSendBuffer(DeliveryAnnotations deliveryAnnotations) {
       this.deliveryAnnotationsForSendBuffer = deliveryAnnotations;
    }
 
@@ -320,7 +276,7 @@ public class AMQPMessage extends RefCountMessage {
     *
     * @return a copy of the {@link MessageAnnotations} present in the message or null if non present.
     */
-   public MessageAnnotations getMessageAnnotations() {
+   public final MessageAnnotations getMessageAnnotations() {
       ensureScanning();
       return scanForMessageSection(messageAnnotationsPosition, MessageAnnotations.class);
    }
@@ -331,7 +287,7 @@ public class AMQPMessage extends RefCountMessage {
     *
     * @return a copy of the Message Properties if one exists or null if none present.
     */
-   public Properties getProperties() {
+   public final Properties getProperties() {
       ensureScanning();
       return scanForMessageSection(propertiesPosition, Properties.class);
    }
@@ -342,15 +298,15 @@ public class AMQPMessage extends RefCountMessage {
     *
     * @return a copy of the {@link ApplicationProperties} present in the message or null if non present.
     */
-   public ApplicationProperties getApplicationProperties() {
+   public final ApplicationProperties getApplicationProperties() {
       ensureScanning();
       return scanForMessageSection(applicationPropertiesPosition, ApplicationProperties.class);
    }
 
    /** This is different from toString, as this will print an expanded version of the buffer
     *  in Hex and programmers's readable format */
-   public String toDebugString() {
-      return ByteUtil.debugByteArray(data.array());
+   public final String toDebugString() {
+      return ByteUtil.debugByteArray(getData().array());
    }
 
    /**
@@ -360,7 +316,7 @@ public class AMQPMessage extends RefCountMessage {
     *
     * @return the Section that makes up the body of this message.
     */
-   public Section getBody() {
+   public final Section getBody() {
       ensureScanning();
 
       // We only handle Sections of AmqpSequence, AmqpValue and Data types so we filter on those.
@@ -376,13 +332,13 @@ public class AMQPMessage extends RefCountMessage {
     *
     * @return the Footer that was encoded into this AMQP Message.
     */
-   public Footer getFooter() {
+   public final Footer getFooter() {
       ensureScanning();
       return scanForMessageSection(Math.max(0, remainingBodyPosition), Footer.class);
    }
 
    @SuppressWarnings({ "unchecked", "rawtypes" })
-   private <T> T scanForMessageSection(int scanStartPosition, Class...targetTypes) {
+   protected <T> T scanForMessageSection(int scanStartPosition, Class...targetTypes) {
       ensureMessageDataScanned();
 
       // In cases where we parsed out enough to know the value is not encoded in the message
@@ -391,7 +347,7 @@ public class AMQPMessage extends RefCountMessage {
          return null;
       }
 
-      ReadableBuffer buffer = data.duplicate().position(0);
+      ReadableBuffer buffer = getData().duplicate().position(0);
       final DecoderImpl decoder = TLSEncode.getDecoder();
 
       buffer.position(scanStartPosition);
@@ -418,7 +374,7 @@ public class AMQPMessage extends RefCountMessage {
       return section;
    }
 
-   private ApplicationProperties lazyDecodeApplicationProperties() {
+   protected ApplicationProperties lazyDecodeApplicationProperties() {
       if (applicationProperties == null && applicationPropertiesPosition != VALUE_NOT_PRESENT) {
          applicationProperties = scanForMessageSection(applicationPropertiesPosition, ApplicationProperties.class);
       }
@@ -427,7 +383,7 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @SuppressWarnings("unchecked")
-   private Map<String, Object> getApplicationPropertiesMap(boolean createIfAbsent) {
+   protected Map<String, Object> getApplicationPropertiesMap(boolean createIfAbsent) {
       ApplicationProperties appMap = lazyDecodeApplicationProperties();
       Map<String, Object> map = null;
 
@@ -448,7 +404,7 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @SuppressWarnings("unchecked")
-   private Map<Symbol, Object> getMessageAnnotationsMap(boolean createIfAbsent) {
+   protected Map<Symbol, Object> getMessageAnnotationsMap(boolean createIfAbsent) {
       Map<Symbol, Object> map = null;
 
       if (messageAnnotations != null) {
@@ -467,23 +423,23 @@ public class AMQPMessage extends RefCountMessage {
       return map;
    }
 
-   private Object getMessageAnnotation(String annotation) {
+   protected Object getMessageAnnotation(String annotation) {
       return getMessageAnnotation(Symbol.getSymbol(annotation));
    }
 
-   private Object getMessageAnnotation(Symbol annotation) {
+   protected Object getMessageAnnotation(Symbol annotation) {
       return getMessageAnnotationsMap(false).get(annotation);
    }
 
-   private Object removeMessageAnnotation(Symbol annotation) {
+   protected Object removeMessageAnnotation(Symbol annotation) {
       return getMessageAnnotationsMap(false).remove(annotation);
    }
 
-   private void setMessageAnnotation(String annotation, Object value) {
+   protected void setMessageAnnotation(String annotation, Object value) {
       setMessageAnnotation(Symbol.getSymbol(annotation), value);
    }
 
-   private void setMessageAnnotation(Symbol annotation, Object value) {
+   protected void setMessageAnnotation(Symbol annotation, Object value) {
       getMessageAnnotationsMap(true).put(annotation, value);
    }
 
@@ -491,7 +447,7 @@ public class AMQPMessage extends RefCountMessage {
    // state tracking information is kept up to data.  When the message is manually changed a forced
    // re-encode should be done to update the backing data with the in memory elements.
 
-   private synchronized void ensureMessageDataScanned() {
+   protected synchronized void ensureMessageDataScanned() {
       final byte state = messageDataScanned;
       switch (state) {
          case NOT_SCANNED:
@@ -510,7 +466,76 @@ public class AMQPMessage extends RefCountMessage {
       }
    }
 
-   private synchronized void resetMessageData() {
+
+   protected int getEstimateSavedEncode() {
+      return remainingBodyPosition > 0 ? remainingBodyPosition : 1024;
+   }
+
+   protected void saveEncoding(ByteBuf buf) {
+
+      WritableBuffer oldBuffer = TLSEncode.getEncoder().getBuffer();
+
+      TLSEncode.getEncoder().setByteBuffer(new NettyWritable(buf));
+
+      try {
+         buf.writeInt(headerPosition);
+         buf.writeInt(encodedHeaderSize);
+         TLSEncode.getEncoder().writeObject(header);
+
+         buf.writeInt(deliveryAnnotationsPosition);
+         buf.writeInt(encodedDeliveryAnnotationsSize);
+
+         buf.writeInt(messageAnnotationsPosition);
+         TLSEncode.getEncoder().writeObject(messageAnnotations);
+
+
+         buf.writeInt(propertiesPosition);
+         TLSEncode.getEncoder().writeObject(properties);
+
+         buf.writeInt(applicationPropertiesPosition);
+         buf.writeInt(remainingBodyPosition);
+
+         TLSEncode.getEncoder().writeObject(applicationProperties);
+
+      } finally {
+         TLSEncode.getEncoder().setByteBuffer(oldBuffer);
+      }
+   }
+
+
+   protected void readSavedEncoding(ByteBuf buf) {
+      ReadableBuffer oldBuffer = TLSEncode.getDecoder().getBuffer();
+
+      TLSEncode.getDecoder().setBuffer(new NettyReadable(buf));
+
+      try {
+         messageDataScanned = SCANNED;
+
+         headerPosition = buf.readInt();
+         encodedHeaderSize = buf.readInt();
+         header = (Header)TLSEncode.getDecoder().readObject();
+
+         deliveryAnnotationsPosition = buf.readInt();
+         encodedDeliveryAnnotationsSize = buf.readInt();
+
+         messageAnnotationsPosition = buf.readInt();
+         messageAnnotations = (MessageAnnotations)TLSEncode.getDecoder().readObject();
+
+         propertiesPosition = buf.readInt();
+         properties = (Properties)TLSEncode.getDecoder().readObject();
+
+         applicationPropertiesPosition = buf.readInt();
+         remainingBodyPosition = buf.readInt();
+
+         applicationProperties = (ApplicationProperties)TLSEncode.getDecoder().readObject();
+      } finally {
+         TLSEncode.getDecoder().setBuffer(oldBuffer);
+      }
+   }
+
+
+
+   protected synchronized void resetMessageData() {
       header = null;
       messageAnnotations = null;
       properties = null;
@@ -527,13 +552,15 @@ public class AMQPMessage extends RefCountMessage {
       remainingBodyPosition = VALUE_NOT_PRESENT;
    }
 
-   private synchronized void scanMessageData() {
+   protected synchronized void scanMessageData() {
       this.messageDataScanned = SCANNED;
       DecoderImpl decoder = TLSEncode.getDecoder();
-      decoder.setBuffer(data.rewind());
+      decoder.setBuffer(getData().rewind());
 
       resetMessageData();
 
+      ReadableBuffer data = getData();
+
       try {
          while (data.hasRemaining()) {
             int constructorPos = data.position();
@@ -582,25 +609,7 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message copy() {
-      ensureDataIsValid();
-
-      ReadableBuffer view = data.duplicate().rewind();
-      byte[] newData = new byte[view.remaining()];
-
-      // Copy the full message contents with delivery annotations as they will
-      // be trimmed on send and may become useful on the broker at a later time.
-      view.get(newData);
-
-      AMQPMessage newEncode = new AMQPMessage(this.messageFormat, newData, extraProperties, coreMessageObjectPools);
-      newEncode.setMessageID(this.getMessageID());
-      return newEncode;
-   }
-
-   @Override
-   public org.apache.activemq.artemis.api.core.Message copy(long newID) {
-      return copy().setMessageID(newID);
-   }
+   public abstract org.apache.activemq.artemis.api.core.Message copy();
 
    // Core Message APIs for persisting and encoding of message data along with
    // utilities for checking memory usage and encoded size characteristics.
@@ -614,7 +623,7 @@ public class AMQPMessage extends RefCountMessage {
     * @see #getSendBuffer(int) for the actual method used for message sends.
     */
    @Override
-   public void sendBuffer(ByteBuf buffer, int deliveryCount) {
+   public final void sendBuffer(ByteBuf buffer, int deliveryCount) {
       ensureDataIsValid();
       NettyWritable writable = new NettyWritable(buffer);
       writable.put(getSendBuffer(deliveryCount));
@@ -644,15 +653,15 @@ public class AMQPMessage extends RefCountMessage {
       } else {
          // Common case message has no delivery annotations, no delivery annotations for the send buffer were set
          // and this is the first delivery so no re-encoding or section skipping needed.
-         return data.duplicate();
+         return getData().duplicate();
       }
    }
 
-   private ReadableBuffer createCopyWithSkippedOrExplicitlySetDeliveryAnnotations() {
+   protected ReadableBuffer createCopyWithSkippedOrExplicitlySetDeliveryAnnotations() {
       // The original message had delivery annotations, or delivery annotations for the send buffer are set.
       // That means we must copy into a new buffer skipping the original delivery annotations section
       // (not meant to survive beyond this hop) and including the delivery annotations for the send buffer if set.
-      ReadableBuffer duplicate = data.duplicate();
+      ReadableBuffer duplicate = getData().duplicate();
 
       final ByteBuf result = PooledByteBufAllocator.DEFAULT.heapBuffer(getEncodeSize());
       result.writeBytes(duplicate.limit(encodedHeaderSize).byteBuffer());
@@ -665,7 +674,7 @@ public class AMQPMessage extends RefCountMessage {
       return new NettyReadable(result);
    }
 
-   private ReadableBuffer createCopyWithNewDeliveryCount(int deliveryCount) {
+   protected ReadableBuffer createCopyWithNewDeliveryCount(int deliveryCount) {
       assert deliveryCount > 1;
 
       final int amqpDeliveryCount = deliveryCount - 1;
@@ -690,14 +699,14 @@ public class AMQPMessage extends RefCountMessage {
 
       writeDeliveryAnnotationsForSendBuffer(result);
       // skip existing delivery annotations of the original message
-      data.position(encodedHeaderSize + encodedDeliveryAnnotationsSize);
-      result.writeBytes(data.byteBuffer());
-      data.position(0);
+      getData().position(encodedHeaderSize + encodedDeliveryAnnotationsSize);
+      result.writeBytes(getData().byteBuffer());
+      getData().position(0);
 
       return new NettyReadable(result);
    }
 
-   private void writeDeliveryAnnotationsForSendBuffer(ByteBuf result) {
+   protected void writeDeliveryAnnotationsForSendBuffer(ByteBuf result) {
       if (deliveryAnnotationsForSendBuffer != null && !deliveryAnnotationsForSendBuffer.getValue().isEmpty()) {
          TLSEncode.getEncoder().setByteBuffer(new NettyWritable(result));
          TLSEncode.getEncoder().writeObject(deliveryAnnotationsForSendBuffer);
@@ -705,7 +714,7 @@ public class AMQPMessage extends RefCountMessage {
       }
    }
 
-   private int getDeliveryAnnotationsForSendBufferSize() {
+   protected int getDeliveryAnnotationsForSendBufferSize() {
       if (deliveryAnnotationsForSendBuffer == null || deliveryAnnotationsForSendBuffer.getValue().isEmpty()) {
          return 0;
       }
@@ -722,45 +731,35 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public ByteBuf getBuffer() {
-      if (data == null) {
+   public final ByteBuf getBuffer() {
+      if (getData() == null) {
          return null;
       } else {
-         if (data instanceof NettyReadable) {
-            return ((NettyReadable) data).getByteBuf();
+         if (getData() instanceof NettyReadable) {
+            return ((NettyReadable) getData()).getByteBuf();
          } else {
-            return Unpooled.wrappedBuffer(data.byteBuffer());
+            return Unpooled.wrappedBuffer(getData().byteBuffer());
          }
       }
    }
 
    @Override
-   public AMQPMessage setBuffer(ByteBuf buffer) {
+   public final AMQPMessage setBuffer(ByteBuf buffer) {
       // If this is ever called we would be in a highly unfortunate state
-      this.data = null;
+      //this.data = null;
       return this;
    }
 
    @Override
-   public int getEncodeSize() {
-      ensureDataIsValid();
-      // The encoded size will exclude any delivery annotations that are present as we will clip them.
-      return data.remaining() - encodedDeliveryAnnotationsSize + getDeliveryAnnotationsForSendBufferSize();
-   }
+   public abstract int getEncodeSize();
 
    @Override
-   public void receiveBuffer(ByteBuf buffer) {
+   public final void receiveBuffer(ByteBuf buffer) {
       // Not used for AMQP messages.
    }
 
    @Override
-   public int getMemoryEstimate() {
-      if (memoryEstimate == -1) {
-         memoryEstimate = memoryOffset + (data != null ? data.capacity() : 0);
-      }
-
-      return memoryEstimate;
-   }
+   public abstract int getMemoryEstimate();
 
    @Override
    public ICoreMessage toCore(CoreMessageObjectPools coreMessageObjectPools) {
@@ -779,40 +778,19 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public void persist(ActiveMQBuffer targetRecord) {
-      ensureDataIsValid();
-      targetRecord.writeInt(internalPersistSize());
-      if (data.hasArray()) {
-         targetRecord.writeBytes(data.array(), data.arrayOffset(), data.remaining());
-      } else {
-         targetRecord.writeBytes(data.byteBuffer());
-      }
-   }
+   public abstract void persist(ActiveMQBuffer targetRecord);
 
    @Override
-   public int getPersistSize() {
-      ensureDataIsValid();
-      return DataConstants.SIZE_INT + internalPersistSize();
-   }
+   public abstract int getPersistSize();
 
-   private int internalPersistSize() {
-      return data.remaining();
+   protected int internalPersistSize() {
+      return getData().remaining();
    }
 
    @Override
-   public void reloadPersistence(ActiveMQBuffer record, CoreMessageObjectPools pools) {
-      int size = record.readInt();
-      byte[] recordArray = new byte[size];
-      record.readBytes(recordArray);
-      data = ReadableBuffer.ByteBufferReader.wrap(ByteBuffer.wrap(recordArray));
-
-      // Message state is now that the underlying buffer is loaded, but the contents not yet scanned
-      resetMessageData();
-      modified = false;
-      messageDataScanned = RELOAD_PERSISTENCE;
-   }
+   public abstract void reloadPersistence(ActiveMQBuffer record, CoreMessageObjectPools pools);
 
-   private synchronized void lazyScanAfterReloadPersistence() {
+   protected synchronized void lazyScanAfterReloadPersistence() {
       assert messageDataScanned == RELOAD_PERSISTENCE;
       scanMessageData();
       messageDataScanned = SCANNED;
@@ -829,118 +807,46 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public long getPersistentSize() throws ActiveMQException {
-      return getEncodeSize();
-   }
+   public abstract long getPersistentSize() throws ActiveMQException;
 
    @Override
-   public Persister<org.apache.activemq.artemis.api.core.Message, CoreMessageObjectPools> getPersister() {
-      return AMQPMessagePersisterV2.getInstance();
-   }
+   public abstract Persister<org.apache.activemq.artemis.api.core.Message> getPersister();
 
    @Override
-   public void reencode() {
-      ensureMessageDataScanned();
-
-      // The address was updated on a message with Properties so we update them
-      // for cases where there are no properties we aren't adding a properties
-      // section which seems wrong but this preserves previous behavior.
-      if (properties != null && address != null) {
-         properties.setTo(address.toString());
-      }
+   public abstract void reencode();
 
-      encodeMessage();
-      scanMessageData();
-   }
-
-   private synchronized void ensureDataIsValid() {
-      if (modified) {
-         encodeMessage();
-      }
-   }
-
-   private synchronized void encodeMessage() {
-      this.modified = false;
-      this.messageDataScanned = NOT_SCANNED;
-      int estimated = Math.max(1500, data != null ? data.capacity() + 1000 : 0);
-      ByteBuf buffer = PooledByteBufAllocator.DEFAULT.heapBuffer(estimated);
-      EncoderImpl encoder = TLSEncode.getEncoder();
-
-      try {
-         NettyWritable writable = new NettyWritable(buffer);
+   protected abstract void ensureDataIsValid();
 
-         encoder.setByteBuffer(writable);
-         if (header != null) {
-            encoder.writeObject(header);
-         }
-
-         // We currently do not encode any delivery annotations but it is conceivable
-         // that at some point they may need to be preserved, this is where that needs
-         // to happen.
-
-         if (messageAnnotations != null) {
-            encoder.writeObject(messageAnnotations);
-         }
-         if (properties != null) {
-            encoder.writeObject(properties);
-         }
-
-         // Whenever possible avoid encoding sections we don't need to by
-         // checking if application properties where loaded or added and
-         // encoding only in that case.
-         if (applicationProperties != null) {
-            encoder.writeObject(applicationProperties);
-
-            // Now raw write the remainder body and footer if present.
-            if (data != null && remainingBodyPosition != VALUE_NOT_PRESENT) {
-               writable.put(data.position(remainingBodyPosition));
-            }
-         } else if (data != null && applicationPropertiesPosition != VALUE_NOT_PRESENT) {
-            // Writes out ApplicationProperties, Body and Footer in one go if present.
-            writable.put(data.position(applicationPropertiesPosition));
-         } else if (data != null && remainingBodyPosition != VALUE_NOT_PRESENT) {
-            // No Application properties at all so raw write Body and Footer sections
-            writable.put(data.position(remainingBodyPosition));
-         }
-
-         byte[] bytes = new byte[buffer.writerIndex()];
-
-         buffer.readBytes(bytes);
-         data = ReadableBuffer.ByteBufferReader.wrap(ByteBuffer.wrap(bytes));
-      } finally {
-         encoder.setByteBuffer((WritableBuffer) null);
-         buffer.release();
-      }
-   }
+   protected abstract void encodeMessage();
 
    // These methods interact with the Extra Properties that can accompany the message but
    // because these are not sent on the wire, update to these does not force a re-encode on
    // send of the message.
 
-   public TypedProperties createExtraProperties() {
+   public final TypedProperties createExtraProperties() {
       if (extraProperties == null) {
          extraProperties = new TypedProperties(INTERNAL_PROPERTY_NAMES_PREDICATE);
       }
       return extraProperties;
    }
 
-   public TypedProperties getExtraProperties() {
+   public final TypedProperties getExtraProperties() {
       return extraProperties;
    }
 
-   public AMQPMessage setExtraProperties(TypedProperties extraProperties) {
+   public final AMQPMessage setExtraProperties(TypedProperties extraProperties) {
       this.extraProperties = extraProperties;
       return this;
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message putExtraBytesProperty(SimpleString key, byte[] value) {
+   public final org.apache.activemq.artemis.api.core.Message putExtraBytesProperty(SimpleString key, byte[] value) {
       createExtraProperties().putBytesProperty(key, value);
       return this;
    }
 
    @Override
-   public byte[] getExtraBytesProperty(SimpleString key) throws ActiveMQPropertyConversionException {
+   public final byte[] getExtraBytesProperty(SimpleString key) throws ActiveMQPropertyConversionException {
       if (extraProperties == null) {
          return null;
       } else {
@@ -956,7 +862,7 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public byte[] removeExtraBytesProperty(SimpleString key) throws ActiveMQPropertyConversionException {
+   public final byte[] removeExtraBytesProperty(SimpleString key) throws ActiveMQPropertyConversionException {
       if (extraProperties == null) {
          return null;
       } else {
@@ -967,38 +873,38 @@ public class AMQPMessage extends RefCountMessage {
    // Message meta data access for Core and AMQP usage.
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message setConnectionID(String connectionID) {
+   public final org.apache.activemq.artemis.api.core.Message setConnectionID(String connectionID) {
       this.connectionID = connectionID;
       return this;
    }
 
    @Override
-   public String getConnectionID() {
+   public final String getConnectionID() {
       return connectionID;
    }
 
-   public long getMessageFormat() {
+   public final long getMessageFormat() {
       return messageFormat;
    }
 
    @Override
-   public long getMessageID() {
+   public final long getMessageID() {
       return messageID;
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message setMessageID(long id) {
+   public final org.apache.activemq.artemis.api.core.Message setMessageID(long id) {
       this.messageID = id;
       return this;
    }
 
    @Override
-   public long getExpiration() {
+   public final long getExpiration() {
       return expiration;
    }
 
    @Override
-   public AMQPMessage setExpiration(long expiration) {
+   public final AMQPMessage setExpiration(long expiration) {
       if (properties != null) {
          if (expiration <= 0) {
             properties.setAbsoluteExpiryTime(null);
@@ -1022,7 +928,7 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public Object getUserID() {
+   public final Object getUserID() {
       // User ID in Artemis API means Message ID
       if (properties != null && properties.getMessageId() != null) {
          return properties.getMessageId();
@@ -1039,7 +945,7 @@ public class AMQPMessage extends RefCountMessage {
     *
     * @return the UserID value in the AMQP Properties if one is present.
     */
-   public Object getAMQPUserID() {
+   public final Object getAMQPUserID() {
       if (properties != null && properties.getUserId() != null) {
          Binary binary = properties.getUserId();
          return new String(binary.getArray(), binary.getArrayOffset(), binary.getLength(), StandardCharsets.UTF_8);
@@ -1049,18 +955,18 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message setUserID(Object userID) {
+   public final org.apache.activemq.artemis.api.core.Message setUserID(Object userID) {
       return this;
    }
 
    @Override
-   public Object getDuplicateProperty() {
+   public final Object getDuplicateProperty() {
       return getObjectProperty(org.apache.activemq.artemis.api.core.Message.HDR_DUPLICATE_DETECTION_ID);
    }
 
    @Override
    public boolean isDurable() {
-      if (header != null && header.getDurable() != null) {
+      if (header != null && header .getDurable() != null) {
          return header.getDurable();
       } else {
          return false;
@@ -1068,7 +974,7 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message setDurable(boolean durable) {
+   public final org.apache.activemq.artemis.api.core.Message setDurable(boolean durable) {
       if (header == null) {
          header = new Header();
       }
@@ -1079,26 +985,26 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public String getAddress() {
+   public final String getAddress() {
       SimpleString addressSimpleString = getAddressSimpleString();
       return addressSimpleString == null ? null : addressSimpleString.toString();
    }
 
    @Override
-   public AMQPMessage setAddress(String address) {
+   public final AMQPMessage setAddress(String address) {
       setAddress(cachedAddressSimpleString(address));
       return this;
    }
 
    @Override
-   public AMQPMessage setAddress(SimpleString address) {
+   public final AMQPMessage setAddress(SimpleString address) {
       this.address = address;
       createExtraProperties().putSimpleStringProperty(ADDRESS_PROPERTY, address);
       return this;
    }
 
    @Override
-   public SimpleString getAddressSimpleString() {
+   public final SimpleString getAddressSimpleString() {
       if (address == null) {
          TypedProperties extraProperties = getExtraProperties();
 
@@ -1117,12 +1023,12 @@ public class AMQPMessage extends RefCountMessage {
       return address;
    }
 
-   private SimpleString cachedAddressSimpleString(String address) {
+   protected SimpleString cachedAddressSimpleString(String address) {
       return CoreMessageObjectPools.cachedAddressSimpleString(address, coreMessageObjectPools);
    }
 
    @Override
-   public long getTimestamp() {
+   public final long getTimestamp() {
       if (properties != null && properties.getCreationTime() != null) {
          return properties.getCreationTime().getTime();
       } else {
@@ -1131,7 +1037,7 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message setTimestamp(long timestamp) {
+   public final org.apache.activemq.artemis.api.core.Message setTimestamp(long timestamp) {
       if (properties == null) {
          properties = new Properties();
       }
@@ -1140,7 +1046,7 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public byte getPriority() {
+   public final byte getPriority() {
       if (header != null && header.getPriority() != null) {
          return (byte) Math.min(header.getPriority().intValue(), MAX_MESSAGE_PRIORITY);
       } else {
@@ -1149,7 +1055,7 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message setPriority(byte priority) {
+   public final org.apache.activemq.artemis.api.core.Message setPriority(byte priority) {
       if (header == null) {
          header = new Header();
       }
@@ -1158,7 +1064,7 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public SimpleString getReplyTo() {
+   public final SimpleString getReplyTo() {
       if (properties != null) {
          return SimpleString.toSimpleString(properties.getReplyTo());
       } else {
@@ -1167,7 +1073,7 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public AMQPMessage setReplyTo(SimpleString address) {
+   public final AMQPMessage setReplyTo(SimpleString address) {
       if (properties == null) {
          properties = new Properties();
       }
@@ -1177,7 +1083,7 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public RoutingType getRoutingType() {
+   public final RoutingType getRoutingType() {
       ensureMessageDataScanned();
       Object routingType = getMessageAnnotation(AMQPMessageSupport.ROUTING_TYPE);
 
@@ -1200,7 +1106,7 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message setRoutingType(RoutingType routingType) {
+   public final org.apache.activemq.artemis.api.core.Message setRoutingType(RoutingType routingType) {
       if (routingType == null) {
          removeMessageAnnotation(AMQPMessageSupport.ROUTING_TYPE);
       } else {
@@ -1210,7 +1116,7 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public SimpleString getGroupID() {
+   public final SimpleString getGroupID() {
       ensureMessageDataScanned();
 
       if (properties != null && properties.getGroupId() != null) {
@@ -1222,7 +1128,7 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public int getGroupSequence() {
+   public final int getGroupSequence() {
       ensureMessageDataScanned();
 
       if (properties != null && properties.getGroupSequence() != null) {
@@ -1233,12 +1139,12 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public Object getCorrelationID() {
+   public final Object getCorrelationID() {
       return properties != null ? properties.getCorrelationId() : null;
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message setCorrelationID(final Object correlationID) {
+   public final org.apache.activemq.artemis.api.core.Message setCorrelationID(final Object correlationID) {
       if (properties == null) {
          properties = new Properties();
       }
@@ -1274,11 +1180,11 @@ public class AMQPMessage extends RefCountMessage {
          }
          return false;
       }
-      return AMQPMessageSymbolSearch.anyMessageAnnotations(data, symbolNeedles);
+      return AMQPMessageSymbolSearch.anyMessageAnnotations(getData(), symbolNeedles);
    }
 
    @Override
-   public Long getScheduledDeliveryTime() {
+   public final Long getScheduledDeliveryTime() {
       ensureMessageDataScanned();
       if (scheduledTime < 0) {
          Object objscheduledTime = getMessageAnnotation(AMQPMessageSupport.SCHEDULED_DELIVERY_TIME);
@@ -1297,7 +1203,7 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public AMQPMessage setScheduledDeliveryTime(Long time) {
+   public final AMQPMessage setScheduledDeliveryTime(Long time) {
       if (time != null && time.longValue() > 0) {
          setMessageAnnotation(AMQPMessageSupport.SCHEDULED_DELIVERY_TIME, time);
          removeMessageAnnotation(AMQPMessageSupport.SCHEDULED_DELIVERY_DELAY);
@@ -1312,17 +1218,17 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public Object removeAnnotation(SimpleString key) {
+   public final Object removeAnnotation(SimpleString key) {
       return removeMessageAnnotation(Symbol.getSymbol(key.toString()));
    }
 
    @Override
-   public Object getAnnotation(SimpleString key) {
+   public final Object getAnnotation(SimpleString key) {
       return getMessageAnnotation(key.toString());
    }
 
    @Override
-   public AMQPMessage setAnnotation(SimpleString key, Object value) {
+   public final AMQPMessage setAnnotation(SimpleString key, Object value) {
       setMessageAnnotation(key.toString(), value);
       return this;
    }
@@ -1333,52 +1239,52 @@ public class AMQPMessage extends RefCountMessage {
    // the next send of the Message will not include changes made here.
 
    @Override
-   public Object removeProperty(SimpleString key) {
+   public final Object removeProperty(SimpleString key) {
       return removeProperty(key.toString());
    }
 
    @Override
-   public Object removeProperty(String key) {
+   public final Object removeProperty(String key) {
       return getApplicationPropertiesMap(false).remove(key);
    }
 
    @Override
-   public boolean containsProperty(SimpleString key) {
+   public final boolean containsProperty(SimpleString key) {
       return containsProperty(key.toString());
    }
 
    @Override
-   public boolean containsProperty(String key) {
+   public final boolean containsProperty(String key) {
       return getApplicationPropertiesMap(false).containsKey(key);
    }
 
    @Override
-   public Boolean getBooleanProperty(String key) throws ActiveMQPropertyConversionException {
+   public final Boolean getBooleanProperty(String key) throws ActiveMQPropertyConversionException {
       return (Boolean) getApplicationPropertiesMap(false).get(key);
    }
 
    @Override
-   public Byte getByteProperty(String key) throws ActiveMQPropertyConversionException {
+   public final Byte getByteProperty(String key) throws ActiveMQPropertyConversionException {
       return (Byte) getApplicationPropertiesMap(false).get(key);
    }
 
    @Override
-   public Double getDoubleProperty(String key) throws ActiveMQPropertyConversionException {
+   public final Double getDoubleProperty(String key) throws ActiveMQPropertyConversionException {
       return (Double) getApplicationPropertiesMap(false).get(key);
    }
 
    @Override
-   public Integer getIntProperty(String key) throws ActiveMQPropertyConversionException {
+   public final Integer getIntProperty(String key) throws ActiveMQPropertyConversionException {
       return (Integer) getApplicationPropertiesMap(false).get(key);
    }
 
    @Override
-   public Long getLongProperty(String key) throws ActiveMQPropertyConversionException {
+   public final Long getLongProperty(String key) throws ActiveMQPropertyConversionException {
       return (Long) getApplicationPropertiesMap(false).get(key);
    }
 
    @Override
-   public Object getObjectProperty(String key) {
+   public final Object getObjectProperty(String key) {
       if (key.equals(MessageUtil.TYPE_HEADER_NAME.toString())) {
          if (properties != null) {
             return properties.getSubject();
@@ -1411,17 +1317,17 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public Short getShortProperty(String key) throws ActiveMQPropertyConversionException {
+   public final Short getShortProperty(String key) throws ActiveMQPropertyConversionException {
       return (Short) getApplicationPropertiesMap(false).get(key);
    }
 
    @Override
-   public Float getFloatProperty(String key) throws ActiveMQPropertyConversionException {
+   public final Float getFloatProperty(String key) throws ActiveMQPropertyConversionException {
       return (Float) getApplicationPropertiesMap(false).get(key);
    }
 
    @Override
-   public String getStringProperty(String key) throws ActiveMQPropertyConversionException {
+   public final String getStringProperty(String key) throws ActiveMQPropertyConversionException {
       if (key.equals(MessageUtil.TYPE_HEADER_NAME.toString())) {
          return properties.getSubject();
       } else if (key.equals(MessageUtil.CONNECTION_ID_PROPERTY_NAME.toString())) {
@@ -1432,7 +1338,7 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public Set<SimpleString> getPropertyNames() {
+   public final Set<SimpleString> getPropertyNames() {
       HashSet<SimpleString> values = new HashSet<>();
       for (Object k : getApplicationPropertiesMap(false).keySet()) {
          values.add(SimpleString.toSimpleString(k.toString(), getPropertyKeysPool()));
@@ -1441,66 +1347,66 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public Boolean getBooleanProperty(SimpleString key) throws ActiveMQPropertyConversionException {
+   public final Boolean getBooleanProperty(SimpleString key) throws ActiveMQPropertyConversionException {
       return getBooleanProperty(key.toString());
    }
 
    @Override
-   public Byte getByteProperty(SimpleString key) throws ActiveMQPropertyConversionException {
+   public final Byte getByteProperty(SimpleString key) throws ActiveMQPropertyConversionException {
       return getByteProperty(key.toString());
    }
 
    @Override
-   public byte[] getBytesProperty(String key) throws ActiveMQPropertyConversionException {
+   public final byte[] getBytesProperty(String key) throws ActiveMQPropertyConversionException {
       return (byte[]) getApplicationPropertiesMap(false).get(key);
    }
 
    @Override
-   public Double getDoubleProperty(SimpleString key) throws ActiveMQPropertyConversionException {
+   public final Double getDoubleProperty(SimpleString key) throws ActiveMQPropertyConversionException {
       return getDoubleProperty(key.toString());
    }
 
    @Override
-   public Integer getIntProperty(SimpleString key) throws ActiveMQPropertyConversionException {
+   public final Integer getIntProperty(SimpleString key) throws ActiveMQPropertyConversionException {
       return getIntProperty(key.toString());
    }
 
    @Override
-   public Long getLongProperty(SimpleString key) throws ActiveMQPropertyConversionException {
+   public final Long getLongProperty(SimpleString key) throws ActiveMQPropertyConversionException {
       return getLongProperty(key.toString());
    }
 
    @Override
-   public Object getObjectProperty(SimpleString key) {
+   public final Object getObjectProperty(SimpleString key) {
       return getObjectProperty(key.toString());
    }
 
    @Override
-   public Short getShortProperty(SimpleString key) throws ActiveMQPropertyConversionException {
+   public final Short getShortProperty(SimpleString key) throws ActiveMQPropertyConversionException {
       return getShortProperty(key.toString());
    }
 
    @Override
-   public Float getFloatProperty(SimpleString key) throws ActiveMQPropertyConversionException {
+   public final Float getFloatProperty(SimpleString key) throws ActiveMQPropertyConversionException {
       return getFloatProperty(key.toString());
    }
 
    @Override
-   public String getStringProperty(SimpleString key) throws ActiveMQPropertyConversionException {
+   public final String getStringProperty(SimpleString key) throws ActiveMQPropertyConversionException {
       return getStringProperty(key.toString());
    }
 
    @Override
-   public SimpleString getSimpleStringProperty(SimpleString key) throws ActiveMQPropertyConversionException {
+   public final SimpleString getSimpleStringProperty(SimpleString key) throws ActiveMQPropertyConversionException {
       return getSimpleStringProperty(key.toString());
    }
 
    @Override
-   public byte[] getBytesProperty(SimpleString key) throws ActiveMQPropertyConversionException {
+   public final byte[] getBytesProperty(SimpleString key) throws ActiveMQPropertyConversionException {
       return getBytesProperty(key.toString());
    }
    @Override
-   public SimpleString getSimpleStringProperty(String key) throws ActiveMQPropertyConversionException {
+   public final SimpleString getSimpleStringProperty(String key) throws ActiveMQPropertyConversionException {
       return SimpleString.toSimpleString((String) getApplicationPropertiesMap(false).get(key), getPropertyValuesPool());
    }
 
@@ -1509,139 +1415,139 @@ public class AMQPMessage extends RefCountMessage {
    // is done prior to the next dispatch the old view of the message will be sent.
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message putBooleanProperty(String key, boolean value) {
+   public final org.apache.activemq.artemis.api.core.Message putBooleanProperty(String key, boolean value) {
       getApplicationPropertiesMap(true).put(key, Boolean.valueOf(value));
       return this;
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message putByteProperty(String key, byte value) {
+   public final org.apache.activemq.artemis.api.core.Message putByteProperty(String key, byte value) {
       getApplicationPropertiesMap(true).put(key, Byte.valueOf(value));
       return this;
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message putBytesProperty(String key, byte[] value) {
+   public final org.apache.activemq.artemis.api.core.Message putBytesProperty(String key, byte[] value) {
       getApplicationPropertiesMap(true).put(key, value);
       return this;
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message putShortProperty(String key, short value) {
+   public final org.apache.activemq.artemis.api.core.Message putShortProperty(String key, short value) {
       getApplicationPropertiesMap(true).put(key, Short.valueOf(value));
       return this;
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message putCharProperty(String key, char value) {
+   public final org.apache.activemq.artemis.api.core.Message putCharProperty(String key, char value) {
       getApplicationPropertiesMap(true).put(key, Character.valueOf(value));
       return this;
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message putIntProperty(String key, int value) {
+   public final org.apache.activemq.artemis.api.core.Message putIntProperty(String key, int value) {
       getApplicationPropertiesMap(true).put(key, Integer.valueOf(value));
       return this;
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message putLongProperty(String key, long value) {
+   public final org.apache.activemq.artemis.api.core.Message putLongProperty(String key, long value) {
       getApplicationPropertiesMap(true).put(key, Long.valueOf(value));
       return this;
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message putFloatProperty(String key, float value) {
+   public final org.apache.activemq.artemis.api.core.Message putFloatProperty(String key, float value) {
       getApplicationPropertiesMap(true).put(key, Float.valueOf(value));
       return this;
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message putDoubleProperty(String key, double value) {
+   public final org.apache.activemq.artemis.api.core.Message putDoubleProperty(String key, double value) {
       getApplicationPropertiesMap(true).put(key, Double.valueOf(value));
       return this;
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message putBooleanProperty(SimpleString key, boolean value) {
+   public final org.apache.activemq.artemis.api.core.Message putBooleanProperty(SimpleString key, boolean value) {
       getApplicationPropertiesMap(true).put(key.toString(), Boolean.valueOf(value));
       return this;
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message putByteProperty(SimpleString key, byte value) {
+   public final org.apache.activemq.artemis.api.core.Message putByteProperty(SimpleString key, byte value) {
       return putByteProperty(key.toString(), value);
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message putBytesProperty(SimpleString key, byte[] value) {
+   public final org.apache.activemq.artemis.api.core.Message putBytesProperty(SimpleString key, byte[] value) {
       return putBytesProperty(key.toString(), value);
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message putShortProperty(SimpleString key, short value) {
+   public final org.apache.activemq.artemis.api.core.Message putShortProperty(SimpleString key, short value) {
       return putShortProperty(key.toString(), value);
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message putCharProperty(SimpleString key, char value) {
+   public final org.apache.activemq.artemis.api.core.Message putCharProperty(SimpleString key, char value) {
       return putCharProperty(key.toString(), value);
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message putIntProperty(SimpleString key, int value) {
+   public final org.apache.activemq.artemis.api.core.Message putIntProperty(SimpleString key, int value) {
       return putIntProperty(key.toString(), value);
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message putLongProperty(SimpleString key, long value) {
+   public final org.apache.activemq.artemis.api.core.Message putLongProperty(SimpleString key, long value) {
       return putLongProperty(key.toString(), value);
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message putFloatProperty(SimpleString key, float value) {
+   public final org.apache.activemq.artemis.api.core.Message putFloatProperty(SimpleString key, float value) {
       return putFloatProperty(key.toString(), value);
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message putDoubleProperty(SimpleString key, double value) {
+   public final org.apache.activemq.artemis.api.core.Message putDoubleProperty(SimpleString key, double value) {
       return putDoubleProperty(key.toString(), value);
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message putStringProperty(String key, String value) {
+   public final org.apache.activemq.artemis.api.core.Message putStringProperty(String key, String value) {
       getApplicationPropertiesMap(true).put(key, value);
       return this;
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message putObjectProperty(String key, Object value) throws ActiveMQPropertyConversionException {
+   public final org.apache.activemq.artemis.api.core.Message putObjectProperty(String key, Object value) throws ActiveMQPropertyConversionException {
       getApplicationPropertiesMap(true).put(key, value);
       return this;
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message putObjectProperty(SimpleString key, Object value) throws ActiveMQPropertyConversionException {
+   public final org.apache.activemq.artemis.api.core.Message putObjectProperty(SimpleString key, Object value) throws ActiveMQPropertyConversionException {
       return putObjectProperty(key.toString(), value);
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message putStringProperty(SimpleString key, SimpleString value) {
+   public final org.apache.activemq.artemis.api.core.Message putStringProperty(SimpleString key, SimpleString value) {
       return putStringProperty(key.toString(), value.toString());
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message putStringProperty(SimpleString key, String value) {
+   public final org.apache.activemq.artemis.api.core.Message putStringProperty(SimpleString key, String value) {
       return putStringProperty(key.toString(), value);
    }
 
    @Override
-   public SimpleString getLastValueProperty() {
+   public final SimpleString getLastValueProperty() {
       return getSimpleStringProperty(HDR_LAST_VALUE_NAME);
    }
 
    @Override
-   public org.apache.activemq.artemis.api.core.Message setLastValueProperty(SimpleString lastValueName) {
+   public final org.apache.activemq.artemis.api.core.Message setLastValueProperty(SimpleString lastValueName) {
       return putStringProperty(HDR_LAST_VALUE_NAME, lastValueName);
    }
 
@@ -1659,7 +1565,7 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public synchronized boolean acceptsConsumer(long consumer) {
+   public final synchronized boolean acceptsConsumer(long consumer) {
       if (rejectedConsumers == null) {
          return true;
       } else {
@@ -1668,7 +1574,7 @@ public class AMQPMessage extends RefCountMessage {
    }
 
    @Override
-   public synchronized void rejectConsumer(long consumer) {
+   public final synchronized void rejectConsumer(long consumer) {
       if (rejectedConsumers == null) {
          rejectedConsumers = new HashSet<>();
       }
@@ -1676,11 +1582,11 @@ public class AMQPMessage extends RefCountMessage {
       rejectedConsumers.add(consumer);
    }
 
-   private SimpleString.StringSimpleStringPool getPropertyKeysPool() {
+   protected SimpleString.StringSimpleStringPool getPropertyKeysPool() {
       return coreMessageObjectPools == null ? null : coreMessageObjectPools.getPropertiesStringSimpleStringPools().getPropertyKeysPool();
    }
 
-   private SimpleString.StringSimpleStringPool getPropertyValuesPool() {
+   protected SimpleString.StringSimpleStringPool getPropertyValuesPool() {
       return coreMessageObjectPools == null ? null : coreMessageObjectPools.getPropertiesStringSimpleStringPools().getPropertyValuesPool();
    }
 }
\ No newline at end of file
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPMessagePersister.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPMessagePersister.java
index 9ab0842..e98abcb 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPMessagePersister.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPMessagePersister.java
@@ -20,13 +20,14 @@ package org.apache.activemq.artemis.protocol.amqp.broker;
 import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
 import org.apache.activemq.artemis.api.core.Message;
 import org.apache.activemq.artemis.api.core.SimpleString;
-import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
 import org.apache.activemq.artemis.spi.core.protocol.MessagePersister;
 import org.apache.activemq.artemis.utils.DataConstants;
+import static org.apache.activemq.artemis.core.persistence.PersisterIDs.AMQPMessagePersister_ID;
 
 public class AMQPMessagePersister extends MessagePersister {
 
-   public static final byte ID = 2;
+   public static final byte ID = AMQPMessagePersister_ID;
 
    public static AMQPMessagePersister theInstance;
 
@@ -63,7 +64,7 @@ public class AMQPMessagePersister extends MessagePersister {
    }
 
    @Override
-   public Message decode(ActiveMQBuffer buffer, CoreMessageObjectPools pool) {
+   public Message decode(ActiveMQBuffer buffer, Message record,  CoreMessageObjectPools pool) {
       long id = buffer.readLong();
       long format = buffer.readLong();
       final SimpleString address;
@@ -72,7 +73,7 @@ public class AMQPMessagePersister extends MessagePersister {
       } else {
          address = SimpleString.readNullableSimpleString(buffer.byteBuf(), pool.getAddressDecoderPool());
       }
-      AMQPMessage record = new AMQPMessage(format);
+      record = new AMQPStandardMessage(format);
       record.reloadPersistence(buffer, pool);
       record.setMessageID(id);
       if (address != null) {
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPMessagePersisterV2.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPMessagePersisterV2.java
index d263fe9..1438f76 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPMessagePersisterV2.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPMessagePersisterV2.java
@@ -19,12 +19,14 @@ package org.apache.activemq.artemis.protocol.amqp.broker;
 
 import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
 import org.apache.activemq.artemis.api.core.Message;
-import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
 import org.apache.activemq.artemis.utils.DataConstants;
 import org.apache.activemq.artemis.utils.collections.TypedProperties;
+import static org.apache.activemq.artemis.core.persistence.PersisterIDs.AMQPMessagePersisterV2_ID;
 
 public class AMQPMessagePersisterV2 extends AMQPMessagePersister {
-   public static final byte ID = 3;
+
+   public static final byte ID = AMQPMessagePersisterV2_ID;
 
    public static AMQPMessagePersisterV2 theInstance;
 
@@ -70,8 +72,8 @@ public class AMQPMessagePersisterV2 extends AMQPMessagePersister {
    }
 
    @Override
-   public Message decode(ActiveMQBuffer buffer, CoreMessageObjectPools pool) {
-      AMQPMessage message = (AMQPMessage) super.decode(buffer, pool);
+   public Message decode(ActiveMQBuffer buffer, Message record, CoreMessageObjectPools pool) {
+      AMQPMessage message = (AMQPMessage) super.decode(buffer, record, pool);
       int size = buffer.readInt();
 
       if (size != 0) {
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPSessionCallback.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPSessionCallback.java
index dad5d54..a5d7930 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPSessionCallback.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPSessionCallback.java
@@ -29,7 +29,7 @@ import org.apache.activemq.artemis.api.core.RoutingType;
 import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
 import org.apache.activemq.artemis.core.io.IOCallback;
-import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
 import org.apache.activemq.artemis.core.paging.PagingManager;
 import org.apache.activemq.artemis.core.paging.PagingStore;
 import org.apache.activemq.artemis.core.persistence.OperationContext;
@@ -106,8 +106,7 @@ public class AMQPSessionCallback implements SessionCallback {
 
    private final boolean directDeliver;
 
-
-   private CoreMessageObjectPools coreMessageObjectPools = new CoreMessageObjectPools();
+   private final CoreMessageObjectPools coreMessageObjectPools = new CoreMessageObjectPools();
 
    private final AddressQueryCache<AddressQueryResult> addressQueryCache = new AddressQueryCache<>();
 
@@ -129,6 +128,14 @@ public class AMQPSessionCallback implements SessionCallback {
       this.directDeliver = manager.isDirectDeliver();
    }
 
+   public StorageManager getStorageManager() {
+      return storageManager;
+   }
+
+   public CoreMessageObjectPools getCoreMessageObjectPools() {
+      return coreMessageObjectPools;
+   }
+
    @Override
    public boolean isWritable(ReadyListener callback, Object protocolContext) {
       ProtonServerSenderContext senderContext = (ProtonServerSenderContext) protocolContext;
@@ -425,6 +432,10 @@ public class AMQPSessionCallback implements SessionCallback {
       ((ServerConsumer) consumer).receiveCredits(-1);
    }
 
+   public AMQPStandardMessage createStandardMessage(Delivery delivery, ReadableBuffer data) {
+      return new AMQPStandardMessage(delivery.getMessageFormat(), data, null, coreMessageObjectPools);
+   }
+
    public void serverSend(final ProtonServerReceiverContext context,
                           final Transaction transaction,
                           final Receiver receiver,
@@ -433,7 +444,17 @@ public class AMQPSessionCallback implements SessionCallback {
                           int messageFormat,
                           ReadableBuffer data,
                           RoutingContext routingContext) throws Exception {
-      AMQPMessage message = new AMQPMessage(messageFormat, data, null, coreMessageObjectPools);
+      AMQPStandardMessage message = new AMQPStandardMessage(messageFormat, data, null, coreMessageObjectPools);
+      serverSend(context, transaction, receiver, delivery, address, routingContext, message);
+   }
+
+   public void serverSend(ProtonServerReceiverContext context,
+                          Transaction transaction,
+                          Receiver receiver,
+                          Delivery delivery,
+                          SimpleString address,
+                          RoutingContext routingContext,
+                          AMQPMessage message) throws Exception {
       if (address != null) {
          message.setAddress(address);
       } else {
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPStandardMessage.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPStandardMessage.java
new file mode 100644
index 0000000..99d3be3
--- /dev/null
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPStandardMessage.java
@@ -0,0 +1,262 @@
+/*
+ * 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.activemq.artemis.protocol.amqp.broker;
+
+import java.nio.ByteBuffer;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.PooledByteBufAllocator;
+import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
+import org.apache.activemq.artemis.api.core.ActiveMQException;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.Persister;
+import org.apache.activemq.artemis.protocol.amqp.util.NettyWritable;
+import org.apache.activemq.artemis.protocol.amqp.util.TLSEncode;
+import org.apache.activemq.artemis.utils.DataConstants;
+import org.apache.activemq.artemis.utils.collections.TypedProperties;
+import org.apache.qpid.proton.codec.EncoderImpl;
+import org.apache.qpid.proton.codec.ReadableBuffer;
+import org.apache.qpid.proton.codec.WritableBuffer;
+
+// see https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#section-message-format
+public class AMQPStandardMessage extends AMQPMessage {
+
+   // Buffer and state for the data backing this message.
+   protected ReadableBuffer data;
+
+   /**
+    * Creates a new {@link AMQPStandardMessage} instance from binary encoded message data.
+    *
+    * @param messageFormat   The Message format tag given the in Transfer that carried this message
+    * @param data            The encoded AMQP message
+    * @param extraProperties Broker specific extra properties that should be carried with this message
+    */
+   public AMQPStandardMessage(long messageFormat, byte[] data, TypedProperties extraProperties) {
+      this(messageFormat, data, extraProperties, null);
+   }
+
+   /**
+    * Creates a new {@link AMQPStandardMessage} instance from binary encoded message data.
+    *
+    * @param messageFormat          The Message format tag given the in Transfer that carried this message
+    * @param data                   The encoded AMQP message
+    * @param extraProperties        Broker specific extra properties that should be carried with this message
+    * @param coreMessageObjectPools Object pool used to accelerate some String operations.
+    */
+   public AMQPStandardMessage(long messageFormat,
+                              byte[] data,
+                              TypedProperties extraProperties,
+                              CoreMessageObjectPools coreMessageObjectPools) {
+      this(messageFormat, ReadableBuffer.ByteBufferReader.wrap(data), extraProperties, coreMessageObjectPools);
+   }
+
+   /**
+    * Creates a new {@link AMQPStandardMessage} instance from binary encoded message data.
+    *
+    * @param messageFormat          The Message format tag given the in Transfer that carried this message
+    * @param data                   The encoded AMQP message in an {@link ReadableBuffer} wrapper.
+    * @param extraProperties        Broker specific extra properties that should be carried with this message
+    * @param coreMessageObjectPools Object pool used to accelerate some String operations.
+    */
+   public AMQPStandardMessage(long messageFormat,
+                              ReadableBuffer data,
+                              TypedProperties extraProperties,
+                              CoreMessageObjectPools coreMessageObjectPools) {
+      super(messageFormat, extraProperties, coreMessageObjectPools);
+      this.data = data;
+      ensureMessageDataScanned();
+   }
+
+   /**
+    * Internal constructor used for persistence reload of the message.
+    * <p>
+    * The message will not be usable until the persistence mechanism populates the message
+    * data and triggers a parse of the message contents to fill in the message state.
+    *
+    * @param messageFormat The Message format tag given the in Transfer that carried this message
+    */
+   AMQPStandardMessage(long messageFormat) {
+      super(messageFormat);
+   }
+
+   @Override
+   public org.apache.activemq.artemis.api.core.Message copy() {
+      ensureDataIsValid();
+
+      ReadableBuffer view = data.duplicate().rewind();
+      byte[] newData = new byte[view.remaining()];
+
+      // Copy the full message contents with delivery annotations as they will
+      // be trimmed on send and may become useful on the broker at a later time.
+      view.get(newData);
+
+      AMQPStandardMessage newEncode = new AMQPStandardMessage(this.messageFormat, newData, extraProperties, coreMessageObjectPools);
+      newEncode.setMessageID(this.getMessageID());
+      return newEncode;
+   }
+
+   @Override
+   public int getEncodeSize() {
+      ensureDataIsValid();
+      // The encoded size will exclude any delivery annotations that are present as we will clip them.
+      return data.remaining() - encodedDeliveryAnnotationsSize + getDeliveryAnnotationsForSendBufferSize();
+   }
+
+   @Override
+   protected ReadableBuffer getData() {
+      return data;
+   }
+
+   @Override
+   public int getMemoryEstimate() {
+      if (memoryEstimate == -1) {
+         memoryEstimate = memoryOffset + (data != null ? data.capacity() : 0);
+      }
+
+      return memoryEstimate;
+   }
+
+   @Override
+   public void persist(ActiveMQBuffer targetRecord) {
+      ensureDataIsValid();
+      targetRecord.writeInt(internalPersistSize());
+      if (data.hasArray()) {
+         targetRecord.writeBytes(data.array(), data.arrayOffset(), data.remaining());
+      } else {
+         targetRecord.writeBytes(data.byteBuffer());
+      }
+   }
+
+   @Override
+   public final org.apache.activemq.artemis.api.core.Message copy(long newID) {
+      return copy().setMessageID(newID);
+   }
+
+   @Override
+   public int getPersistSize() {
+      ensureDataIsValid();
+      return DataConstants.SIZE_INT + internalPersistSize();
+   }
+
+   @Override
+   public void reloadPersistence(ActiveMQBuffer record, CoreMessageObjectPools pools) {
+      int size = record.readInt();
+      byte[] recordArray = new byte[size];
+      record.readBytes(recordArray);
+      data = ReadableBuffer.ByteBufferReader.wrap(ByteBuffer.wrap(recordArray));
+
+      // Message state is now that the underlying buffer is loaded, but the contents not yet scanned
+      resetMessageData();
+      modified = false;
+      messageDataScanned = RELOAD_PERSISTENCE;
+      // can happen when moved to a durable location.  We must re-encode here to
+      // avoid a subsequent redelivery from suddenly appearing with a durable header
+      // tag when the initial delivery did not.
+      if (!isDurable()) {
+         setDurable(true);
+         reencode();
+      }
+   }
+
+   @Override
+   public long getPersistentSize() throws ActiveMQException {
+      return getEncodeSize();
+   }
+
+   @Override
+   public Persister<org.apache.activemq.artemis.api.core.Message> getPersister() {
+      return AMQPMessagePersisterV2.getInstance();
+   }
+
+   @Override
+   public void reencode() {
+      ensureMessageDataScanned();
+
+      // The address was updated on a message with Properties so we update them
+      // for cases where there are no properties we aren't adding a properties
+      // section which seems wrong but this preserves previous behavior.
+      if (properties != null && address != null) {
+         properties.setTo(address.toString());
+      }
+
+      encodeMessage();
+      scanMessageData();
+   }
+
+   @Override
+   protected synchronized void ensureDataIsValid() {
+      if (modified) {
+         encodeMessage();
+      }
+   }
+
+   @Override
+   protected synchronized void encodeMessage() {
+      this.modified = false;
+      this.messageDataScanned = NOT_SCANNED;
+      int estimated = Math.max(1500, data != null ? data.capacity() + 1000 : 0);
+      ByteBuf buffer = PooledByteBufAllocator.DEFAULT.heapBuffer(estimated);
+      EncoderImpl encoder = TLSEncode.getEncoder();
+
+      try {
+         NettyWritable writable = new NettyWritable(buffer);
+
+         encoder.setByteBuffer(writable);
+         if (header != null) {
+            encoder.writeObject(header);
+         }
+
+         // We currently do not encode any delivery annotations but it is conceivable
+         // that at some point they may need to be preserved, this is where that needs
+         // to happen.
+
+         if (messageAnnotations != null) {
+            encoder.writeObject(messageAnnotations);
+         }
+         if (properties != null) {
+            encoder.writeObject(properties);
+         }
+
+         // Whenever possible avoid encoding sections we don't need to by
+         // checking if application properties where loaded or added and
+         // encoding only in that case.
+         if (applicationProperties != null) {
+            encoder.writeObject(applicationProperties);
+
+            // Now raw write the remainder body and footer if present.
+            if (data != null && remainingBodyPosition != VALUE_NOT_PRESENT) {
+               writable.put(data.position(remainingBodyPosition));
+            }
+         } else if (data != null && applicationPropertiesPosition != VALUE_NOT_PRESENT) {
+            // Writes out ApplicationProperties, Body and Footer in one go if present.
+            writable.put(data.position(applicationPropertiesPosition));
+         } else if (data != null && remainingBodyPosition != VALUE_NOT_PRESENT) {
+            // No Application properties at all so raw write Body and Footer sections
+            writable.put(data.position(remainingBodyPosition));
+         }
+
+         byte[] bytes = new byte[buffer.writerIndex()];
+
+         buffer.readBytes(bytes);
+         data = ReadableBuffer.ByteBufferReader.wrap(ByteBuffer.wrap(bytes));
+      } finally {
+         encoder.setByteBuffer((WritableBuffer) null);
+         buffer.release();
+      }
+   }
+}
+
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AmqpReadableBuffer.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AmqpReadableBuffer.java
new file mode 100644
index 0000000..0cdae0f
--- /dev/null
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AmqpReadableBuffer.java
@@ -0,0 +1,227 @@
+/*
+ * 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.activemq.artemis.protocol.amqp.broker;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.StandardCharsets;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.util.internal.PlatformDependent;
+import org.apache.qpid.proton.codec.ReadableBuffer;
+import org.apache.qpid.proton.codec.WritableBuffer;
+
+public class AmqpReadableBuffer implements ReadableBuffer {
+   private ByteBuffer buffer;
+
+   public AmqpReadableBuffer(ByteBuffer buffer) {
+      this.buffer = buffer;
+   }
+
+   public ByteBuffer getBuffer() {
+      return this.buffer;
+   }
+
+   @Override
+   public int capacity() {
+      return this.buffer.capacity();
+   }
+
+   @Override
+   public boolean hasArray() {
+      return this.buffer.hasArray();
+   }
+
+   @Override
+   public byte[] array() {
+      if (this.buffer.hasArray()) {
+         return this.buffer.array();
+      } else {
+         byte[] bytes = new byte[buffer.remaining()];
+         buffer.get(bytes);
+         return bytes;
+      }
+   }
+
+   public void freeDirectBuffer() {
+      // releasing direct buffer created from mmap
+      PlatformDependent.freeDirectBuffer(buffer);
+   }
+
+   @Override
+   public int arrayOffset() {
+      return this.buffer.arrayOffset();
+   }
+
+   @Override
+   public ReadableBuffer reclaimRead() {
+      return this;
+   }
+
+   @Override
+   public byte get() {
+      return this.buffer.get();
+   }
+
+   @Override
+   public byte get(int index) {
+      return this.buffer.get(index);
+   }
+
+   @Override
+   public int getInt() {
+      return this.buffer.getInt();
+   }
+
+   @Override
+   public long getLong() {
+      return this.buffer.getLong();
+   }
+
+   @Override
+   public short getShort() {
+      return this.buffer.getShort();
+   }
+
+   @Override
+   public float getFloat() {
+      return this.buffer.getFloat();
+   }
+
+   @Override
+   public double getDouble() {
+      return this.buffer.getDouble();
+   }
+
+   @Override
+   public ReadableBuffer get(byte[] target, int offset, int length) {
+      this.buffer.get(target, offset, length);
+      return this;
+   }
+
+   @Override
+   public ReadableBuffer get(byte[] target) {
+      this.buffer.get(target);
+      return this;
+   }
+
+   @Override
+   public ReadableBuffer get(WritableBuffer target) {
+      int start = target.position();
+      if (this.buffer.hasArray()) {
+         target.put(this.buffer.array(), this.buffer.arrayOffset() + this.buffer.position(), this.buffer.remaining());
+      } else {
+         target.put(this.buffer);
+      }
+
+      int written = target.position() - start;
+      this.buffer.position(this.buffer.position() + written);
+      return this;
+   }
+
+   @Override
+   public ReadableBuffer slice() {
+      return new AmqpReadableBuffer(this.buffer.slice());
+   }
+
+   @Override
+   public ReadableBuffer flip() {
+      this.buffer.flip();
+      return this;
+   }
+
+   @Override
+   public ReadableBuffer limit(int limit) {
+      this.buffer.limit(limit);
+      return this;
+   }
+
+   @Override
+   public int limit() {
+      return this.buffer.limit();
+   }
+
+   @Override
+   public ReadableBuffer position(int position) {
+      this.buffer.position(position);
+      return this;
+   }
+
+   @Override
+   public int position() {
+      return this.buffer.position();
+   }
+
+   @Override
+   public ReadableBuffer mark() {
+      this.buffer.mark();
+      return this;
+   }
+
+   @Override
+   public ReadableBuffer reset() {
+      this.buffer.reset();
+      return this;
+   }
+
+   @Override
+   public ReadableBuffer rewind() {
+      this.buffer.rewind();
+      return this;
+   }
+
+   @Override
+   public ReadableBuffer clear() {
+      this.buffer.clear();
+      return this;
+   }
+
+   @Override
+   public int remaining() {
+      return this.buffer.remaining();
+   }
+
+   @Override
+   public boolean hasRemaining() {
+      return this.buffer.hasRemaining();
+   }
+
+   @Override
+   public ReadableBuffer duplicate() {
+      return new AmqpReadableBuffer(this.buffer.duplicate());
+   }
+
+   @Override
+   public ByteBuffer byteBuffer() {
+      return this.buffer;
+   }
+
+   @Override
+   public String readUTF8() throws CharacterCodingException {
+      ByteBuf wrappedNetty = Unpooled.wrappedBuffer(buffer);
+      return wrappedNetty.toString(StandardCharsets.UTF_8);
+   }
+
+   @Override
+   public String readString(CharsetDecoder decoder) throws CharacterCodingException {
+      ByteBuf wrappedNetty = Unpooled.wrappedBuffer(buffer);
+      return wrappedNetty.toString(StandardCharsets.UTF_8);
+   }
+}
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/ProtonProtocolManager.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/ProtonProtocolManager.java
index e0af481..f76a4d5 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/ProtonProtocolManager.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/ProtonProtocolManager.java
@@ -65,6 +65,11 @@ public class ProtonProtocolManager extends AbstractProtocolManager<AMQPMessage,
 
    private final Map<SimpleString, RoutingType> prefixes = new HashMap<>();
 
+   /** minLargeMessageSize determines when a message should be considered as large.
+    *  minLargeMessageSize = -1 basically disables large message control over AMQP.
+    */
+   private int amqpMinLargeMessageSize = 100 * 1024;
+
    private int amqpCredits = AmqpSupport.AMQP_CREDITS_DEFAULT;
 
    private int amqpLowCredits = AmqpSupport.AMQP_LOW_CREDITS_DEFAULT;
@@ -112,6 +117,15 @@ public class ProtonProtocolManager extends AbstractProtocolManager<AMQPMessage,
 
    }
 
+   public int getAmqpMinLargeMessageSize() {
+      return amqpMinLargeMessageSize;
+   }
+
+   public ProtonProtocolManager setAmqpMinLargeMessageSize(int amqpMinLargeMessageSize) {
+      this.amqpMinLargeMessageSize = amqpMinLargeMessageSize;
+      return this;
+   }
+
    public boolean isAmqpDuplicateDetection() {
       return amqpDuplicateDetection;
    }
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/ProtonProtocolManagerFactory.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/ProtonProtocolManagerFactory.java
index 7470d60..6e956cc 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/ProtonProtocolManagerFactory.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/ProtonProtocolManagerFactory.java
@@ -21,7 +21,6 @@ import java.util.Map;
 
 import org.apache.activemq.artemis.api.core.BaseInterceptor;
 import org.apache.activemq.artemis.api.core.Message;
-import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
 import org.apache.activemq.artemis.core.persistence.Persister;
 import org.apache.activemq.artemis.core.server.ActiveMQServer;
 import org.apache.activemq.artemis.spi.core.protocol.AbstractProtocolManagerFactory;
@@ -40,9 +39,9 @@ public class ProtonProtocolManagerFactory extends AbstractProtocolManagerFactory
    private static String[] SUPPORTED_PROTOCOLS = {AMQP_PROTOCOL_NAME};
 
    @Override
-   public Persister<Message, CoreMessageObjectPools>[] getPersister() {
+   public Persister<Message>[] getPersister() {
 
-      Persister[] persisters = new Persister[]{AMQPMessagePersister.getInstance(), AMQPMessagePersisterV2.getInstance()};
+      Persister[] persisters = new Persister[]{AMQPMessagePersister.getInstance(), AMQPMessagePersisterV2.getInstance(), AMQPLargeMessagePersister.getInstance()};
       return persisters;
    }
 
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/AMQPConverter.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/AMQPConverter.java
index e67fc67..09b4ce3 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/AMQPConverter.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/AMQPConverter.java
@@ -17,7 +17,8 @@
 package org.apache.activemq.artemis.protocol.amqp.converter;
 
 import org.apache.activemq.artemis.api.core.ICoreMessage;
-import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.StorageManager;
 import org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessage;
 import org.apache.activemq.artemis.spi.core.protocol.MessageConverter;
 
@@ -34,8 +35,8 @@ public class AMQPConverter implements MessageConverter<AMQPMessage> {
    }
 
    @Override
-   public AMQPMessage fromCore(ICoreMessage coreMessage) throws Exception {
-      return CoreAmqpConverter.fromCore(coreMessage);
+   public AMQPMessage fromCore(ICoreMessage coreMessage, StorageManager storageManager) throws Exception {
+      return CoreAmqpConverter.fromCore(coreMessage, storageManager);
    }
 
    @Override
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/AMQPMessageSupport.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/AMQPMessageSupport.java
index 21116a6..dd81c4c 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/AMQPMessageSupport.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/AMQPMessageSupport.java
@@ -36,7 +36,7 @@ import javax.jms.TemporaryTopic;
 import javax.jms.Topic;
 
 import org.apache.activemq.artemis.core.message.impl.CoreMessage;
-import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
 import org.apache.activemq.artemis.jms.client.ActiveMQDestination;
 import org.apache.activemq.artemis.jms.client.ActiveMQQueue;
 import org.apache.activemq.artemis.jms.client.ActiveMQTemporaryQueue;
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/AmqpCoreConverter.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/AmqpCoreConverter.java
index 3c21e08..ad079e9 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/AmqpCoreConverter.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/AmqpCoreConverter.java
@@ -64,7 +64,7 @@ import javax.jms.JMSException;
 
 import org.apache.activemq.artemis.api.core.ActiveMQPropertyConversionException;
 import org.apache.activemq.artemis.api.core.ICoreMessage;
-import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
 import org.apache.activemq.artemis.jms.client.ActiveMQDestination;
 import org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessage;
 import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSMessage;
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/CoreAmqpConverter.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/CoreAmqpConverter.java
index dcb054a..b9375ce 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/CoreAmqpConverter.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/CoreAmqpConverter.java
@@ -68,7 +68,9 @@ import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
 import org.apache.activemq.artemis.api.core.ICoreMessage;
 import org.apache.activemq.artemis.api.core.Message;
 import org.apache.activemq.artemis.api.core.SimpleString;
+import org.apache.activemq.artemis.core.persistence.StorageManager;
 import org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessage;
+import org.apache.activemq.artemis.protocol.amqp.broker.AMQPStandardMessage;
 import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSBytesMessage;
 import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSMapMessage;
 import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSMessage;
@@ -107,22 +109,22 @@ public class CoreAmqpConverter {
 
    private static Logger logger = Logger.getLogger(CoreAmqpConverter.class);
 
-   public static AMQPMessage checkAMQP(Message message) throws Exception {
+   public static AMQPMessage checkAMQP(Message message, StorageManager storageManager) throws Exception {
       if (message instanceof AMQPMessage) {
          return (AMQPMessage)message;
       } else {
          // It will first convert to Core, then to AMQP
-         return fromCore(message.toCore());
+         return fromCore(message.toCore(), storageManager);
       }
    }
 
-   public static AMQPMessage fromCore(ICoreMessage coreMessage) throws Exception {
+   public static AMQPMessage fromCore(ICoreMessage coreMessage, StorageManager storageManager) throws Exception {
       if (coreMessage == null) {
          return null;
       }
       if (coreMessage.isServerMessage() && coreMessage.isLargeMessage() && coreMessage.getType() == EMBEDDED_TYPE) {
          //large AMQP messages received across cluster nodes
-         final Message message = EmbedMessageUtil.extractEmbedded(coreMessage);
+         final Message message = EmbedMessageUtil.extractEmbedded(coreMessage, storageManager);
          if (message instanceof AMQPMessage) {
             return (AMQPMessage) message;
          }
@@ -363,7 +365,7 @@ public class CoreAmqpConverter {
          byte[] data = new byte[buffer.writerIndex()];
          buffer.readBytes(data);
 
-         AMQPMessage amqpMessage = new AMQPMessage(messageFormat, data, null);
+         AMQPMessage amqpMessage = new AMQPStandardMessage(messageFormat, data, null);
          amqpMessage.setMessageID(message.getInnerMessage().getMessageID());
          amqpMessage.setReplyTo(coreMessage.getReplyTo());
          return amqpMessage;
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPConnectionContext.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPConnectionContext.java
index e6ec486..63e18a7 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPConnectionContext.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPConnectionContext.java
@@ -188,6 +188,9 @@ public class AMQPConnectionContext extends ProtonInitializable implements EventH
       return false;
    }
 
+   public void instantFlush() {
+      handler.instantFlush();
+   }
    public void flush() {
       handler.flush();
    }
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/ProtonServerReceiverContext.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/ProtonServerReceiverContext.java
index 8a412bc..a6cfb5e 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/ProtonServerReceiverContext.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/ProtonServerReceiverContext.java
@@ -25,12 +25,15 @@ import org.apache.activemq.artemis.api.core.ActiveMQExceptionType;
 import org.apache.activemq.artemis.api.core.ActiveMQSecurityException;
 import org.apache.activemq.artemis.api.core.RoutingType;
 import org.apache.activemq.artemis.api.core.SimpleString;
+import org.apache.activemq.artemis.core.persistence.impl.nullpm.NullStorageManager;
 import org.apache.activemq.artemis.core.security.CheckType;
 import org.apache.activemq.artemis.core.security.SecurityAuth;
 import org.apache.activemq.artemis.core.server.RoutingContext;
 import org.apache.activemq.artemis.core.server.impl.AddressInfo;
 import org.apache.activemq.artemis.core.server.impl.RoutingContextImpl;
 import org.apache.activemq.artemis.core.transaction.Transaction;
+import org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessage;
+import org.apache.activemq.artemis.protocol.amqp.broker.AMQPLargeMessage;
 import org.apache.activemq.artemis.protocol.amqp.broker.AMQPSessionCallback;
 import org.apache.activemq.artemis.protocol.amqp.exceptions.ActiveMQAMQPException;
 import org.apache.activemq.artemis.protocol.amqp.exceptions.ActiveMQAMQPInternalErrorException;
@@ -121,6 +124,8 @@ public class ProtonServerReceiverContext extends ProtonInitializable implements
    // Used by the broker to decide when to refresh clients credit.  This is not used when client requests credit.
    private final int minCreditRefresh;
 
+   private final int minLargeMessageSize;
+
    public ProtonServerReceiverContext(AMQPSessionCallback sessionSPI,
                                       AMQPConnectionContext connection,
                                       AMQPSessionContext protonSession,
@@ -134,6 +139,7 @@ public class ProtonServerReceiverContext extends ProtonInitializable implements
       this.minCreditRefresh = connection.getAmqpLowCredits();
       this.creditRunnable = createCreditRunnable(amqpCredits, minCreditRefresh, receiver, connection).setRan();
       useModified = this.connection.getProtocolManager().isUseModifiedForTransientDeliveryErrors();
+      this.minLargeMessageSize = connection.getProtocolManager().getAmqpMinLargeMessageSize();
    }
 
    @Override
@@ -264,6 +270,8 @@ public class ProtonServerReceiverContext extends ProtonInitializable implements
       return defaultRoutingType;
    }
 
+   volatile AMQPLargeMessage currentLargeMessage;
+
    /*
     * called when Proton receives a message to be delivered via a Delivery.
     *
@@ -278,39 +286,78 @@ public class ProtonServerReceiverContext extends ProtonInitializable implements
          return;
       }
 
-      if (delivery.isAborted()) {
-         // Aborting implicitly remotely settles, so advance
-         // receiver to the next delivery and settle locally.
-         receiver.advance();
-         delivery.settle();
+      try {
+         if (delivery.isAborted()) {
+            // Aborting implicitly remotely settles, so advance
+            // receiver to the next delivery and settle locally.
+            receiver.advance();
+            delivery.settle();
+
+            // Replenish the credit if not doing a drain
+            if (!receiver.getDrain()) {
+               receiver.flow(1);
+            }
+
+            return;
+         } else if (delivery.isPartial()) {
+            if (sessionSPI.getStorageManager() instanceof NullStorageManager) {
+               // if we are dealing with the NullStorageManager we should just make it a regular message anyways
+               return;
+            }
+
+            if (currentLargeMessage == null) {
+               // minLargeMessageSize < 0 means no large message treatment, make it disabled
+               if (minLargeMessageSize > 0 && delivery.available() >= minLargeMessageSize) {
+                  initializeCurrentLargeMessage(delivery, receiver);
+               }
+            } else {
+               currentLargeMessage.addBytes(receiver.recv());
+            }
+
+            return;
+         }
+
+         AMQPMessage message;
 
-         // Replenish the credit if not doing a drain
-         if (!receiver.getDrain()) {
-            receiver.flow(1);
+         // this is treating the case where the frameSize > minLargeMessage and the message is still large enough
+         if (!(sessionSPI.getStorageManager() instanceof NullStorageManager) && currentLargeMessage == null && minLargeMessageSize > 0 && delivery.available() >= minLargeMessageSize) {
+            initializeCurrentLargeMessage(delivery, receiver);
          }
 
-         return;
-      } else if (delivery.isPartial()) {
-         return;
-      }
+         if (currentLargeMessage != null) {
+            currentLargeMessage.addBytes(receiver.recv());
+            receiver.advance();
+            currentLargeMessage.finishParse();
+            message = currentLargeMessage;
+            currentLargeMessage = null;
+         } else {
+            ReadableBuffer data = receiver.recv();
+            receiver.advance();
+            message = sessionSPI.createStandardMessage(delivery, data);
+         }
 
-      ReadableBuffer data = receiver.recv();
-      receiver.advance();
-      Transaction tx = null;
+         Transaction tx = null;
+         if (delivery.getRemoteState() instanceof TransactionalState) {
+            TransactionalState txState = (TransactionalState) delivery.getRemoteState();
+            tx = this.sessionSPI.getTransaction(txState.getTxnId(), false);
+         }
 
-      if (delivery.getRemoteState() instanceof TransactionalState) {
-         TransactionalState txState = (TransactionalState) delivery.getRemoteState();
-         tx = this.sessionSPI.getTransaction(txState.getTxnId(), false);
+         actualDelivery(message, delivery, receiver, tx);
+      } catch (Exception e) {
+         throw new ActiveMQAMQPInternalErrorException(e.getMessage(), e);
       }
 
-      final Transaction txUsed = tx;
+   }
 
-      actualDelivery(delivery, receiver, data, txUsed);
+   private void initializeCurrentLargeMessage(Delivery delivery, Receiver receiver) throws Exception {
+      long id = sessionSPI.getStorageManager().generateID();
+      currentLargeMessage = new AMQPLargeMessage(id, delivery.getMessageFormat(), null, sessionSPI.getCoreMessageObjectPools(), sessionSPI.getStorageManager());
+      currentLargeMessage.addBytes(receiver.recv());
    }
 
-   private void actualDelivery(Delivery delivery, Receiver receiver, ReadableBuffer data, Transaction tx) {
+   private void actualDelivery(AMQPMessage message, Delivery delivery, Receiver receiver, Transaction tx) {
       try {
-         sessionSPI.serverSend(this, tx, receiver, delivery, address, delivery.getMessageFormat(), data, routingContext);
+         sessionSPI.serverSend(this, tx, receiver, delivery, address, routingContext, message);
       } catch (Exception e) {
          log.warn(e.getMessage(), e);
          DeliveryState deliveryState = determineDeliveryState(((Source) receiver.getSource()),
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/ProtonServerSenderContext.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/ProtonServerSenderContext.java
index 3ab2699..eac0f10 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/ProtonServerSenderContext.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/ProtonServerSenderContext.java
@@ -16,6 +16,7 @@
  */
 package org.apache.activemq.artemis.protocol.amqp.proton;
 
+import java.nio.ByteBuffer;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
@@ -29,6 +30,7 @@ import org.apache.activemq.artemis.api.core.Message;
 import org.apache.activemq.artemis.api.core.RoutingType;
 import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.core.io.IOCallback;
+import org.apache.activemq.artemis.core.message.LargeBodyReader;
 import org.apache.activemq.artemis.core.persistence.OperationContext;
 import org.apache.activemq.artemis.core.server.AddressQueryResult;
 import org.apache.activemq.artemis.core.server.Consumer;
@@ -37,6 +39,7 @@ import org.apache.activemq.artemis.core.server.QueueQueryResult;
 import org.apache.activemq.artemis.core.server.ServerConsumer;
 import org.apache.activemq.artemis.core.server.impl.ServerConsumerImpl;
 import org.apache.activemq.artemis.jms.client.ActiveMQDestination;
+import org.apache.activemq.artemis.protocol.amqp.broker.AMQPLargeMessage;
 import org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessage;
 import org.apache.activemq.artemis.protocol.amqp.broker.AMQPSessionCallback;
 import org.apache.activemq.artemis.protocol.amqp.broker.ActiveMQProtonRemotingConnection;
@@ -110,6 +113,12 @@ public class ProtonServerSenderContext extends ProtonInitializable implements Pr
    private SimpleString tempQueueName;
    private final AtomicBoolean draining = new AtomicBoolean(false);
 
+   // once a large message is accepted, we shouldn't accept any further messages
+   // as large message could be interrupted due to flow control and resumed at the same message
+   volatile boolean hasLarge = false;
+   volatile LargeMessageDeliveryContext pendingLargeMessage = null;
+
+
    private int credits = 0;
 
    private AtomicInteger pending = new AtomicInteger(0);
@@ -170,6 +179,11 @@ public class ProtonServerSenderContext extends ProtonInitializable implements Pr
    }
 
    public boolean hasCredits() {
+      if (hasLarge) {
+         // we will resume accepting once the large message is finished
+         return false;
+      }
+
       if (!connection.flowControl(onflowControlReady)) {
          return false;
       }
@@ -764,9 +778,13 @@ public class ProtonServerSenderContext extends ProtonInitializable implements Pr
             credits--;
          }
 
+         if (messageReference.getMessage() instanceof AMQPLargeMessage) {
+            hasLarge = true;
+         }
+
          if (messageReference instanceof Runnable && consumer.allowReferenceCallback()) {
             messageReference.onDelivery(executeDelivery);
-            connection.runNow((Runnable)messageReference);
+            connection.runNow((Runnable) messageReference);
          } else {
             connection.runNow(() -> executeDelivery(messageReference));
          }
@@ -785,39 +803,78 @@ public class ProtonServerSenderContext extends ProtonInitializable implements Pr
             log.debug("Not delivering message " + messageReference + " as the sender is closed and credits were available, if you see too many of these it means clients are issuing credits and closing the connection with pending credits a lot of times");
             return;
          }
-         AMQPMessage message = CoreAmqpConverter.checkAMQP(messageReference.getMessage());
+         AMQPMessage message = CoreAmqpConverter.checkAMQP(messageReference.getMessage(), sessionSPI.getStorageManager());
 
          if (sessionSPI.invokeOutgoing(message, (ActiveMQProtonRemotingConnection) sessionSPI.getTransportConnection().getProtocolConnection()) != null) {
             return;
          }
+         if (message instanceof AMQPLargeMessage) {
+            deliverLarge(messageReference, (AMQPLargeMessage) message);
+         } else {
+            deliverStandard(messageReference, message);
+         }
 
-         // Let the Message decide how to present the message bytes
-         ReadableBuffer sendBuffer = message.getSendBuffer(messageReference.getDeliveryCount());
-         // we only need a tag if we are going to settle later
-         byte[] tag = preSettle ? new byte[0] : protonSession.getTag();
+      } catch (Exception e) {
+         log.warn(e.getMessage(), e);
+         brokerConsumer.errorProcessing(e, messageReference);
+      }
+   }
 
-         boolean releaseRequired = sendBuffer instanceof NettyReadable;
-         final Delivery delivery;
-         delivery = sender.delivery(tag, 0, tag.length);
-         delivery.setMessageFormat((int) message.getMessageFormat());
-         delivery.setContext(messageReference);
+   private class LargeMessageDeliveryContext {
+
+      LargeMessageDeliveryContext(MessageReference reference, AMQPLargeMessage message, Delivery delivery) {
+         this.position = 0L;
+         this.reference = reference;
+         this.message = message;
+         this.delivery = delivery;
+      }
 
+      long position;
+      final MessageReference reference;
+      final AMQPLargeMessage message;
+      final Delivery delivery;
+
+      void resume() {
+         connection.runNow(this::deliver);
+      }
+      private static final int BUFFER_LENGTH = 1024;
+
+      void deliver() {
+         // Let the Message decide how to present the message bytes
+         LargeBodyReader context = message.getLargeBodyReader();
          try {
 
-            if (releaseRequired) {
-               sender.send(sendBuffer);
-               // Above send copied, so release now if needed
-               releaseRequired = false;
-               ((NettyReadable) sendBuffer).getByteBuf().release();
-            } else {
-               // Don't have pooled content, no need to release or copy.
-               sender.sendNoCopy(sendBuffer);
+            context.open();
+            try {
+               context.position(position);
+               long bodySize = context.getSize();
+
+               // TODO: it would be nice to use pooled buffer here,
+               //       however I would need a version of ReadableBuffer for Netty
+               ByteBuffer buf = ByteBuffer.allocate(BUFFER_LENGTH);
+
+               for (; position < bodySize; ) {
+                  if (!connection.flowControl(this::resume)) {
+                     context.close();
+                     return;
+                  }
+                  buf.clear();
+                  int size = context.readInto(buf);
+
+                  sender.send(buf.array(), 0, size);
+
+                  connection.instantFlush();
+
+                  position += size;
+               }
+            } finally {
+               context.close();
             }
 
             if (preSettle) {
                // Presettled means the client implicitly accepts any delivery we send it.
                try {
-                  sessionSPI.ack(null, brokerConsumer, messageReference.getMessage());
+                  sessionSPI.ack(null, brokerConsumer, reference.getMessage());
                } catch (Exception e) {
                   log.debug(e.getMessage(), e);
                }
@@ -827,17 +884,84 @@ public class ProtonServerSenderContext extends ProtonInitializable implements Pr
             }
 
             connection.flush();
-         } finally {
+
             synchronized (creditsLock) {
                pending.decrementAndGet();
             }
-            if (releaseRequired) {
-               ((NettyReadable) sendBuffer).getByteBuf().release();
+
+            finishLargeMessage();
+         } catch (Exception e) {
+            log.warn(e.getMessage(), e);
+            brokerConsumer.errorProcessing(e, reference);
+         }
+      }
+   }
+
+   private void finishLargeMessage() {
+      pendingLargeMessage = null;
+      hasLarge = false;
+      brokerConsumer.promptDelivery();
+   }
+
+   private void deliverLarge(MessageReference messageReference, AMQPLargeMessage message) {
+
+      // we only need a tag if we are going to settle later
+      byte[] tag = preSettle ? new byte[0] : protonSession.getTag();
+
+      final Delivery delivery;
+      delivery = sender.delivery(tag, 0, tag.length);
+      delivery.setMessageFormat((int) message.getMessageFormat());
+      delivery.setContext(messageReference);
+
+      pendingLargeMessage = new LargeMessageDeliveryContext(messageReference, message, delivery);
+      pendingLargeMessage.deliver();
+
+   }
+
+   private void deliverStandard(MessageReference messageReference, AMQPMessage message) {
+      // Let the Message decide how to present the message bytes
+      ReadableBuffer sendBuffer = message.getSendBuffer(messageReference.getDeliveryCount());
+      // we only need a tag if we are going to settle later
+      byte[] tag = preSettle ? new byte[0] : protonSession.getTag();
+
+      boolean releaseRequired = sendBuffer instanceof NettyReadable;
+      final Delivery delivery;
+      delivery = sender.delivery(tag, 0, tag.length);
+      delivery.setMessageFormat((int) message.getMessageFormat());
+      delivery.setContext(messageReference);
+
+      try {
+
+         if (releaseRequired) {
+            sender.send(sendBuffer);
+            // Above send copied, so release now if needed
+            releaseRequired = false;
+            ((NettyReadable) sendBuffer).getByteBuf().release();
+         } else {
+            // Don't have pooled content, no need to release or copy.
+            sender.sendNoCopy(sendBuffer);
+         }
+
+         if (preSettle) {
+            // Presettled means the client implicitly accepts any delivery we send it.
+            try {
+               sessionSPI.ack(null, brokerConsumer, messageReference.getMessage());
+            } catch (Exception e) {
+               log.debug(e.getMessage(), e);
             }
+            delivery.settle();
+         } else {
+            sender.advance();
+         }
+
+         connection.flush();
+      } finally {
+         synchronized (creditsLock) {
+            pending.decrementAndGet();
+         }
+         if (releaseRequired) {
+            ((NettyReadable) sendBuffer).getByteBuf().release();
          }
-      } catch (Exception e) {
-         log.warn(e.getMessage(), e);
-         brokerConsumer.errorProcessing(e, messageReference);
       }
    }
 
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/handler/ProtonHandler.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/handler/ProtonHandler.java
index 00dfd00..62667dd 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/handler/ProtonHandler.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/handler/ProtonHandler.java
@@ -88,6 +88,8 @@ public class ProtonHandler extends ProtonInitializable implements SaslListener {
 
    boolean scheduledFlush = false;
 
+   boolean flushInstantly = false;
+
    public ProtonHandler(EventLoop workerExecutor, ArtemisExecutor poolExecutor, boolean isServer) {
       this.workerExecutor = workerExecutor;
       this.poolExecutor = poolExecutor;
@@ -174,12 +176,29 @@ public class ProtonHandler extends ProtonInitializable implements SaslListener {
 
 
 
+   /** When processing large messages, we require to flush bytes every processing */
+   public void instantFlush() {
+      this.flushInstantly = true;
+      // This will perform event handling, and at some point the flushBytes will be called
+      this.flush();
+   }
+
    public void flushBytes() {
       requireHandler();
 
-      if (!scheduledFlush) {
-         scheduledFlush = true;
-         workerExecutor.execute(this::actualFlush);
+      if (flushInstantly) {
+         flushInstantly = false;
+         scheduledFlush = false;
+         actualFlush();
+      } else {
+         // Under regular circunstances, it would be too costly to flush every time,
+         // so we flush only once at the end of processing.
+
+         // this decision was made after extensive performance testing.
+         if (!scheduledFlush) {
+            scheduledFlush = true;
+            workerExecutor.execute(this::actualFlush);
+         }
       }
    }
 
diff --git a/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPMessageTest.java b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPMessageTest.java
index 0f68edf..8822481 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPMessageTest.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPMessageTest.java
@@ -104,13 +104,13 @@ public class AMQPMessageTest {
    @Test
    public void testCreateMessageFromEncodedByteArrayData() {
       // Constructor 1
-      AMQPMessage decoded = new AMQPMessage(0, encodedProtonMessage, null);
+      AMQPStandardMessage decoded = new AMQPStandardMessage(0, encodedProtonMessage, null);
 
       assertTrue(decoded.isDurable());
       assertEquals(TEST_TO_ADDRESS, decoded.getAddress());
 
       // Constructor 2
-      decoded = new AMQPMessage(0, encodedProtonMessage, null, null);
+      decoded = new AMQPStandardMessage(0, encodedProtonMessage, null, null);
 
       assertTrue(decoded.isDurable());
       assertEquals(TEST_TO_ADDRESS, decoded.getAddress());
@@ -118,7 +118,7 @@ public class AMQPMessageTest {
 
    @Test
    public void testCreateMessageFromEncodedReadableBuffer() {
-      AMQPMessage decoded = new AMQPMessage(0, ReadableBuffer.ByteBufferReader.wrap(encodedProtonMessage), null, null);
+      AMQPStandardMessage decoded = new AMQPStandardMessage(0, ReadableBuffer.ByteBufferReader.wrap(encodedProtonMessage), null, null);
 
       assertEquals(true, decoded.getHeader().getDurable());
       assertEquals(TEST_TO_ADDRESS, decoded.getAddress());
@@ -126,7 +126,7 @@ public class AMQPMessageTest {
 
    @Test
    public void testCreateMessageFromEncodedByteArrayDataWithExtraProperties() {
-      AMQPMessage decoded = new AMQPMessage(0, encodedProtonMessage, new TypedProperties(), null);
+      AMQPStandardMessage decoded = new AMQPStandardMessage(0, encodedProtonMessage, new TypedProperties(), null);
 
       assertEquals(true, decoded.getHeader().getDurable());
       assertEquals(TEST_TO_ADDRESS, decoded.getAddress());
@@ -138,7 +138,7 @@ public class AMQPMessageTest {
       MessageImpl protonMessage = createProtonMessage();
       ActiveMQBuffer encoded = encodeMessageAsPersistedBuffer(protonMessage);
 
-      AMQPMessage message = new AMQPMessage(0);
+      AMQPStandardMessage message = new AMQPStandardMessage(0);
       try {
          message.getProtonMessage();
          fail("Should throw NPE due to not being initialized yet");
@@ -165,7 +165,7 @@ public class AMQPMessageTest {
       annotations.getValue().put(AMQPMessageSupport.SCHEDULED_DELIVERY_TIME, scheduledTime);
       ActiveMQBuffer encoded = encodeMessageAsPersistedBuffer(protonMessage);
 
-      AMQPMessage message = new AMQPMessage(0);
+      AMQPMessage message = new AMQPStandardMessage(0);
       try {
          message.getProtonMessage();
          fail("Should throw NPE due to not being initialized yet");
@@ -188,7 +188,7 @@ public class AMQPMessageTest {
       annotations.getValue().put(AMQPMessageSupport.SCHEDULED_DELIVERY_DELAY, scheduledDelay);
       ActiveMQBuffer encoded = encodeMessageAsPersistedBuffer(protonMessage);
 
-      AMQPMessage message = new AMQPMessage(0);
+      AMQPMessage message = new AMQPStandardMessage(0);
       try {
          message.getProtonMessage();
          fail("Should throw NPE due to not being initialized yet");
@@ -208,7 +208,7 @@ public class AMQPMessageTest {
       MessageImpl protonMessage = createProtonMessage();
       ActiveMQBuffer encoded = encodeMessageAsPersistedBuffer(protonMessage);
 
-      AMQPMessage message = new AMQPMessage(0);
+      AMQPMessage message = new AMQPStandardMessage(0);
       try {
          message.getProtonMessage();
          fail("Should throw NPE due to not being initialized yet");
@@ -227,7 +227,7 @@ public class AMQPMessageTest {
 
    @Test
    public void testGetMemoryEstimate() {
-      AMQPMessage decoded = new AMQPMessage(0, encodedProtonMessage, new TypedProperties(), null);
+      AMQPStandardMessage decoded = new AMQPStandardMessage(0, encodedProtonMessage, new TypedProperties(), null);
 
       int estimate = decoded.getMemoryEstimate();
       assertTrue(encodedProtonMessage.length < decoded.getMemoryEstimate());
@@ -257,7 +257,7 @@ public class AMQPMessageTest {
 
 
       for (int testTry = 0; testTry < 100; testTry++) {
-         AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+         AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
          Thread[] threads = new Thread[100];
 
          CountDownLatch latchAlign = new CountDownLatch(threads.length);
@@ -304,7 +304,7 @@ public class AMQPMessageTest {
    @Test
    public void testGetConnectionID() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(null, decoded.getConnectionID());
    }
@@ -312,7 +312,7 @@ public class AMQPMessageTest {
    @Test
    public void testSetConnectionID() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       final String ID = UUID.randomUUID().toString();
 
@@ -324,7 +324,7 @@ public class AMQPMessageTest {
    @Test
    public void testGetConnectionIDFromProperties() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       final String ID = UUID.randomUUID().toString();
 
@@ -339,7 +339,7 @@ public class AMQPMessageTest {
    @Test
    public void testGetLastValueFromMessageWithNone() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertNull(decoded.getLastValueProperty());
    }
@@ -349,7 +349,7 @@ public class AMQPMessageTest {
       SimpleString lastValue = new SimpleString("last-address");
 
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertNull(decoded.getLastValueProperty());
       decoded.setLastValueProperty(lastValue);
@@ -361,7 +361,7 @@ public class AMQPMessageTest {
    @Test
    public void getUserIDWhenNoPropertiesExists() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertNull(decoded.getUserID());
       decoded.setUserID(UUID.randomUUID().toString());
@@ -373,7 +373,7 @@ public class AMQPMessageTest {
       final String ID = UUID.randomUUID().toString();
 
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertNull(decoded.getUserID());
       assertNull(decoded.getProperties());
@@ -391,7 +391,7 @@ public class AMQPMessageTest {
 
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
       protonMessage.setProperties(new Properties());
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertNull(decoded.getUserID());
       assertNotNull(decoded.getProperties());
@@ -412,7 +412,7 @@ public class AMQPMessageTest {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
       protonMessage.setProperties(new Properties());
       protonMessage.setMessageId(ID);
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertNotNull(decoded.getUserID());
       assertNotNull(decoded.getProperties());
@@ -433,7 +433,7 @@ public class AMQPMessageTest {
    @Test
    public void testGetDuplicateProperty() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(null, decoded.getDuplicateProperty());
    }
@@ -448,7 +448,7 @@ public class AMQPMessageTest {
       protonMessage.setHeader(new Header());
       protonMessage.setAddress(ADDRESS);
 
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(ADDRESS, decoded.getAddress());
    }
@@ -461,7 +461,7 @@ public class AMQPMessageTest {
       protonMessage.setHeader(new Header());
       protonMessage.setAddress(ADDRESS);
 
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(ADDRESS, decoded.getAddressSimpleString().toString());
    }
@@ -469,7 +469,7 @@ public class AMQPMessageTest {
    @Test
    public void testGetAddressFromMessageWithNoValueSet() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertNull(decoded.getAddress());
       assertNull(decoded.getAddressSimpleString());
@@ -483,7 +483,7 @@ public class AMQPMessageTest {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
       protonMessage.setAddress(ADDRESS);
 
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(ADDRESS, decoded.getAddress());
       decoded.setAddress(NEW_ADDRESS);
@@ -498,7 +498,7 @@ public class AMQPMessageTest {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
       protonMessage.setAddress(ADDRESS);
 
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(ADDRESS, decoded.getAddress());
       decoded.setAddress(NEW_ADDRESS);
@@ -516,7 +516,7 @@ public class AMQPMessageTest {
       protonMessage.setHeader(new Header());
       protonMessage.setDurable(true);
 
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
       assertTrue(decoded.isDurable());
    }
 
@@ -526,21 +526,21 @@ public class AMQPMessageTest {
       protonMessage.setHeader(new Header());
       protonMessage.setDurable(false);
 
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
       assertFalse(decoded.isDurable());
    }
 
    @Test
    public void testIsDurableFromMessageWithNoValueSet() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
       assertFalse(decoded.isDurable());
    }
 
    @Test
    public void testIsDuranleReturnsTrueOnceUpdated() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
       assertFalse(decoded.isDurable());
       decoded.setDurable(true);
       assertTrue(decoded.isDurable());
@@ -550,7 +550,7 @@ public class AMQPMessageTest {
    public void testNonDurableMessageReencodedToDurable() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
       protonMessage.setHeader(new Header());
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
       assertFalse(decoded.isDurable());
 
       // Underlying message data not updated yet
@@ -567,7 +567,7 @@ public class AMQPMessageTest {
    @Test
    public void testMessageWithNoHeaderGetsOneWhenDurableSetAndReencoded() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
       assertFalse(decoded.isDurable());
 
       // Underlying message data not updated yet
@@ -588,7 +588,7 @@ public class AMQPMessageTest {
    @Test
    public void testGetRoutingTypeFromMessageWithoutIt() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertNull(decoded.getRoutingType());
    }
@@ -598,7 +598,7 @@ public class AMQPMessageTest {
       RoutingType type = RoutingType.ANYCAST;
 
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertNull(decoded.getRoutingType());
       decoded.setRoutingType(type);
@@ -610,7 +610,7 @@ public class AMQPMessageTest {
       RoutingType type = RoutingType.ANYCAST;
 
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertNull(decoded.getRoutingType());
       decoded.setRoutingType(type);
@@ -625,7 +625,7 @@ public class AMQPMessageTest {
       MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
       annotations.getValue().put(AMQPMessageSupport.ROUTING_TYPE, RoutingType.ANYCAST.getType());
       protonMessage.setMessageAnnotations(annotations);
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(RoutingType.ANYCAST, decoded.getRoutingType());
       decoded.setRoutingType(null);
@@ -641,7 +641,7 @@ public class AMQPMessageTest {
       MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
       annotations.getValue().put(AMQPMessageSupport.ROUTING_TYPE, RoutingType.ANYCAST.getType());
       protonMessage.setMessageAnnotations(annotations);
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(RoutingType.ANYCAST, decoded.getRoutingType());
    }
@@ -652,7 +652,7 @@ public class AMQPMessageTest {
       MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
       annotations.getValue().put(AMQPMessageSupport.ROUTING_TYPE, RoutingType.MULTICAST.getType());
       protonMessage.setMessageAnnotations(annotations);
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(RoutingType.MULTICAST, decoded.getRoutingType());
    }
@@ -663,7 +663,7 @@ public class AMQPMessageTest {
       MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
       annotations.getValue().put(AMQPMessageSupport.JMS_DEST_TYPE_MSG_ANNOTATION, AMQPMessageSupport.QUEUE_TYPE);
       protonMessage.setMessageAnnotations(annotations);
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(RoutingType.ANYCAST, decoded.getRoutingType());
    }
@@ -674,7 +674,7 @@ public class AMQPMessageTest {
       MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
       annotations.getValue().put(AMQPMessageSupport.JMS_DEST_TYPE_MSG_ANNOTATION, AMQPMessageSupport.TEMP_QUEUE_TYPE);
       protonMessage.setMessageAnnotations(annotations);
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(RoutingType.ANYCAST, decoded.getRoutingType());
    }
@@ -685,7 +685,7 @@ public class AMQPMessageTest {
       MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
       annotations.getValue().put(AMQPMessageSupport.JMS_DEST_TYPE_MSG_ANNOTATION, AMQPMessageSupport.TOPIC_TYPE);
       protonMessage.setMessageAnnotations(annotations);
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(RoutingType.MULTICAST, decoded.getRoutingType());
    }
@@ -696,7 +696,7 @@ public class AMQPMessageTest {
       MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
       annotations.getValue().put(AMQPMessageSupport.JMS_DEST_TYPE_MSG_ANNOTATION, AMQPMessageSupport.TEMP_TOPIC_TYPE);
       protonMessage.setMessageAnnotations(annotations);
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(RoutingType.MULTICAST, decoded.getRoutingType());
    }
@@ -707,7 +707,7 @@ public class AMQPMessageTest {
       MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
       annotations.getValue().put(AMQPMessageSupport.JMS_DEST_TYPE_MSG_ANNOTATION, (byte) 32);
       protonMessage.setMessageAnnotations(annotations);
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertNull(decoded.getRoutingType());
    }
@@ -721,7 +721,7 @@ public class AMQPMessageTest {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
       protonMessage.setHeader(new Header());
       protonMessage.setGroupId(GROUP_ID);
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(GROUP_ID, decoded.getGroupID().toString());
    }
@@ -730,14 +730,14 @@ public class AMQPMessageTest {
    public void testGetGroupIDFromMessageWithNoGroupId() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
       protonMessage.setProperties(new Properties());
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
       assertNull(decoded.getGroupID());
    }
 
    @Test
    public void testGetGroupIDFromMessageWithNoProperties() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
       assertNull(decoded.getGroupID());
    }
 
@@ -750,7 +750,7 @@ public class AMQPMessageTest {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
       protonMessage.setHeader(new Header());
       protonMessage.setReplyTo(REPLY_TO);
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(REPLY_TO, decoded.getReplyTo().toString());
    }
@@ -759,14 +759,14 @@ public class AMQPMessageTest {
    public void testGetReplyToFromMessageWithNoReplyTo() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
       protonMessage.setProperties(new Properties());
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
       assertNull(decoded.getReplyTo());
    }
 
    @Test
    public void testGetReplyToFromMessageWithNoProperties() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
       assertNull(decoded.getReplyTo());
    }
 
@@ -776,7 +776,7 @@ public class AMQPMessageTest {
 
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
       protonMessage.setProperties(new Properties());
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
       assertNull(decoded.getReplyTo());
 
       decoded.setReplyTo(new SimpleString(REPLY_TO));
@@ -791,7 +791,7 @@ public class AMQPMessageTest {
       final String REPLY_TO = "address-1";
 
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
       assertNull(decoded.getReplyTo());
 
       decoded.setReplyTo(new SimpleString(REPLY_TO));
@@ -808,7 +808,7 @@ public class AMQPMessageTest {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
       protonMessage.setProperties(new Properties());
       protonMessage.setReplyTo(REPLY_TO);
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
       assertEquals(REPLY_TO, decoded.getReplyTo().toString());
 
       decoded.setReplyTo(null);
@@ -826,7 +826,7 @@ public class AMQPMessageTest {
 
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
       protonMessage.setUserId(USER_NAME.getBytes(StandardCharsets.UTF_8));
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(USER_NAME, decoded.getAMQPUserID());
    }
@@ -834,7 +834,7 @@ public class AMQPMessageTest {
    @Test
    public void testGetUserIDFromMessageWithNoProperties() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertNull(decoded.getAMQPUserID());
    }
@@ -843,7 +843,7 @@ public class AMQPMessageTest {
    public void testGetUserIDFromMessageWithNoUserID() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
       protonMessage.setProperties(new Properties());
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertNull(decoded.getAMQPUserID());
    }
@@ -857,7 +857,7 @@ public class AMQPMessageTest {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
       protonMessage.setHeader(new Header());
       protonMessage.setPriority(PRIORITY);
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(PRIORITY, decoded.getPriority());
    }
@@ -865,27 +865,27 @@ public class AMQPMessageTest {
    @Test
    public void testGetPriorityFromMessageWithNoHeader() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
-      assertEquals(AMQPMessage.DEFAULT_MESSAGE_PRIORITY, decoded.getPriority());
+      assertEquals(AMQPStandardMessage.DEFAULT_MESSAGE_PRIORITY, decoded.getPriority());
    }
 
    @Test
    public void testGetPriorityFromMessageWithNoPrioritySet() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
       protonMessage.setHeader(new Header());
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
-      assertEquals(AMQPMessage.DEFAULT_MESSAGE_PRIORITY, decoded.getPriority());
+      assertEquals(AMQPStandardMessage.DEFAULT_MESSAGE_PRIORITY, decoded.getPriority());
    }
 
    @Test
    public void testSetPriorityOnMessageWithHeader() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
       protonMessage.setHeader(new Header());
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
-      assertEquals(AMQPMessage.DEFAULT_MESSAGE_PRIORITY, decoded.getPriority());
+      assertEquals(AMQPStandardMessage.DEFAULT_MESSAGE_PRIORITY, decoded.getPriority());
 
       decoded.setPriority((byte) 9);
       decoded.reencode();
@@ -897,9 +897,9 @@ public class AMQPMessageTest {
    @Test
    public void testSetPriorityOnMessageWithoutHeader() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
-      assertEquals(AMQPMessage.DEFAULT_MESSAGE_PRIORITY, decoded.getPriority());
+      assertEquals(AMQPStandardMessage.DEFAULT_MESSAGE_PRIORITY, decoded.getPriority());
 
       decoded.setPriority((byte) 9);
       decoded.reencode();
@@ -913,7 +913,7 @@ public class AMQPMessageTest {
    @Test
    public void testGetExpirationFromMessageWithNoHeader() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(0, decoded.getExpiration());
    }
@@ -922,7 +922,7 @@ public class AMQPMessageTest {
    public void testGetExpirationFromMessageWithNoTTLInHeader() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
       protonMessage.setHeader(new Header());
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(0, decoded.getExpiration());
    }
@@ -932,7 +932,7 @@ public class AMQPMessageTest {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
       protonMessage.setHeader(new Header());
       protonMessage.setProperties(new Properties());
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(0, decoded.getExpiration());
    }
@@ -944,7 +944,7 @@ public class AMQPMessageTest {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
       protonMessage.setHeader(new Header());
       protonMessage.setTtl(ttl);
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertTrue(decoded.getExpiration() > System.currentTimeMillis());
    }
@@ -957,7 +957,7 @@ public class AMQPMessageTest {
       Properties properties = new Properties();
       properties.setAbsoluteExpiryTime(expirationTime);
       protonMessage.setProperties(properties);
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(expirationTime.getTime(), decoded.getExpiration());
    }
@@ -970,7 +970,7 @@ public class AMQPMessageTest {
       Properties properties = new Properties();
       properties.setAbsoluteExpiryTime(expirationTime);
       protonMessage.setProperties(properties);
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(0, decoded.getExpiration());
    }
@@ -986,7 +986,7 @@ public class AMQPMessageTest {
       Properties properties = new Properties();
       properties.setAbsoluteExpiryTime(expirationTime);
       protonMessage.setProperties(properties);
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(expirationTime.getTime(), decoded.getExpiration());
    }
@@ -996,7 +996,7 @@ public class AMQPMessageTest {
       final Date expirationTime = new Date(System.currentTimeMillis());
 
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(0, decoded.getExpiration());
       decoded.setExpiration(expirationTime.getTime());
@@ -1011,7 +1011,7 @@ public class AMQPMessageTest {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
       protonMessage.setProperties(new Properties());
       protonMessage.setExpiryTime(originalExpirationTime.getTime());
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(originalExpirationTime.getTime(), decoded.getExpiration());
       decoded.setExpiration(expirationTime.getTime());
@@ -1026,7 +1026,7 @@ public class AMQPMessageTest {
       final Date expirationTime = new Date(System.currentTimeMillis());
 
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(0, decoded.getExpiration());
       decoded.setExpiration(expirationTime.getTime());
@@ -1043,7 +1043,7 @@ public class AMQPMessageTest {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
       protonMessage.setProperties(new Properties());
       protonMessage.setExpiryTime(expirationTime.getTime());
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(expirationTime.getTime(), decoded.getExpiration());
       decoded.setExpiration(-1);
@@ -1057,7 +1057,7 @@ public class AMQPMessageTest {
    @Test
    public void testSetExpirationToClearDoesNotAddPropertiesWhenNonePresent() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(0, decoded.getExpiration());
       decoded.setExpiration(-1);
@@ -1075,7 +1075,7 @@ public class AMQPMessageTest {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
       protonMessage.setHeader(new Header());
       protonMessage.setTtl(ttl);
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertTrue(decoded.getExpiration() > System.currentTimeMillis());
 
@@ -1097,7 +1097,7 @@ public class AMQPMessageTest {
       Properties properties = new Properties();
       properties.setCreationTime(timestamp);
       protonMessage.setProperties(properties);
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(timestamp.getTime(), decoded.getTimestamp());
    }
@@ -1106,7 +1106,7 @@ public class AMQPMessageTest {
    public void testGetTimestampFromMessageWithNoCreateTimeSet() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
       protonMessage.setHeader(new Header());
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(0L, decoded.getTimestamp());
    }
@@ -1114,7 +1114,7 @@ public class AMQPMessageTest {
    @Test
    public void testGetTimestampFromMessageWithNoHeader() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(0L, decoded.getTimestamp());
    }
@@ -1123,7 +1123,7 @@ public class AMQPMessageTest {
    public void testSetTimestampOnMessage() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
       protonMessage.setProperties(new Properties());
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(0L, decoded.getTimestamp());
 
@@ -1139,7 +1139,7 @@ public class AMQPMessageTest {
    @Test
    public void testSetTimestampOnMessageWithNoPropertiesSection() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(0L, decoded.getTimestamp());
 
@@ -1163,7 +1163,7 @@ public class AMQPMessageTest {
       MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
       annotations.getValue().put(AMQPMessageSupport.SCHEDULED_DELIVERY_TIME, scheduledTime);
       protonMessage.setMessageAnnotations(annotations);
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(scheduledTime, decoded.getScheduledDeliveryTime().longValue());
    }
@@ -1178,7 +1178,7 @@ public class AMQPMessageTest {
       annotations.getValue().put(AMQPMessageSupport.SCHEDULED_DELIVERY_DELAY, scheduledDelay);
       annotations.getValue().put(AMQPMessageSupport.SCHEDULED_DELIVERY_TIME, scheduledTime);
       protonMessage.setMessageAnnotations(annotations);
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(scheduledTime, decoded.getScheduledDeliveryTime().longValue());
    }
@@ -1191,7 +1191,7 @@ public class AMQPMessageTest {
       MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
       annotations.getValue().put(AMQPMessageSupport.SCHEDULED_DELIVERY_DELAY, scheduledDelay);
       protonMessage.setMessageAnnotations(annotations);
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertTrue(decoded.getScheduledDeliveryTime().longValue() > System.currentTimeMillis());
    }
@@ -1199,7 +1199,7 @@ public class AMQPMessageTest {
    @Test
    public void testGetScheduledDeliveryTimeWhenMessageHasNoSetValue() {
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
       assertEquals(0, decoded.getScheduledDeliveryTime().longValue());
    }
 
@@ -1208,7 +1208,7 @@ public class AMQPMessageTest {
       final long scheduledTime = System.currentTimeMillis() + 5000;
 
       MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(0, decoded.getScheduledDeliveryTime().longValue());
       decoded.setScheduledDeliveryTime(scheduledTime);
@@ -1228,7 +1228,7 @@ public class AMQPMessageTest {
       MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
       annotations.getValue().put(AMQPMessageSupport.SCHEDULED_DELIVERY_TIME, scheduledTime);
       protonMessage.setMessageAnnotations(annotations);
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(scheduledTime, decoded.getScheduledDeliveryTime().longValue());
 
@@ -1247,7 +1247,7 @@ public class AMQPMessageTest {
       MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
       annotations.getValue().put(AMQPMessageSupport.SCHEDULED_DELIVERY_DELAY, scheduledDelay);
       protonMessage.setMessageAnnotations(annotations);
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertTrue(decoded.getScheduledDeliveryTime().longValue() > System.currentTimeMillis());
 
@@ -1267,7 +1267,7 @@ public class AMQPMessageTest {
       annotations.getValue().put(AMQPMessageSupport.SCHEDULED_DELIVERY_DELAY, scheduledDelay);
       annotations.getValue().put(AMQPMessageSupport.SCHEDULED_DELIVERY_TIME, scheduledTime);
       protonMessage.setMessageAnnotations(annotations);
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertEquals(scheduledTime, decoded.getScheduledDeliveryTime().longValue());
 
@@ -1281,7 +1281,7 @@ public class AMQPMessageTest {
 
    @Test
    public void testGetAnnotation() {
-      AMQPMessage message = new AMQPMessage(0, encodedProtonMessage, null);
+      AMQPStandardMessage message = new AMQPStandardMessage(0, encodedProtonMessage, null);
 
       Object result = message.getAnnotation(new SimpleString(TEST_MESSAGE_ANNOTATION_KEY));
       String stringResult = message.getAnnotationString(new SimpleString(TEST_MESSAGE_ANNOTATION_KEY));
@@ -1291,7 +1291,7 @@ public class AMQPMessageTest {
 
    @Test
    public void testRemoveAnnotation() {
-      AMQPMessage message = new AMQPMessage(0, encodedProtonMessage, null);
+      AMQPStandardMessage message = new AMQPStandardMessage(0, encodedProtonMessage, null);
 
       assertNotNull(message.getAnnotation(new SimpleString(TEST_MESSAGE_ANNOTATION_KEY)));
       message.removeAnnotation(new SimpleString(TEST_MESSAGE_ANNOTATION_KEY));
@@ -1304,7 +1304,7 @@ public class AMQPMessageTest {
 
    @Test
    public void testSetAnnotation() {
-      AMQPMessage message = new AMQPMessage(0, encodedProtonMessage, null);
+      AMQPStandardMessage message = new AMQPStandardMessage(0, encodedProtonMessage, null);
 
       final SimpleString newAnnotation = new SimpleString("testSetAnnotation");
       final String newValue = "newValue";
@@ -1322,7 +1322,7 @@ public class AMQPMessageTest {
    @Test
    public void testGetProtonMessage() {
       MessageImpl protonMessage = createProtonMessage();
-      AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);
+      AMQPStandardMessage message = new AMQPStandardMessage(0, encodeMessage(protonMessage), null, null);
 
       assertProtonMessageEquals(protonMessage, message.getProtonMessage());
 
@@ -1335,7 +1335,7 @@ public class AMQPMessageTest {
    @Test
    public void testGetHeader() {
       MessageImpl protonMessage = createProtonMessage();
-      AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);
+      AMQPStandardMessage message = new AMQPStandardMessage(0, encodeMessage(protonMessage), null, null);
 
       Header decoded = message.getHeader();
       assertNotSame(decoded, protonMessage.getHeader());
@@ -1353,7 +1353,7 @@ public class AMQPMessageTest {
    @Test
    public void testGetProperties() {
       MessageImpl protonMessage = createProtonMessage();
-      AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);
+      AMQPStandardMessage message = new AMQPStandardMessage(0, encodeMessage(protonMessage), null, null);
 
       Properties decoded = message.getProperties();
       assertNotSame(decoded, protonMessage.getProperties());
@@ -1375,7 +1375,7 @@ public class AMQPMessageTest {
       deliveryAnnotations.getValue().put(Symbol.valueOf(UUID.randomUUID().toString()), "test-1");
       protonMessage.setDeliveryAnnotations(deliveryAnnotations);
 
-      AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);
+      AMQPStandardMessage message = new AMQPStandardMessage(0, encodeMessage(protonMessage), null, null);
 
       DeliveryAnnotations decoded = message.getDeliveryAnnotations();
       assertNotSame(decoded, protonMessage.getDeliveryAnnotations());
@@ -1391,7 +1391,7 @@ public class AMQPMessageTest {
    @Test
    public void testGetMessageAnnotations() {
       MessageImpl protonMessage = createProtonMessage();
-      AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);
+      AMQPStandardMessage message = new AMQPStandardMessage(0, encodeMessage(protonMessage), null, null);
 
       MessageAnnotations decoded = message.getMessageAnnotations();
       assertNotSame(decoded, protonMessage.getMessageAnnotations());
@@ -1407,7 +1407,7 @@ public class AMQPMessageTest {
    @Test
    public void testGetApplicationProperties() {
       MessageImpl protonMessage = createProtonMessage();
-      AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);
+      AMQPStandardMessage message = new AMQPStandardMessage(0, encodeMessage(protonMessage), null, null);
 
       ApplicationProperties decoded = message.getApplicationProperties();
       assertNotSame(decoded, protonMessage.getApplicationProperties());
@@ -1423,7 +1423,7 @@ public class AMQPMessageTest {
    @Test
    public void testGetBody() {
       MessageImpl protonMessage = createProtonMessage();
-      AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);
+      AMQPStandardMessage message = new AMQPStandardMessage(0, encodeMessage(protonMessage), null, null);
 
       Object body = message.getBody();
       assertTrue(body instanceof AmqpValue);
@@ -1442,7 +1442,7 @@ public class AMQPMessageTest {
       footer.getValue().put(Symbol.valueOf(UUID.randomUUID().toString()), "test-1");
       protonMessage.setFooter(footer);
 
-      AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);
+      AMQPStandardMessage message = new AMQPStandardMessage(0, encodeMessage(protonMessage), null, null);
 
       Footer decoded = message.getFooter();
       assertNotSame(decoded, protonMessage.getFooter());
@@ -1460,7 +1460,7 @@ public class AMQPMessageTest {
    @Test
    public void testApplicationPropertiesReencodeAfterUpdate() {
       MessageImpl protonMessage = createProtonMessage();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertProtonMessageEquals(protonMessage, decoded.getProtonMessage());
 
@@ -1478,7 +1478,7 @@ public class AMQPMessageTest {
       final SimpleString TEST_ANNOTATION = new SimpleString("testMessageAnnotationsReencodeAfterUpdate");
 
       MessageImpl protonMessage = createProtonMessage();
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertProtonMessageEquals(protonMessage, decoded.getProtonMessage());
 
@@ -1499,7 +1499,7 @@ public class AMQPMessageTest {
       byte[] value = RandomUtil.randomBytes();
       SimpleString name = SimpleString.toSimpleString("myProperty");
 
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       assertNull(decoded.getExtraProperties());
       assertNull(decoded.getExtraBytesProperty(name));
@@ -1518,7 +1518,7 @@ public class AMQPMessageTest {
 
       byte[] original = RandomUtil.randomBytes();
       SimpleString name = SimpleString.toSimpleString("myProperty");
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
       decoded.setAddress("someAddress");
       decoded.setMessageID(33);
       decoded.putExtraBytesProperty(name, original);
@@ -1530,7 +1530,7 @@ public class AMQPMessageTest {
       try {
          decoded.getPersister().encode(buffer, decoded);
          Assert.assertEquals(AMQPMessagePersisterV2.getInstance().getID(), buffer.readByte()); // the journal reader will read 1 byte to find the persister
-         AMQPMessage readMessage = (AMQPMessage)decoded.getPersister().decode(buffer, null);
+         AMQPStandardMessage readMessage = (AMQPStandardMessage)decoded.getPersister().decode(buffer, null, null);
          Assert.assertEquals(33, readMessage.getMessageID());
          Assert.assertEquals("someAddress", readMessage.getAddress());
          assertArrayEquals(original, readMessage.getExtraBytesProperty(name));
@@ -1540,7 +1540,7 @@ public class AMQPMessageTest {
 
       {
          ICoreMessage embeddedMessage = EmbedMessageUtil.embedAsCoreMessage(decoded);
-         AMQPMessage readMessage = (AMQPMessage) EmbedMessageUtil.extractEmbedded(embeddedMessage);
+         AMQPStandardMessage readMessage = (AMQPStandardMessage) EmbedMessageUtil.extractEmbedded(embeddedMessage, null);
          Assert.assertEquals(33, readMessage.getMessageID());
          Assert.assertEquals("someAddress", readMessage.getAddress());
          assertArrayEquals(original, readMessage.getExtraBytesProperty(name));
@@ -1578,9 +1578,9 @@ public class AMQPMessageTest {
 
       ReadableBuffer readable = new NettyReadable(encodedBytes);
 
-      AMQPMessage message = null;
+      AMQPStandardMessage message = null;
       try {
-         message = new AMQPMessage(0, readable, null, null);
+         message = new AMQPStandardMessage(0, readable, null, null);
       } catch (Exception decodeError) {
          fail("Should not have encountered an exception on partial decode: " + decodeError.getMessage());
       }
@@ -1619,9 +1619,9 @@ public class AMQPMessageTest {
 
       ReadableBuffer readable = new NettyReadable(encodedBytes);
 
-      AMQPMessage message = null;
+      AMQPStandardMessage message = null;
       try {
-         message = new AMQPMessage(0, readable, null, null);
+         message = new AMQPStandardMessage(0, readable, null, null);
       } catch (Exception decodeError) {
          fail("Should not have encountered an exception on partial decode: " + decodeError.getMessage());
       }
@@ -1659,9 +1659,9 @@ public class AMQPMessageTest {
 
       ReadableBuffer readable = new NettyReadable(encodedBytes);
 
-      AMQPMessage message = null;
+      AMQPStandardMessage message = null;
       try {
-         message = new AMQPMessage(0, readable, null, null);
+         message = new AMQPStandardMessage(0, readable, null, null);
       } catch (Exception decodeError) {
          fail("Should not have encountered an exception on partial decode: " + decodeError.getMessage());
       }
@@ -1681,9 +1681,9 @@ public class AMQPMessageTest {
 
    @Test
    public void testCopyMessage() {
-      AMQPMessage message = new AMQPMessage(0, encodedProtonMessage, null, null);
+      AMQPStandardMessage message = new AMQPStandardMessage(0, encodedProtonMessage, null, null);
       message.setMessageID(127);
-      AMQPMessage copy = (AMQPMessage) message.copy();
+      AMQPStandardMessage copy = (AMQPStandardMessage) message.copy();
 
       assertEquals(message.getMessageID(), copy.getMessageID());
       assertProtonMessageEquals(message.getProtonMessage(), copy.getProtonMessage());
@@ -1691,9 +1691,9 @@ public class AMQPMessageTest {
 
    @Test
    public void testCopyMessageWithNewArtemisMessageID() {
-      AMQPMessage message = new AMQPMessage(0, encodedProtonMessage, null, null);
+      AMQPStandardMessage message = new AMQPStandardMessage(0, encodedProtonMessage, null, null);
       message.setMessageID(127);
-      AMQPMessage copy = (AMQPMessage) message.copy(255);
+      AMQPStandardMessage copy = (AMQPStandardMessage) message.copy(255);
 
       assertNotEquals(message.getMessageID(), copy.getMessageID());
       assertProtonMessageEquals(message.getProtonMessage(), copy.getProtonMessage());
@@ -1706,9 +1706,9 @@ public class AMQPMessageTest {
       deliveryAnnotations.getValue().put(Symbol.valueOf("testCopyMessageRemovesMessageAnnotations"), "1");
       protonMessage.setDeliveryAnnotations(deliveryAnnotations);
 
-      AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);
+      AMQPStandardMessage message = new AMQPStandardMessage(0, encodeMessage(protonMessage), null, null);
       message.setMessageID(127);
-      AMQPMessage copy = (AMQPMessage) message.copy();
+      AMQPStandardMessage copy = (AMQPStandardMessage) message.copy();
 
       assertEquals(message.getMessageID(), copy.getMessageID());
       assertProtonMessageEquals(message.getProtonMessage(), copy.getProtonMessage());
@@ -1717,14 +1717,14 @@ public class AMQPMessageTest {
 
    @Test
    public void testDecodeCopyUpdateReencodeAndThenDecodeAgain() {
-      AMQPMessage message = new AMQPMessage(0, encodedProtonMessage, null, null);
+      AMQPStandardMessage message = new AMQPStandardMessage(0, encodedProtonMessage, null, null);
 
       // Sanity checks
       assertTrue(message.isDurable());
       assertEquals(TEST_STRING_BODY, ((AmqpValue) message.getBody()).getValue());
 
       // Copy the message
-      message = (AMQPMessage) message.copy();
+      message = (AMQPStandardMessage) message.copy();
 
       // Sanity checks
       assertTrue(message.isDurable());
@@ -1748,13 +1748,13 @@ public class AMQPMessageTest {
    @Test
    public void testSendBuffer() {
       ByteBuf buffer = Unpooled.buffer(255);
-      AMQPMessage message = new AMQPMessage(0, encodedProtonMessage, null, null);
+      AMQPStandardMessage message = new AMQPStandardMessage(0, encodedProtonMessage, null, null);
 
       message.sendBuffer(buffer, 1);
 
       assertNotNull(buffer);
 
-      AMQPMessage copy = new AMQPMessage(0, new NettyReadable(buffer), null, null);
+      AMQPStandardMessage copy = new AMQPStandardMessage(0, new NettyReadable(buffer), null, null);
 
       assertProtonMessageEquals(message.getProtonMessage(), copy.getProtonMessage());
    }
@@ -1763,7 +1763,7 @@ public class AMQPMessageTest {
 
    @Test
    public void testGetSendBuffer() {
-      AMQPMessage message = new AMQPMessage(0, encodedProtonMessage, null, null);
+      AMQPStandardMessage message = new AMQPStandardMessage(0, encodedProtonMessage, null, null);
 
       ReadableBuffer buffer = message.getSendBuffer(1);
       assertNotNull(buffer);
@@ -1771,20 +1771,20 @@ public class AMQPMessageTest {
 
       assertTrue(Arrays.equals(encodedProtonMessage, buffer.array()));
 
-      AMQPMessage copy = new AMQPMessage(0, buffer, null, null);
+      AMQPStandardMessage copy = new AMQPStandardMessage(0, buffer, null, null);
 
       assertProtonMessageEquals(message.getProtonMessage(), copy.getProtonMessage());
    }
 
    @Test
    public void testGetSendBufferAddsDeliveryCountOnlyToSendMessage() {
-      AMQPMessage message = new AMQPMessage(0, encodedProtonMessage, null, null);
+      AMQPStandardMessage message = new AMQPStandardMessage(0, encodedProtonMessage, null, null);
 
       ReadableBuffer buffer = message.getSendBuffer(7);
       assertNotNull(buffer);
       message.reencode(); // Ensures Header is current if accidentally updated
 
-      AMQPMessage copy = new AMQPMessage(0, buffer, null, null);
+      AMQPStandardMessage copy = new AMQPStandardMessage(0, buffer, null, null);
 
       MessageImpl originalsProtonMessage = message.getProtonMessage();
       MessageImpl copyProtonMessage = copy.getProtonMessage();
@@ -1797,13 +1797,13 @@ public class AMQPMessageTest {
    @Test
    public void testGetSendBufferAddsDeliveryCountOnlyToSendMessageOriginalHadNoHeader() {
       MessageImpl protonMessage = (MessageImpl) Proton.message();
-      AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);
+      AMQPStandardMessage message = new AMQPStandardMessage(0, encodeMessage(protonMessage), null, null);
 
       ReadableBuffer buffer = message.getSendBuffer(7);
       assertNotNull(buffer);
       message.reencode(); // Ensures Header is current if accidentally updated
 
-      AMQPMessage copy = new AMQPMessage(0, buffer, null, null);
+      AMQPStandardMessage copy = new AMQPStandardMessage(0, buffer, null, null);
 
       MessageImpl originalsProtonMessage = message.getProtonMessage();
       MessageImpl copyProtonMessage = copy.getProtonMessage();
@@ -1819,12 +1819,12 @@ public class AMQPMessageTest {
       DeliveryAnnotations deliveryAnnotations = new DeliveryAnnotations(new HashMap<>());
       deliveryAnnotations.getValue().put(Symbol.valueOf("testGetSendBufferRemoveDeliveryAnnotations"), "X");
       protonMessage.setDeliveryAnnotations(deliveryAnnotations);
-      AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);
+      AMQPStandardMessage message = new AMQPStandardMessage(0, encodeMessage(protonMessage), null, null);
 
       ReadableBuffer buffer = message.getSendBuffer(1);
       assertNotNull(buffer);
 
-      AMQPMessage copy = new AMQPMessage(0, buffer, null, null);
+      AMQPStandardMessage copy = new AMQPStandardMessage(0, buffer, null, null);
 
       MessageImpl copyProtonMessage = copy.getProtonMessage();
       assertProtonMessageNotEquals(message.getProtonMessage(), copyProtonMessage);
@@ -1837,13 +1837,13 @@ public class AMQPMessageTest {
       DeliveryAnnotations deliveryAnnotations = new DeliveryAnnotations(new HashMap<>());
       deliveryAnnotations.getValue().put(Symbol.valueOf("testGetSendBufferRemoveDeliveryAnnotations"), "X");
       protonMessage.setDeliveryAnnotations(deliveryAnnotations);
-      AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);
+      AMQPStandardMessage message = new AMQPStandardMessage(0, encodeMessage(protonMessage), null, null);
 
       ReadableBuffer buffer = message.getSendBuffer(7);
       assertNotNull(buffer);
       message.reencode(); // Ensures Header is current if accidentally updated
 
-      AMQPMessage copy = new AMQPMessage(0, buffer, null, null);
+      AMQPStandardMessage copy = new AMQPStandardMessage(0, buffer, null, null);
 
       MessageImpl originalsProtonMessage = message.getProtonMessage();
       MessageImpl copyProtonMessage = copy.getProtonMessage();
@@ -1976,7 +1976,7 @@ public class AMQPMessageTest {
          protonMessage.setFooter(foot);
       }
 
-      AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);
+      AMQPStandardMessage message = new AMQPStandardMessage(0, encodeMessage(protonMessage), null, null);
 
       message.reencode();
 
@@ -2004,18 +2004,18 @@ public class AMQPMessageTest {
       deliveryAnnotations.getValue().put(Symbol.getSymbol(annotationKey), annotationValue);
       protonMessage.setDeliveryAnnotations(deliveryAnnotations);
 
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       ReadableBuffer sendBuffer = decoded.getSendBuffer(1);
       assertEquals(decoded.getEncodeSize(), sendBuffer.capacity());
-      AMQPMessage msgFromSendBuffer = new AMQPMessage(0, sendBuffer, null, null);
+      AMQPStandardMessage msgFromSendBuffer = new AMQPStandardMessage(0, sendBuffer, null, null);
       assertEquals("someNiceLocal", msgFromSendBuffer.getAddress());
       assertNull(msgFromSendBuffer.getDeliveryAnnotations());
 
       // again with higher deliveryCount
       ReadableBuffer sendBuffer2 = decoded.getSendBuffer(5);
       assertEquals(decoded.getEncodeSize(), sendBuffer2.capacity());
-      AMQPMessage msgFromSendBuffer2 = new AMQPMessage(0, sendBuffer2, null, null);
+      AMQPStandardMessage msgFromSendBuffer2 = new AMQPStandardMessage(0, sendBuffer2, null, null);
       assertEquals("someNiceLocal", msgFromSendBuffer2.getAddress());
       assertNull(msgFromSendBuffer2.getDeliveryAnnotations());
    }
@@ -2031,7 +2031,7 @@ public class AMQPMessageTest {
       protonMessage.setProperties(properties);
       protonMessage.setBody(new AmqpValue("Sample payload"));
 
-      AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
+      AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
 
       DeliveryAnnotations newDeliveryAnnotations = new DeliveryAnnotations(new HashMap<>());
       final String annotationKey = "annotationKey";
@@ -2041,7 +2041,7 @@ public class AMQPMessageTest {
 
       ReadableBuffer sendBuffer = decoded.getSendBuffer(1);
       assertEquals(decoded.getEncodeSize(), sendBuffer.capacity());
-      AMQPMessage msgFromSendBuffer = new AMQPMessage(0, sendBuffer, null, null);
+      AMQPStandardMessage msgFromSendBuffer = new AMQPStandardMessage(0, sendBuffer, null, null);
       assertEquals("someNiceLocal", msgFromSendBuffer.getAddress());
       assertNotNull(msgFromSendBuffer.getDeliveryAnnotations());
       assertEquals(1, msgFromSendBuffer.getDeliveryAnnotations().getValue().size());
@@ -2056,7 +2056,7 @@ public class AMQPMessageTest {
 
       ReadableBuffer sendBuffer2 = decoded.getSendBuffer(5);
       assertEquals(decoded.getEncodeSize(), sendBuffer2.capacity());
-      AMQPMessage msgFromSendBuffer2 = new AMQPMessage(0, sendBuffer2, null, null);
+      AMQPStandardMessage msgFromSendBuffer2 = new AMQPStandardMessage(0, sendBuffer2, null, null);
       assertEquals("someNiceLocal", msgFromSendBuffer2.getAddress());
       assertNotNull(msgFromSendBuffer2.getDeliveryAnnotations());
       assertEquals(1, msgFromSendBuffer2.getDeliveryAnnotations().getValue().size());
@@ -2426,13 +2426,13 @@ public class AMQPMessageTest {
       return bytes;
    }
 
-   private AMQPMessage encodeAndDecodeMessage(MessageImpl message) {
+   private AMQPStandardMessage encodeAndDecodeMessage(MessageImpl message) {
       ByteBuf nettyBuffer = Unpooled.buffer(1500);
 
       message.encode(new NettyWritable(nettyBuffer));
       byte[] bytes = new byte[nettyBuffer.writerIndex()];
       nettyBuffer.readBytes(bytes);
 
-      return new AMQPMessage(0, bytes, null);
+      return new AMQPStandardMessage(0, bytes, null);
    }
 }
diff --git a/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/TestConversions.java b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/TestConversions.java
index 472e9d9..cda1b3f 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/TestConversions.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/TestConversions.java
@@ -30,6 +30,7 @@ import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.core.buffers.impl.ResetLimitWrappedActiveMQBuffer;
 import org.apache.activemq.artemis.core.message.impl.CoreMessage;
 import org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessage;
+import org.apache.activemq.artemis.protocol.amqp.broker.AMQPStandardMessage;
 import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSBytesMessage;
 import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSMapMessage;
 import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSMessage;
@@ -168,7 +169,7 @@ public class TestConversions extends Assert {
       assertEquals(1, mapMessage.getInt("someint"));
       assertEquals("value", mapMessage.getString("somestr"));
 
-      AMQPMessage newAMQP = CoreAmqpConverter.fromCore(mapMessage.getInnerMessage());
+      AMQPMessage newAMQP = CoreAmqpConverter.fromCore(mapMessage.getInnerMessage(), null);
       assertNotNull(newAMQP.getBody());
    }
 
@@ -286,7 +287,7 @@ public class TestConversions extends Assert {
       assertTrue(mapMessage.propertyExists(JMS_AMQP_ENCODED_MESSAGE_ANNOTATION_PREFIX + annotationName));
       assertArrayEquals(encodedEmbeddedMap, (byte[]) mapMessage.getObjectProperty(JMS_AMQP_ENCODED_MESSAGE_ANNOTATION_PREFIX + annotationName));
 
-      AMQPMessage newAMQP = CoreAmqpConverter.fromCore(mapMessage.getInnerMessage());
+      AMQPMessage newAMQP = CoreAmqpConverter.fromCore(mapMessage.getInnerMessage(), null);
       assertNotNull(newAMQP.getBody());
       assertNotNull(newAMQP.getMessageAnnotations());
       assertNotNull(newAMQP.getMessageAnnotations().getValue());
@@ -338,7 +339,7 @@ public class TestConversions extends Assert {
       assertTrue(mapMessage.propertyExists(JMS_AMQP_ENCODED_FOOTER_PREFIX + footerName));
       assertArrayEquals(encodedEmbeddedMap, (byte[]) mapMessage.getObjectProperty(JMS_AMQP_ENCODED_FOOTER_PREFIX + footerName));
 
-      AMQPMessage newAMQP = CoreAmqpConverter.fromCore(mapMessage.getInnerMessage());
+      AMQPMessage newAMQP = CoreAmqpConverter.fromCore(mapMessage.getInnerMessage(), null);
       assertNotNull(newAMQP.getBody());
       assertNotNull(newAMQP.getFooter());
       assertNotNull(newAMQP.getFooter().getValue());
@@ -368,7 +369,7 @@ public class TestConversions extends Assert {
       serverMessage.setObjectProperty(JMS_AMQP_ENCODED_DELIVERY_ANNOTATION_PREFIX + annotationName, encodedEmbeddedMap);
       serverMessage.encode();
 
-      AMQPMessage newAMQP = CoreAmqpConverter.fromCore(serverMessage.getInnerMessage());
+      AMQPMessage newAMQP = CoreAmqpConverter.fromCore(serverMessage.getInnerMessage(), null);
       assertNull(newAMQP.getBody());
       assertNotNull(newAMQP.getDeliveryAnnotations());
       assertNotNull(newAMQP.getDeliveryAnnotations().getValue());
@@ -520,7 +521,7 @@ public class TestConversions extends Assert {
 
       NettyReadable readable = new NettyReadable(encoded.getByteBuf());
 
-      return new AMQPMessage(AMQPMessage.DEFAULT_MESSAGE_FORMAT, readable, null, null);
+      return new AMQPStandardMessage(AMQPMessage.DEFAULT_MESSAGE_FORMAT, readable, null, null);
    }
 
    private ServerJMSMessage createMessage() {
diff --git a/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSMappingInboundTransformerTest.java b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSMappingInboundTransformerTest.java
index d7cd714..0b8ac8d 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSMappingInboundTransformerTest.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSMappingInboundTransformerTest.java
@@ -38,6 +38,7 @@ import javax.jms.Topic;
 
 import org.apache.activemq.artemis.api.core.ICoreMessage;
 import org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessage;
+import org.apache.activemq.artemis.protocol.amqp.broker.AMQPStandardMessage;
 import org.apache.activemq.artemis.protocol.amqp.converter.AMQPMessageSupport;
 import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSBytesMessage;
 import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSMapMessage;
@@ -80,7 +81,7 @@ public class JMSMappingInboundTransformerTest {
       MessageImpl message = (MessageImpl) Message.Factory.create();
       message.setContentType(AMQPMessageSupport.OCTET_STREAM_CONTENT_TYPE);
 
-      AMQPMessage messageEncode = encodeAndCreateAMQPMessage(message);
+      AMQPStandardMessage messageEncode = encodeAndCreateAMQPMessage(message);
 
       ICoreMessage coreMessage = messageEncode.toCore();
 
@@ -135,7 +136,7 @@ public class JMSMappingInboundTransformerTest {
       message.setBody(new Data(binary));
       message.setContentType(AMQPMessageSupport.OCTET_STREAM_CONTENT_TYPE);
 
-      AMQPMessage amqp = encodeAndCreateAMQPMessage(message);
+      AMQPStandardMessage amqp = encodeAndCreateAMQPMessage(message);
       javax.jms.Message jmsMessage = ServerJMSMessage.wrapCoreMessage(amqp.toCore());
 
       assertNotNull("Message should not be null", jmsMessage);
@@ -570,12 +571,12 @@ public class JMSMappingInboundTransformerTest {
       assertTrue("Expected TextMessage", jmsMessage instanceof TextMessage);
    }
 
-   private AMQPMessage encodeAndCreateAMQPMessage(MessageImpl message) {
+   private AMQPStandardMessage encodeAndCreateAMQPMessage(MessageImpl message) {
       NettyWritable encoded = new NettyWritable(Unpooled.buffer(1024));
       message.encode(encoded);
 
       NettyReadable readable = new NettyReadable(encoded.getByteBuf());
 
-      return new AMQPMessage(AMQPMessage.DEFAULT_MESSAGE_FORMAT, readable, null, null);
+      return new AMQPStandardMessage(AMQPMessage.DEFAULT_MESSAGE_FORMAT, readable, null, null);
    }
 }
diff --git a/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSMappingOutboundTransformerTest.java b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSMappingOutboundTransformerTest.java
index 062e0dd..069996a 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSMappingOutboundTransformerTest.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSMappingOutboundTransformerTest.java
@@ -84,7 +84,7 @@ public class JMSMappingOutboundTransformerTest {
       ServerJMSMessage outbound = createMessage();
       outbound.encode();
 
-      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage());
+      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage(), null);
 
       assertNull(amqp.getBody());
    }
@@ -95,7 +95,7 @@ public class JMSMappingOutboundTransformerTest {
       outbound.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_NULL);
       outbound.encode();
 
-      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage());
+      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage(), null);
 
       assertNull(amqp.getBody());
    }
@@ -109,7 +109,7 @@ public class JMSMappingOutboundTransformerTest {
       outbound.writeBytes(expectedPayload);
       outbound.encode();
 
-      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage());
+      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage(), null);
 
       assertNotNull(amqp.getBody());
       assertTrue(amqp.getBody() instanceof Data);
@@ -128,7 +128,7 @@ public class JMSMappingOutboundTransformerTest {
       outbound.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_VALUE_BINARY);
       outbound.encode();
 
-      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage());
+      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage(), null);
 
       assertNotNull(amqp.getBody());
       assertTrue(amqp.getBody() instanceof AmqpValue);
@@ -144,7 +144,7 @@ public class JMSMappingOutboundTransformerTest {
       outbound.writeBytes(expectedPayload);
       outbound.encode();
 
-      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage());
+      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage(), null);
 
       assertNotNull(amqp.getBody());
       assertTrue(amqp.getBody() instanceof AmqpValue);
@@ -164,7 +164,7 @@ public class JMSMappingOutboundTransformerTest {
       ServerJMSMapMessage outbound = createMapMessage();
       outbound.encode();
 
-      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage());
+      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage(), null);
 
       assertNotNull(amqp.getBody());
       assertTrue(amqp.getBody() instanceof AmqpValue);
@@ -179,7 +179,7 @@ public class JMSMappingOutboundTransformerTest {
       outbound.setBytes("bytes", byteArray);
       outbound.encode();
 
-      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage());
+      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage(), null);
 
       assertNotNull(amqp.getBody());
       assertTrue(amqp.getBody() instanceof AmqpValue);
@@ -201,7 +201,7 @@ public class JMSMappingOutboundTransformerTest {
       outbound.setBoolean("property-3", true);
       outbound.encode();
 
-      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage());
+      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage(), null);
 
       assertNotNull(amqp.getBody());
       assertTrue(amqp.getBody() instanceof AmqpValue);
@@ -223,7 +223,7 @@ public class JMSMappingOutboundTransformerTest {
       outbound.writeString("test");
       outbound.encode();
 
-      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage());
+      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage(), null);
 
       assertNotNull(amqp.getBody());
       assertTrue(amqp.getBody() instanceof AmqpValue);
@@ -244,7 +244,7 @@ public class JMSMappingOutboundTransformerTest {
       outbound.writeString("test");
       outbound.encode();
 
-      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage());
+      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage(), null);
 
       assertNotNull(amqp.getBody());
       assertTrue(amqp.getBody() instanceof AmqpValue);
@@ -265,7 +265,7 @@ public class JMSMappingOutboundTransformerTest {
       outbound.writeString("test");
       outbound.encode();
 
-      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage());
+      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage(), null);
 
       assertNotNull(amqp.getBody());
       assertTrue(amqp.getBody() instanceof AmqpSequence);
@@ -284,7 +284,7 @@ public class JMSMappingOutboundTransformerTest {
       ServerJMSObjectMessage outbound = createObjectMessage();
       outbound.encode();
 
-      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage());
+      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage(), null);
 
       assertNotNull(amqp.getBody());
       assertTrue(amqp.getBody() instanceof Data);
@@ -297,7 +297,7 @@ public class JMSMappingOutboundTransformerTest {
       outbound.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_UNKNOWN);
       outbound.encode();
 
-      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage());
+      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage(), null);
 
       assertNotNull(amqp.getBody());
       assertTrue(amqp.getBody() instanceof Data);
@@ -309,7 +309,7 @@ public class JMSMappingOutboundTransformerTest {
       ServerJMSObjectMessage outbound = createObjectMessage(TEST_OBJECT_VALUE);
       outbound.encode();
 
-      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage());
+      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage(), null);
 
       assertNotNull(amqp.getBody());
       assertTrue(amqp.getBody() instanceof Data);
@@ -326,7 +326,7 @@ public class JMSMappingOutboundTransformerTest {
       outbound.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_UNKNOWN);
       outbound.encode();
 
-      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage());
+      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage(), null);
 
       assertNotNull(amqp.getBody());
       assertTrue(amqp.getBody() instanceof Data);
@@ -343,7 +343,7 @@ public class JMSMappingOutboundTransformerTest {
       outbound.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_VALUE_BINARY);
       outbound.encode();
 
-      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage());
+      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage(), null);
 
       assertNotNull(amqp.getBody());
       assertTrue(amqp.getBody() instanceof AmqpValue);
@@ -361,7 +361,7 @@ public class JMSMappingOutboundTransformerTest {
       outbound.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_VALUE_BINARY);
       outbound.encode();
 
-      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage());
+      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage(), null);
 
       assertNotNull(amqp.getBody());
       assertTrue(amqp.getBody() instanceof AmqpValue);
@@ -376,7 +376,7 @@ public class JMSMappingOutboundTransformerTest {
       ServerJMSTextMessage outbound = createTextMessage();
       outbound.encode();
 
-      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage());
+      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage(), null);
 
       assertNotNull(amqp.getBody());
       assertTrue(amqp.getBody() instanceof AmqpValue);
@@ -389,7 +389,7 @@ public class JMSMappingOutboundTransformerTest {
       ServerJMSTextMessage outbound = createTextMessage(contentString);
       outbound.encode();
 
-      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage());
+      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage(), null);
 
       assertNotNull(amqp.getBody());
       assertTrue(amqp.getBody() instanceof AmqpValue);
@@ -402,7 +402,7 @@ public class JMSMappingOutboundTransformerTest {
       ServerJMSTextMessage outbound = createTextMessage(contentString);
       outbound.encode();
 
-      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage());
+      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage(), null);
 
       assertNotNull(amqp.getBody());
       assertTrue(amqp.getBody() instanceof AmqpValue);
@@ -415,7 +415,7 @@ public class JMSMappingOutboundTransformerTest {
       ServerJMSTextMessage outbound = createTextMessage(contentString);
       outbound.encode();
 
-      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage());
+      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage(), null);
 
       assertNotNull(amqp.getBody());
       assertTrue(amqp.getBody() instanceof AmqpValue);
@@ -432,7 +432,7 @@ public class JMSMappingOutboundTransformerTest {
       outbound.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_DATA);
       outbound.encode();
 
-      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage());
+      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage(), null);
 
       assertNotNull(amqp.getBody());
       assertTrue(amqp.getBody() instanceof Data);
@@ -450,7 +450,7 @@ public class JMSMappingOutboundTransformerTest {
       outbound.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_DATA);
       outbound.encode();
 
-      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage());
+      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(outbound.getInnerMessage(), null);
 
       assertNotNull(amqp.getBody());
       assertTrue(amqp.getBody() instanceof Data);
@@ -478,7 +478,7 @@ public class JMSMappingOutboundTransformerTest {
       textMessage.setText("myTextMessageContent");
       textMessage.setJMSDestination(jmsDestination);
 
-      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(textMessage.getInnerMessage());
+      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(textMessage.getInnerMessage(), null);
 
       MessageAnnotations ma = amqp.getMessageAnnotations();
       Map<Symbol, Object> maMap = ma == null ? null : ma.getValue();
@@ -511,7 +511,7 @@ public class JMSMappingOutboundTransformerTest {
       textMessage.setText("myTextMessageContent");
       textMessage.setJMSReplyTo(jmsReplyTo);
 
-      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(textMessage.getInnerMessage());
+      AMQPMessage amqp = AMQPConverter.getInstance().fromCore(textMessage.getInnerMessage(), null);
 
       MessageAnnotations ma = amqp.getMessageAnnotations();
       Map<Symbol, Object> maMap = ma == null ? null : ma.getValue();
diff --git a/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSTransformationSpeedComparisonTest.java b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSTransformationSpeedComparisonTest.java
index a6f735d..b92dfdc 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSTransformationSpeedComparisonTest.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSTransformationSpeedComparisonTest.java
@@ -23,6 +23,7 @@ import java.util.concurrent.TimeUnit;
 
 import org.apache.activemq.artemis.api.core.ICoreMessage;
 import org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessage;
+import org.apache.activemq.artemis.protocol.amqp.broker.AMQPStandardMessage;
 import org.apache.activemq.artemis.protocol.amqp.converter.AMQPConverter;
 import org.apache.activemq.artemis.protocol.amqp.util.NettyReadable;
 import org.apache.activemq.artemis.protocol.amqp.util.NettyWritable;
@@ -62,12 +63,12 @@ public class JMSTransformationSpeedComparisonTest {
    public void testBodyOnlyMessage() throws Exception {
       MessageImpl message = (MessageImpl) Proton.message();
       message.setBody(new AmqpValue("String payload for AMQP message conversion performance testing."));
-      AMQPMessage encoded = encodeAndCreateAMQPMessage(message);
+      AMQPStandardMessage encoded = encodeAndCreateAMQPMessage(message);
 
       // Warm up
       for (int i = 0; i < WARM_CYCLES; ++i) {
          ICoreMessage intermediate = encoded.toCore();
-         encode(AMQPConverter.getInstance().fromCore(intermediate));
+         encode(AMQPConverter.getInstance().fromCore(intermediate,null));
       }
 
       long totalDuration = 0;
@@ -75,7 +76,7 @@ public class JMSTransformationSpeedComparisonTest {
       long startTime = System.nanoTime();
       for (int i = 0; i < PROFILE_CYCLES; ++i) {
          ICoreMessage intermediate = encoded.toCore();
-         encode(AMQPConverter.getInstance().fromCore(intermediate));
+         encode(AMQPConverter.getInstance().fromCore(intermediate,null));
       }
       totalDuration += System.nanoTime() - startTime;
 
@@ -92,12 +93,12 @@ public class JMSTransformationSpeedComparisonTest {
       message.setContentType("text/plain");
       message.setBody(new AmqpValue("String payload for AMQP message conversion performance testing."));
 
-      AMQPMessage encoded = encodeAndCreateAMQPMessage(message);
+      AMQPStandardMessage encoded = encodeAndCreateAMQPMessage(message);
 
       // Warm up
       for (int i = 0; i < WARM_CYCLES; ++i) {
          ICoreMessage intermediate = encoded.toCore();
-         encode(AMQPConverter.getInstance().fromCore(intermediate));
+         encode(AMQPConverter.getInstance().fromCore(intermediate,null));
       }
 
       long totalDuration = 0;
@@ -105,7 +106,7 @@ public class JMSTransformationSpeedComparisonTest {
       long startTime = System.nanoTime();
       for (int i = 0; i < PROFILE_CYCLES; ++i) {
          ICoreMessage intermediate = encoded.toCore();
-         encode(AMQPConverter.getInstance().fromCore(intermediate));
+         encode(AMQPConverter.getInstance().fromCore(intermediate,null));
       }
       totalDuration += System.nanoTime() - startTime;
 
@@ -114,12 +115,12 @@ public class JMSTransformationSpeedComparisonTest {
 
    @Test
    public void testTypicalQpidJMSMessage() throws Exception {
-      AMQPMessage encoded = encodeAndCreateAMQPMessage(createTypicalQpidJMSMessage());
+      AMQPStandardMessage encoded = encodeAndCreateAMQPMessage(createTypicalQpidJMSMessage());
 
       // Warm up
       for (int i = 0; i < WARM_CYCLES; ++i) {
          ICoreMessage intermediate = encoded.toCore();
-         encode(AMQPConverter.getInstance().fromCore(intermediate));
+         encode(AMQPConverter.getInstance().fromCore(intermediate,null));
       }
 
       long totalDuration = 0;
@@ -127,7 +128,7 @@ public class JMSTransformationSpeedComparisonTest {
       long startTime = System.nanoTime();
       for (int i = 0; i < PROFILE_CYCLES; ++i) {
          ICoreMessage intermediate = encoded.toCore();
-         encode(AMQPConverter.getInstance().fromCore(intermediate));
+         encode(AMQPConverter.getInstance().fromCore(intermediate,null));
       }
       totalDuration += System.nanoTime() - startTime;
 
@@ -137,12 +138,12 @@ public class JMSTransformationSpeedComparisonTest {
    @Test
    public void testComplexQpidJMSMessage() throws Exception {
 
-      AMQPMessage encoded = encodeAndCreateAMQPMessage(createComplexQpidJMSMessage());
+      AMQPStandardMessage encoded = encodeAndCreateAMQPMessage(createComplexQpidJMSMessage());
 
       // Warm up
       for (int i = 0; i < WARM_CYCLES; ++i) {
          ICoreMessage intermediate = encoded.toCore();
-         encode(AMQPConverter.getInstance().fromCore(intermediate));
+         encode(AMQPConverter.getInstance().fromCore(intermediate,null));
       }
 
       long totalDuration = 0;
@@ -150,7 +151,7 @@ public class JMSTransformationSpeedComparisonTest {
       long startTime = System.nanoTime();
       for (int i = 0; i < PROFILE_CYCLES; ++i) {
          ICoreMessage intermediate = encoded.toCore();
-         encode(AMQPConverter.getInstance().fromCore(intermediate));
+         encode(AMQPConverter.getInstance().fromCore(intermediate,null));
       }
       totalDuration += System.nanoTime() - startTime;
 
@@ -160,12 +161,12 @@ public class JMSTransformationSpeedComparisonTest {
    @Test
    public void testTypicalQpidJMSMessageInBoundOnly() throws Exception {
 
-      AMQPMessage encoded = encodeAndCreateAMQPMessage(createTypicalQpidJMSMessage());
+      AMQPStandardMessage encoded = encodeAndCreateAMQPMessage(createTypicalQpidJMSMessage());
 
       // Warm up
       for (int i = 0; i < WARM_CYCLES; ++i) {
          ICoreMessage intermediate = encoded.toCore();
-         encode(AMQPConverter.getInstance().fromCore(intermediate));
+         encode(AMQPConverter.getInstance().fromCore(intermediate,null));
       }
 
       long totalDuration = 0;
@@ -173,7 +174,7 @@ public class JMSTransformationSpeedComparisonTest {
       long startTime = System.nanoTime();
       for (int i = 0; i < PROFILE_CYCLES; ++i) {
          ICoreMessage intermediate = encoded.toCore();
-         encode(AMQPConverter.getInstance().fromCore(intermediate));
+         encode(AMQPConverter.getInstance().fromCore(intermediate,null));
       }
 
       totalDuration += System.nanoTime() - startTime;
@@ -183,12 +184,12 @@ public class JMSTransformationSpeedComparisonTest {
 
    @Test
    public void testTypicalQpidJMSMessageOutBoundOnly() throws Exception {
-      AMQPMessage encoded = encodeAndCreateAMQPMessage(createTypicalQpidJMSMessage());
+      AMQPStandardMessage encoded = encodeAndCreateAMQPMessage(createTypicalQpidJMSMessage());
 
       // Warm up
       for (int i = 0; i < WARM_CYCLES; ++i) {
          ICoreMessage intermediate = encoded.toCore();
-         encode(AMQPConverter.getInstance().fromCore(intermediate));
+         encode(AMQPConverter.getInstance().fromCore(intermediate,null));
       }
 
       long totalDuration = 0;
@@ -196,7 +197,7 @@ public class JMSTransformationSpeedComparisonTest {
       long startTime = System.nanoTime();
       for (int i = 0; i < PROFILE_CYCLES; ++i) {
          ICoreMessage intermediate = encoded.toCore();
-         encode(AMQPConverter.getInstance().fromCore(intermediate));
+         encode(AMQPConverter.getInstance().fromCore(intermediate,null));
       }
 
       totalDuration += System.nanoTime() - startTime;
@@ -272,13 +273,13 @@ public class JMSTransformationSpeedComparisonTest {
       return message;
    }
 
-   private AMQPMessage encodeAndCreateAMQPMessage(MessageImpl message) {
+   private AMQPStandardMessage encodeAndCreateAMQPMessage(MessageImpl message) {
       NettyWritable encoded = new NettyWritable(Unpooled.buffer(1024));
       message.encode(encoded);
 
       NettyReadable readable = new NettyReadable(encoded.getByteBuf());
 
-      return new AMQPMessage(AMQPMessage.DEFAULT_MESSAGE_FORMAT, readable, null, null);
+      return new AMQPStandardMessage(AMQPMessage.DEFAULT_MESSAGE_FORMAT, readable, null, null);
    }
 
    private void encode(AMQPMessage target) {
diff --git a/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/MessageTransformationTest.java b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/MessageTransformationTest.java
index 9d171aa..c4c527e 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/MessageTransformationTest.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/MessageTransformationTest.java
@@ -27,6 +27,7 @@ import java.util.Map;
 
 import org.apache.activemq.artemis.api.core.ICoreMessage;
 import org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessage;
+import org.apache.activemq.artemis.protocol.amqp.broker.AMQPStandardMessage;
 import org.apache.activemq.artemis.protocol.amqp.converter.AMQPConverter;
 import org.apache.activemq.artemis.protocol.amqp.util.NettyReadable;
 import org.apache.activemq.artemis.protocol.amqp.util.NettyWritable;
@@ -58,7 +59,7 @@ public class MessageTransformationTest {
       incomingMessage.setBody(new AmqpValue("String payload for AMQP message conversion performance testing."));
 
       ICoreMessage core = encodeAndCreateAMQPMessage(incomingMessage).toCore();
-      AMQPMessage outboudMessage = AMQPConverter.getInstance().fromCore(core);
+      AMQPMessage outboudMessage = AMQPConverter.getInstance().fromCore(core, null);
 
       assertNull(outboudMessage.getHeader());
 
@@ -76,7 +77,7 @@ public class MessageTransformationTest {
       incomingMessage.setMessageId("ID:SomeQualifier:0:0:1");
 
       ICoreMessage core = encodeAndCreateAMQPMessage(incomingMessage).toCore();
-      AMQPMessage outboudMessage = AMQPConverter.getInstance().fromCore(core);
+      AMQPMessage outboudMessage = AMQPConverter.getInstance().fromCore(core, null);
 
       assertNull(outboudMessage.getHeader());
       assertNotNull(outboudMessage.getProperties());
@@ -90,7 +91,7 @@ public class MessageTransformationTest {
       incomingMessage.setDurable(true);
 
       ICoreMessage core = encodeAndCreateAMQPMessage(incomingMessage).toCore();
-      AMQPMessage outboudMessage = AMQPConverter.getInstance().fromCore(core);
+      AMQPMessage outboudMessage = AMQPConverter.getInstance().fromCore(core, null);
 
       assertNotNull(outboudMessage.getHeader());
 
@@ -145,7 +146,7 @@ public class MessageTransformationTest {
       message.setBody(new AmqpValue("String payload for AMQP message conversion performance testing."));
 
       ICoreMessage core = encodeAndCreateAMQPMessage(message).toCore();
-      AMQPMessage outboudMessage = AMQPConverter.getInstance().fromCore(core);
+      AMQPMessage outboudMessage = AMQPConverter.getInstance().fromCore(core, null);
 
       assertEquals(10, outboudMessage.getApplicationProperties().getValue().size());
       assertEquals(4, outboudMessage.getMessageAnnotations().getValue().size());
@@ -157,6 +158,6 @@ public class MessageTransformationTest {
 
       NettyReadable readable = new NettyReadable(encoded.getByteBuf());
 
-      return new AMQPMessage(AMQPMessage.DEFAULT_MESSAGE_FORMAT, readable, null, null);
+      return new AMQPStandardMessage(AMQPMessage.DEFAULT_MESSAGE_FORMAT, readable, null, null);
    }
 }
diff --git a/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/proton/ProtonServerReceiverContextTest.java b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/proton/ProtonServerReceiverContextTest.java
index 571ca92..62858c8 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/proton/ProtonServerReceiverContextTest.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/proton/ProtonServerReceiverContextTest.java
@@ -19,7 +19,6 @@ package org.apache.activemq.artemis.protocol.amqp.proton;
 import static java.util.Arrays.asList;
 import static java.util.Collections.singletonList;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.doAnswer;
@@ -37,6 +36,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQException;
 import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.core.server.RoutingContext;
 import org.apache.activemq.artemis.core.transaction.Transaction;
+import org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessage;
 import org.apache.activemq.artemis.protocol.amqp.broker.AMQPSessionCallback;
 import org.apache.activemq.artemis.protocol.amqp.broker.ProtonProtocolManager;
 import org.apache.activemq.artemis.protocol.amqp.exceptions.ActiveMQAMQPException;
@@ -48,7 +48,6 @@ import org.apache.qpid.proton.amqp.messaging.Outcome;
 import org.apache.qpid.proton.amqp.messaging.Rejected;
 import org.apache.qpid.proton.amqp.messaging.Source;
 import org.apache.qpid.proton.amqp.transport.DeliveryState;
-import org.apache.qpid.proton.codec.ReadableBuffer;
 import org.apache.qpid.proton.engine.Delivery;
 import org.apache.qpid.proton.engine.Receiver;
 import org.junit.Test;
@@ -68,40 +67,24 @@ public class ProtonServerReceiverContextTest {
 
    @Test
    public void addressFull_SourceSupportsModified() throws Exception {
-      doOnMessageWithDeliveryException(asList(Rejected.DESCRIPTOR_SYMBOL,
-                                              Accepted.DESCRIPTOR_SYMBOL,
-                                              Modified.DESCRIPTOR_SYMBOL),
-                                       null, new ActiveMQAddressFullException(),
-                                       Modified.class);
+      doOnMessageWithDeliveryException(asList(Rejected.DESCRIPTOR_SYMBOL, Accepted.DESCRIPTOR_SYMBOL, Modified.DESCRIPTOR_SYMBOL), null, new ActiveMQAddressFullException(), Modified.class);
    }
 
    @Test
    public void addressFull_SourceDoesNotSupportModified() throws Exception {
-      doOnMessageWithDeliveryException(asList(Rejected.DESCRIPTOR_SYMBOL,
-                                              Accepted.DESCRIPTOR_SYMBOL),
-                                       null, new ActiveMQAddressFullException(),
-                                       Rejected.class);
+      doOnMessageWithDeliveryException(asList(Rejected.DESCRIPTOR_SYMBOL, Accepted.DESCRIPTOR_SYMBOL), null, new ActiveMQAddressFullException(), Rejected.class);
    }
 
    @Test
    public void otherFailure_SourceSupportsRejects() throws Exception {
-      doOnMessageWithDeliveryException(asList(Rejected.DESCRIPTOR_SYMBOL,
-                                              Accepted.DESCRIPTOR_SYMBOL,
-                                              Modified.DESCRIPTOR_SYMBOL),
-                                       null, new ActiveMQException(),
-                                       Rejected.class);
+      doOnMessageWithDeliveryException(asList(Rejected.DESCRIPTOR_SYMBOL, Accepted.DESCRIPTOR_SYMBOL, Modified.DESCRIPTOR_SYMBOL), null, new ActiveMQException(), Rejected.class);
    }
 
    @Test
    public void otherFailure_SourceDoesNotSupportReject() throws Exception {
-      doOnMessageWithDeliveryException(singletonList(Accepted.DESCRIPTOR_SYMBOL),
-                                       Accepted.getInstance(), new ActiveMQException(),
-                                       Accepted.class);
+      doOnMessageWithDeliveryException(singletonList(Accepted.DESCRIPTOR_SYMBOL), Accepted.getInstance(), new ActiveMQException(), Accepted.class);
       // violates AMQP specification - see explanation ProtonServerReceiverContext.determineDeliveryState
-      doOnMessageWithDeliveryException(singletonList(Accepted.DESCRIPTOR_SYMBOL),
-                                       null,
-                                       new ActiveMQException(),
-                                       Rejected.class);
+      doOnMessageWithDeliveryException(singletonList(Accepted.DESCRIPTOR_SYMBOL), null, new ActiveMQException(), Rejected.class);
    }
 
    private void doOnMessageWithAbortedDeliveryTestImpl(boolean drain) throws ActiveMQAMQPException {
@@ -140,7 +123,8 @@ public class ProtonServerReceiverContextTest {
    }
 
    private void doOnMessageWithDeliveryException(List<Symbol> sourceSymbols,
-                                                 Outcome defaultOutcome, Exception deliveryException,
+                                                 Outcome defaultOutcome,
+                                                 Exception deliveryException,
                                                  Class<? extends DeliveryState> expectedDeliveryState) throws Exception {
       AMQPConnectionContext mockConnContext = mock(AMQPConnectionContext.class);
       doAnswer((Answer<Void>) invocation -> {
@@ -152,7 +136,6 @@ public class ProtonServerReceiverContextTest {
       when(mockProtocolManager.isUseModifiedForTransientDeliveryErrors()).thenReturn(true);
       when(mockConnContext.getProtocolManager()).thenReturn(mockProtocolManager);
 
-
       AMQPSessionCallback mockSession = mock(AMQPSessionCallback.class);
 
       Receiver mockReceiver = mock(Receiver.class);
@@ -167,15 +150,7 @@ public class ProtonServerReceiverContextTest {
       source.setDefaultOutcome(defaultOutcome);
       when(mockReceiver.getSource()).thenReturn(source);
 
-      doThrow(deliveryException).when(mockSession)
-                                .serverSend(eq(rc),
-                                            nullable(Transaction.class),
-                                            eq(mockReceiver),
-                                            eq(mockDelivery),
-                                            nullable(SimpleString.class),
-                                            anyInt(),
-                                            nullable(ReadableBuffer.class),
-                                            any(RoutingContext.class));
+      doThrow(deliveryException).when(mockSession).serverSend(eq(rc), nullable(Transaction.class), eq(mockReceiver), eq(mockDelivery), nullable(SimpleString.class), any(RoutingContext.class), nullable(AMQPMessage.class));
 
       rc.onMessage(mockDelivery);
 
diff --git a/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTSession.java b/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTSession.java
index 1da256b..b74622e 100644
--- a/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTSession.java
+++ b/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTSession.java
@@ -20,7 +20,7 @@ package org.apache.activemq.artemis.core.protocol.mqtt;
 import java.util.UUID;
 
 import org.apache.activemq.artemis.core.config.WildcardConfiguration;
-import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
 import org.apache.activemq.artemis.core.server.ActiveMQServer;
 import org.apache.activemq.artemis.core.server.impl.ServerSessionImpl;
 import org.apache.activemq.artemis.spi.core.protocol.SessionCallback;
diff --git a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireMessageConverter.java b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireMessageConverter.java
index 63082d9..cfc4aa6 100644
--- a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireMessageConverter.java
+++ b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireMessageConverter.java
@@ -42,7 +42,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQPropertyConversionException;
 import org.apache.activemq.artemis.api.core.ICoreMessage;
 import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.core.message.impl.CoreMessage;
-import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
 import org.apache.activemq.artemis.core.protocol.openwire.amq.AMQConsumer;
 import org.apache.activemq.artemis.core.protocol.openwire.util.OpenWireUtil;
 import org.apache.activemq.artemis.core.server.MessageReference;
diff --git a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenwireMessage.java b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenwireMessage.java
index 9644b70..0279b5b 100644
--- a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenwireMessage.java
+++ b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenwireMessage.java
@@ -24,9 +24,8 @@ import org.apache.activemq.artemis.api.core.ActiveMQException;
 import org.apache.activemq.artemis.api.core.ActiveMQPropertyConversionException;
 import org.apache.activemq.artemis.api.core.ICoreMessage;
 import org.apache.activemq.artemis.api.core.Message;
-import org.apache.activemq.artemis.api.core.RefCountMessageListener;
 import org.apache.activemq.artemis.api.core.SimpleString;
-import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
 import org.apache.activemq.artemis.core.persistence.Persister;
 
 import io.netty.buffer.ByteBuf;
@@ -70,18 +69,13 @@ public class OpenwireMessage implements Message {
    }
 
    @Override
-   public RefCountMessageListener getContext() {
-      return null;
-   }
-
-   @Override
-   public Message setContext(RefCountMessageListener context) {
+   public Message setBuffer(ByteBuf buffer) {
       return null;
    }
 
    @Override
-   public Message setBuffer(ByteBuf buffer) {
-      return null;
+   public int getDurableCount() {
+      return 0;
    }
 
    @Override
@@ -140,7 +134,7 @@ public class OpenwireMessage implements Message {
    }
 
    @Override
-   public Persister<Message, CoreMessageObjectPools> getPersister() {
+   public Persister<Message> getPersister() {
       return null;
    }
 
@@ -465,22 +459,22 @@ public class OpenwireMessage implements Message {
    }
 
    @Override
-   public int incrementRefCount() throws Exception {
+   public int refUp() {
       return 0;
    }
 
    @Override
-   public int decrementRefCount() throws Exception {
+   public int refDown() {
       return 0;
    }
 
    @Override
-   public int incrementDurableRefCount() {
+   public int durableUp() {
       return 0;
    }
 
    @Override
-   public int decrementDurableRefCount() {
+   public int durableDown() {
       return 0;
    }
 
@@ -503,4 +497,19 @@ public class OpenwireMessage implements Message {
    public long getPersistentSize() throws ActiveMQException {
       return 0;
    }
+
+   @Override
+   public int getUsage() {
+      return 0;
+   }
+
+   @Override
+   public int usageUp() {
+      return 0;
+   }
+
+   @Override
+   public int usageDown() {
+      return 0;
+   }
 }
diff --git a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/amq/AMQSession.java b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/amq/AMQSession.java
index b780563..87a41ee 100644
--- a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/amq/AMQSession.java
+++ b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/amq/AMQSession.java
@@ -32,7 +32,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQQueueExistsException;
 import org.apache.activemq.artemis.api.core.RoutingType;
 import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.core.io.IOCallback;
-import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
 import org.apache.activemq.artemis.core.paging.PagingStore;
 import org.apache.activemq.artemis.core.protocol.openwire.OpenWireConnection;
 import org.apache.activemq.artemis.core.protocol.openwire.OpenWireMessageConverter;
diff --git a/artemis-protocols/artemis-stomp-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/stomp/StompSession.java b/artemis-protocols/artemis-stomp-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/stomp/StompSession.java
index eec52b6..d7e2ddc 100644
--- a/artemis-protocols/artemis-stomp-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/stomp/StompSession.java
+++ b/artemis-protocols/artemis-stomp-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/stomp/StompSession.java
@@ -363,11 +363,9 @@ public class StompSession implements SessionCallback {
 
       largeMessage.releaseResources(true);
 
-      largeMessage.putLongProperty(Message.HDR_LARGE_BODY_SIZE, bytes.length);
+      largeMessage.toMessage().putLongProperty(Message.HDR_LARGE_BODY_SIZE, bytes.length);
 
-      session.send(largeMessage, direct);
-
-      largeMessage = null;
+      session.send(largeMessage.toMessage(), direct);
    }
 
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/paging/cursor/impl/LivePageCacheImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/paging/cursor/impl/LivePageCacheImpl.java
index 6c98c12..6fb42c1 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/paging/cursor/impl/LivePageCacheImpl.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/paging/cursor/impl/LivePageCacheImpl.java
@@ -19,7 +19,6 @@ package org.apache.activemq.artemis.core.paging.cursor.impl;
 import org.apache.activemq.artemis.core.paging.PagedMessage;
 import org.apache.activemq.artemis.core.paging.cursor.LivePageCache;
 import org.apache.activemq.artemis.core.paging.cursor.PagePosition;
-import org.apache.activemq.artemis.core.server.LargeServerMessage;
 import org.apache.activemq.artemis.utils.collections.ConcurrentAppendOnlyChunkedList;
 import org.jboss.logging.Logger;
 
@@ -73,9 +72,7 @@ public final class LivePageCacheImpl implements LivePageCache {
 
    @Override
    public void addLiveMessage(PagedMessage message) {
-      if (message.getMessage().isLargeMessage()) {
-         ((LargeServerMessage) message.getMessage()).incrementDelayDeletionCount();
-      }
+      message.getMessage().usageUp();
       messages.add(message);
    }
 
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/paging/impl/Page.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/paging/impl/Page.java
index a7a164d..bbaea46 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/paging/impl/Page.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/paging/impl/Page.java
@@ -24,7 +24,6 @@ import java.util.concurrent.atomic.AtomicInteger;
 
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.Unpooled;
-import org.apache.activemq.artemis.api.core.ICoreMessage;
 import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.core.buffers.impl.ChannelBufferWrapper;
 import org.apache.activemq.artemis.core.io.SequentialFile;
@@ -35,7 +34,6 @@ import org.apache.activemq.artemis.core.paging.cursor.PageSubscriptionCounter;
 import org.apache.activemq.artemis.core.persistence.StorageManager;
 import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle;
 import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
-import org.apache.activemq.artemis.core.server.LargeServerMessage;
 import org.apache.activemq.artemis.utils.DataConstants;
 import org.apache.activemq.artemis.utils.Env;
 import org.apache.activemq.artemis.utils.collections.ConcurrentHashSet;
@@ -494,14 +492,10 @@ public final class Page implements Comparable<Page> {
       List<Long> largeMessageIds = new ArrayList<>();
       if (messages != null) {
          for (PagedMessage msg : messages) {
-            if (msg.getMessage() instanceof ICoreMessage && (msg.getMessage()).isLargeMessage()) {
-               LargeServerMessage lmsg = (LargeServerMessage) msg.getMessage();
-
-               // Remember, cannot call delete directly here
-               // Because the large-message may be linked to another message
-               // or it may still being delivered even though it has been acked already
-               lmsg.decrementDelayDeletionCount();
-               largeMessageIds.add(lmsg.getMessageID());
+            // this will trigger large message delete
+            msg.getMessage().usageDown();
+            if ((msg.getMessage()).isLargeMessage()) {
+               largeMessageIds.add(msg.getMessage().getMessageID());
             }
          }
       }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/paging/impl/PagedMessageImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/paging/impl/PagedMessageImpl.java
index 3ef833d..11eb202 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/paging/impl/PagedMessageImpl.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/paging/impl/PagedMessageImpl.java
@@ -74,10 +74,10 @@ public class PagedMessageImpl implements PagedMessage {
          LargeServerMessage lgMessage = storage.createLargeMessage();
 
          ActiveMQBuffer buffer = ActiveMQBuffers.wrappedBuffer(largeMessageLazyData);
-         lgMessage = LargeMessagePersister.getInstance().decode(buffer, lgMessage);
-         lgMessage.incrementDelayDeletionCount();
+         lgMessage = LargeMessagePersister.getInstance().decode(buffer, lgMessage, null);
+         lgMessage.toMessage().usageUp();
          lgMessage.setPaged();
-         this.message = lgMessage;
+         this.message = lgMessage.toMessage();
          largeMessageLazyData = null;
       }
    }
@@ -107,12 +107,12 @@ public class PagedMessageImpl implements PagedMessage {
             largeMessageLazyData = new byte[largeMessageHeaderSize];
             buffer.readBytes(largeMessageLazyData);
          } else {
-            this.message = storageManager.createLargeMessage();
-            LargeMessagePersister.getInstance().decode(buffer, (LargeServerMessage) message);
-            ((LargeServerMessage) message).incrementDelayDeletionCount();
+            this.message = storageManager.createLargeMessage().toMessage();
+            LargeMessagePersister.getInstance().decode(buffer, (LargeServerMessage) message, null);
+            ((LargeServerMessage) message).toMessage().usageUp();
          }
       } else {
-         this.message = MessagePersister.getInstance().decode(buffer, null);
+         this.message = MessagePersister.getInstance().decode(buffer, null, null);
       }
 
       int queueIDsSize = buffer.readInt();
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/paging/impl/PagingStoreImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/paging/impl/PagingStoreImpl.java
index 341ee7c..6f4db11 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/paging/impl/PagingStoreImpl.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/paging/impl/PagingStoreImpl.java
@@ -446,37 +446,7 @@ public class PagingStoreImpl implements PagingStore {
                currentPageId = pageId;
 
                if (pageId != 0) {
-                  Page page = createPage(pageId);
-                  page.open();
-
-                  List<PagedMessage> messages = page.read(storageManager);
-
-                  LivePageCache pageCache = new LivePageCacheImpl(pageId);
-
-                  for (PagedMessage msg : messages) {
-                     pageCache.addLiveMessage(msg);
-                     if (msg.getMessage().isLargeMessage()) {
-                        // We have to do this since addLIveMessage will increment an extra one
-                        ((LargeServerMessage) msg.getMessage()).decrementDelayDeletionCount();
-                     }
-                  }
-
-                  page.setLiveCache(pageCache);
-
-                  currentPageSize = page.getSize();
-
-                  currentPage = page;
-
-                  cursorProvider.addPageCache(pageCache);
-
-                  /**
-                   * The page file might be incomplete in the cases: 1) last message incomplete 2) disk damaged.
-                   * In case 1 we can keep writing the file. But in case 2 we'd better not bcs old data might be overwritten.
-                   * Here we open a new page so the incomplete page would be reserved for recovery if needed.
-                   */
-                  if (page.getSize() != page.getFile().size()) {
-                     openNewPage();
-                  }
+                  reloadLivePage(pageId);
                }
 
                // We will not mark it for paging if there's only a single empty file
@@ -492,6 +462,39 @@ public class PagingStoreImpl implements PagingStore {
       }
    }
 
+   protected void reloadLivePage(int pageId) throws Exception {
+      Page page = createPage(pageId);
+      page.open();
+
+      List<PagedMessage> messages = page.read(storageManager);
+
+      LivePageCache pageCache = new LivePageCacheImpl(pageId);
+
+      for (PagedMessage msg : messages) {
+         pageCache.addLiveMessage(msg);
+         // As we add back to the live page,
+         // we have to discount one when we read the page
+         msg.getMessage().usageDown();
+      }
+
+      page.setLiveCache(pageCache);
+
+      currentPageSize = page.getSize();
+
+      currentPage = page;
+
+      cursorProvider.addPageCache(pageCache);
+
+      /**
+       * The page file might be incomplete in the cases: 1) last message incomplete 2) disk damaged.
+       * In case 1 we can keep writing the file. But in case 2 we'd better not bcs old data might be overwritten.
+       * Here we open a new page so the incomplete page would be reserved for recovery if needed.
+       */
+      if (page.getSize() != page.getFile().size()) {
+         openNewPage();
+      }
+   }
+
    @Override
    public void stopPaging() {
       lock.writeLock().lock();
@@ -953,14 +956,16 @@ public class PagingStoreImpl implements PagingStore {
 
    @Override
    public void durableDown(Message message, int durableCount) {
+      refDown(message, durableCount);
    }
 
    @Override
    public void durableUp(Message message, int durableCount) {
+      refUp(message, durableCount);
    }
 
    @Override
-   public void nonDurableUp(Message message, int count) {
+   public void refUp(Message message, int count) {
       if (count == 1) {
          this.addSize(message.getMemoryEstimate() + MessageReferenceImpl.getMemoryEstimate());
       } else {
@@ -969,9 +974,9 @@ public class PagingStoreImpl implements PagingStore {
    }
 
    @Override
-   public void nonDurableDown(Message message, int count) {
+   public void refDown(Message message, int count) {
       if (count < 0) {
-         // this could happen on paged messages since they are not routed and incrementRefCount is never called
+         // this could happen on paged messages since they are not routed and refUp is never called
          return;
       }
 
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/StorageManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/StorageManager.java
index 0436398..39183cd 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/StorageManager.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/StorageManager.java
@@ -24,6 +24,8 @@ import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
+import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
+import org.apache.activemq.artemis.api.core.ActiveMQException;
 import org.apache.activemq.artemis.api.core.Message;
 import org.apache.activemq.artemis.api.core.Pair;
 import org.apache.activemq.artemis.api.core.SimpleString;
@@ -265,6 +267,17 @@ public interface StorageManager extends IDGenerator, ActiveMQComponent {
     */
    SequentialFile createFileForLargeMessage(long messageID, LargeMessageExtension extension);
 
+   void deleteLargeMessageBody(LargeServerMessage largeServerMessage) throws ActiveMQException;
+
+   default SequentialFile createFileForLargeMessage(long messageID, boolean durable) {
+      if (durable) {
+         return createFileForLargeMessage(messageID, LargeMessageExtension.DURABLE);
+      } else {
+         return createFileForLargeMessage(messageID, LargeMessageExtension.TEMPORARY);
+      }
+   }
+
+
    void prepare(long txID, Xid xid) throws Exception;
 
    void commit(long txID) throws Exception;
@@ -416,6 +429,10 @@ public interface StorageManager extends IDGenerator, ActiveMQComponent {
     */
    void addBytesToLargeMessage(SequentialFile appendFile, long messageID, byte[] bytes) throws Exception;
 
+   void addBytesToLargeMessage(SequentialFile file,
+                               long messageId,
+                               ActiveMQBuffer bytes) throws Exception;
+
    /**
     * Stores the id from IDManager.
     *
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/AbstractJournalStorageManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/AbstractJournalStorageManager.java
index 296b221..9b8177e 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/AbstractJournalStorageManager.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/AbstractJournalStorageManager.java
@@ -58,7 +58,7 @@ import org.apache.activemq.artemis.core.journal.Journal;
 import org.apache.activemq.artemis.core.journal.JournalLoadInformation;
 import org.apache.activemq.artemis.core.journal.PreparedTransactionInfo;
 import org.apache.activemq.artemis.core.journal.RecordInfo;
-import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
 import org.apache.activemq.artemis.core.paging.PageTransactionInfo;
 import org.apache.activemq.artemis.core.paging.PagingManager;
 import org.apache.activemq.artemis.core.paging.PagingStore;
@@ -134,6 +134,19 @@ public abstract class AbstractJournalStorageManager extends CriticalComponentImp
    protected static final int CRITICAL_STOP = 1;
    protected static final int CRITICAL_STOP_2 = 2;
 
+
+   public static ThreadLocal<StorageManager> storageManagerThreadLocal = new ThreadLocal<>();
+
+   /** Persisters may need to access this on reloading of the journal,
+    *  for large message processing */
+   public static void setupThreadLocal(StorageManager manager) {
+      storageManagerThreadLocal.set(manager);
+   }
+
+   public static StorageManager getThreadLocal() {
+      return storageManagerThreadLocal.get();
+   }
+
    private static final Logger logger = Logger.getLogger(AbstractJournalStorageManager.class);
 
    public enum JournalContent {
@@ -359,7 +372,7 @@ public abstract class AbstractJournalStorageManager extends CriticalComponentImp
          // Note that we don't sync, the add reference that comes immediately after will sync if
          // appropriate
 
-         if (message.isLargeMessage()) {
+         if (message.isLargeMessage() && message instanceof LargeServerMessageImpl) {
             messageJournal.appendAddRecord(message.getMessageID(), JournalRecordIds.ADD_LARGE_MESSAGE, LargeMessagePersister.getInstance(), message, false, getContext(false));
          } else {
             messageJournal.appendAddRecord(message.getMessageID(), JournalRecordIds.ADD_MESSAGE_PROTOCOL, message.getPersister(), message, false, getContext(false));
@@ -480,7 +493,8 @@ public abstract class AbstractJournalStorageManager extends CriticalComponentImp
 
       readLock();
       try {
-         if (message.isLargeMessage()) {
+         if (message.isLargeMessage() && message instanceof LargeServerMessageImpl) {
+            // this is a core large message
             messageJournal.appendAddRecordTransactional(txID, message.getMessageID(), JournalRecordIds.ADD_LARGE_MESSAGE, LargeMessagePersister.getInstance(), message);
          } else {
             messageJournal.appendAddRecordTransactional(txID, message.getMessageID(), JournalRecordIds.ADD_MESSAGE_PROTOCOL, message.getPersister(), message);
@@ -843,6 +857,7 @@ public abstract class AbstractJournalStorageManager extends CriticalComponentImp
 
       Map<Long, Message> messages = new HashMap<>();
       readLock();
+      setupThreadLocal(this);
       try {
 
          JournalLoadInformation info = messageJournal.load(records, preparedTransactions, new LargeMessageTXFailureCallback(this));
@@ -908,7 +923,7 @@ public abstract class AbstractJournalStorageManager extends CriticalComponentImp
                   case JournalRecordIds.ADD_LARGE_MESSAGE: {
                      LargeServerMessage largeMessage = parseLargeMessage(buff);
 
-                     messages.put(record.id, largeMessage);
+                     messages.put(record.id, largeMessage.toMessage());
 
                      largeMessages.add(largeMessage);
 
@@ -920,7 +935,15 @@ public abstract class AbstractJournalStorageManager extends CriticalComponentImp
 
                   case JournalRecordIds.ADD_MESSAGE_PROTOCOL: {
 
-                     Message message = MessagePersister.getInstance().decode(buff, pools);
+                     Message message = MessagePersister.getInstance().decode(buff, null, pools);
+
+                     /* if (message instanceof LargeServerMessage) {
+                        try {
+                           ((LargeServerMessage) message).finishParse();
+                        } catch (Exception e) {
+                           logger.warn(e.getMessage(), e);
+                        }
+                     } */
 
                      messages.put(record.id, message);
 
@@ -1194,9 +1217,9 @@ public abstract class AbstractJournalStorageManager extends CriticalComponentImp
          }
 
          for (LargeServerMessage msg : largeMessages) {
-            if (msg.getRefCount() == 0) {
+            if (msg.toMessage().getRefCount() == 0) {
                ActiveMQServerLogger.LOGGER.largeMessageWithNoRef(msg.getMessageID());
-               msg.decrementDelayDeletionCount();
+               msg.toMessage().usageDown();
             }
          }
 
@@ -1217,6 +1240,8 @@ public abstract class AbstractJournalStorageManager extends CriticalComponentImp
          return info;
       } finally {
          readUnLock();
+         // need to clear it, otherwise we may have a permanent leak
+         setupThreadLocal(null);
       }
    }
 
@@ -1712,7 +1737,7 @@ public abstract class AbstractJournalStorageManager extends CriticalComponentImp
          if (largeServerMessage.getPendingRecordID() >= 0) {
             try {
                confirmPendingLargeMessage(largeServerMessage.getPendingRecordID());
-               largeServerMessage.setPendingRecordID(LargeServerMessage.NO_PENDING_ID);
+               largeServerMessage.clearPendingRecordID();
             } catch (Exception e) {
                ActiveMQServerLogger.LOGGER.warn(e.getMessage(), e);
             }
@@ -1758,7 +1783,7 @@ public abstract class AbstractJournalStorageManager extends CriticalComponentImp
 
             switch (recordType) {
                case JournalRecordIds.ADD_LARGE_MESSAGE: {
-                  messages.put(record.id, parseLargeMessage(buff));
+                  messages.put(record.id, parseLargeMessage(buff).toMessage());
 
                   break;
                }
@@ -1770,7 +1795,7 @@ public abstract class AbstractJournalStorageManager extends CriticalComponentImp
                   if (pools == null) {
                      pools = new CoreMessageObjectPools();
                   }
-                  Message message = MessagePersister.getInstance().decode(buff, pools);
+                  Message message = MessagePersister.getInstance().decode(buff, null, pools);
 
                   messages.put(record.id, message);
 
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/DescribeJournal.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/DescribeJournal.java
index 910b0c1..9ee72ea 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/DescribeJournal.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/DescribeJournal.java
@@ -561,16 +561,15 @@ public final class DescribeJournal {
 
             LargeServerMessage largeMessage = new LargeServerMessageImpl(storageManager);
 
-            LargeMessagePersister.getInstance().decode(buffer, largeMessage);
+            LargeMessagePersister.getInstance().decode(buffer, largeMessage, null);
 
-            return new MessageDescribe(largeMessage);
+            return new MessageDescribe(largeMessage.toMessage());
          }
          case ADD_MESSAGE: {
             return "ADD-MESSAGE is not supported any longer, use export/import";
          }
          case ADD_MESSAGE_PROTOCOL: {
-            Message message = MessagePersister.getInstance().decode(buffer, null);
-
+            Message message = MessagePersister.getInstance().decode(buffer, null, null);
             return new MessageDescribe(message);
          }
          case ADD_REF: {
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/JournalStorageManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/JournalStorageManager.java
index e200ead..567ee03 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/JournalStorageManager.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/JournalStorageManager.java
@@ -348,14 +348,6 @@ public class JournalStorageManager extends AbstractJournalStorageManager {
       }
    }
 
-   protected SequentialFile createFileForLargeMessage(final long messageID, final boolean durable) {
-      if (durable) {
-         return createFileForLargeMessage(messageID, LargeMessageExtension.DURABLE);
-      } else {
-         return createFileForLargeMessage(messageID, LargeMessageExtension.TEMPORARY);
-      }
-   }
-
    @Override
    /**
     * @param buff
@@ -365,13 +357,13 @@ public class JournalStorageManager extends AbstractJournalStorageManager {
    protected LargeServerMessage parseLargeMessage(final ActiveMQBuffer buff) throws Exception {
       LargeServerMessage largeMessage = createLargeMessage();
 
-      LargeMessagePersister.getInstance().decode(buff, largeMessage);
+      LargeMessagePersister.getInstance().decode(buff, largeMessage, null);
 
-      if (largeMessage.containsProperty(Message.HDR_ORIG_MESSAGE_ID)) {
+      if (largeMessage.toMessage().containsProperty(Message.HDR_ORIG_MESSAGE_ID)) {
          // for compatibility: couple with old behaviour, copying the old file to avoid message loss
-         long originalMessageID = largeMessage.getLongProperty(Message.HDR_ORIG_MESSAGE_ID);
+         long originalMessageID = largeMessage.toMessage().getLongProperty(Message.HDR_ORIG_MESSAGE_ID);
 
-         SequentialFile currentFile = createFileForLargeMessage(largeMessage.getMessageID(), true);
+         SequentialFile currentFile = createFileForLargeMessage(largeMessage.toMessage().getMessageID(), true);
 
          if (!currentFile.exists()) {
             SequentialFile linkedFile = createFileForLargeMessage(originalMessageID, true);
@@ -442,18 +434,10 @@ public class JournalStorageManager extends AbstractJournalStorageManager {
       journalFF.releaseBuffer(buffer);
    }
 
-   public long storePendingLargeMessage(final long messageID, long recordID) throws Exception {
+   public long storePendingLargeMessage(final long messageID) throws Exception {
       readLock();
       try {
-         if (recordID == LargeServerMessage.NO_PENDING_ID) {
-            recordID = generateID();
-         } else {
-            //this means the large message doesn't
-            //have a pendingRecordID, but one has been
-            //generated (coming from live server) for use.
-            recordID = -recordID;
-         }
-
+         long recordID = generateID();
          messageJournal.appendAddRecord(recordID, JournalRecordIds.ADD_LARGE_MESSAGE_PENDING, new PendingLargeMessageEncoding(messageID), true, getContext(true));
 
          return recordID;
@@ -462,31 +446,31 @@ public class JournalStorageManager extends AbstractJournalStorageManager {
       }
    }
 
-   // This should be accessed from this package only
-   void deleteLargeMessageFile(final LargeServerMessage largeServerMessage) throws ActiveMQException {
+   @Override
+   public void deleteLargeMessageBody(final LargeServerMessage largeServerMessage) throws ActiveMQException {
       synchronized (largeServerMessage) {
-         if (largeServerMessage.getPendingRecordID() < 0) {
+         if (!largeServerMessage.hasPendingRecord()) {
             try {
                // The delete file happens asynchronously
                // And the client won't be waiting for the actual file to be deleted.
                // We set a temporary record (short lived) on the journal
                // to avoid a situation where the server is restarted and pending large message stays on forever
-               largeServerMessage.setPendingRecordID(storePendingLargeMessage(largeServerMessage.getMessageID(), largeServerMessage.getPendingRecordID()));
+               largeServerMessage.setPendingRecordID(storePendingLargeMessage(largeServerMessage.toMessage().getMessageID()));
             } catch (Exception e) {
                throw new ActiveMQInternalErrorException(e.getMessage(), e);
             }
          }
       }
-      final SequentialFile file = largeServerMessage.getFile();
+      final SequentialFile file = largeServerMessage.getAppendFile();
       if (file == null) {
          return;
       }
 
-      if (largeServerMessage.isDurable() && isReplicated()) {
+      if (largeServerMessage.toMessage().isDurable() && isReplicated()) {
          readLock();
          try {
             if (isReplicated() && replicator.isSynchronizing()) {
-               largeMessagesToDelete.put(largeServerMessage.getMessageID(), largeServerMessage);
+               largeMessagesToDelete.put(largeServerMessage.toMessage().getMessageID(), largeServerMessage);
                return;
             }
          } finally {
@@ -500,7 +484,7 @@ public class JournalStorageManager extends AbstractJournalStorageManager {
                readLock();
                try {
                   if (replicator != null) {
-                     replicator.largeMessageDelete(largeServerMessage.getMessageID(), JournalStorageManager.this);
+                     replicator.largeMessageDelete(largeServerMessage.toMessage().getMessageID(), JournalStorageManager.this);
                   }
                   file.delete();
 
@@ -510,7 +494,7 @@ public class JournalStorageManager extends AbstractJournalStorageManager {
                   readUnLock();
                }
             } catch (Exception e) {
-               ActiveMQServerLogger.LOGGER.journalErrorDeletingMessage(e, largeServerMessage.getMessageID());
+               ActiveMQServerLogger.LOGGER.journalErrorDeletingMessage(e, largeServerMessage.toMessage().getMessageID());
             }
          }
 
@@ -575,7 +559,7 @@ public class JournalStorageManager extends AbstractJournalStorageManager {
 
          if (largeMessage.isDurable()) {
             // We store a marker on the journal that the large file is pending
-            long pendingRecordID = storePendingLargeMessage(id, LargeServerMessage.NO_PENDING_ID);
+            long pendingRecordID = storePendingLargeMessage(id);
 
             largeMessage.setPendingRecordID(pendingRecordID);
          }
@@ -831,6 +815,7 @@ public class JournalStorageManager extends AbstractJournalStorageManager {
       }
    }
 
+   @Override
    public final void addBytesToLargeMessage(final SequentialFile file,
                                             final long messageId,
                                             final ActiveMQBuffer bytes) throws Exception {
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/LargeBody.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/LargeBody.java
new file mode 100644
index 0000000..9d17f6e
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/LargeBody.java
@@ -0,0 +1,450 @@
+/*
+ * 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.activemq.artemis.core.persistence.impl.journal;
+
+import java.nio.ByteBuffer;
+
+import io.netty.buffer.Unpooled;
+import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
+import org.apache.activemq.artemis.api.core.ActiveMQException;
+import org.apache.activemq.artemis.api.core.ActiveMQExceptionType;
+import org.apache.activemq.artemis.api.core.ActiveMQIOErrorException;
+import org.apache.activemq.artemis.api.core.ActiveMQInternalErrorException;
+import org.apache.activemq.artemis.api.core.Message;
+import org.apache.activemq.artemis.core.buffers.impl.ChannelBufferWrapper;
+import org.apache.activemq.artemis.core.io.SequentialFile;
+import org.apache.activemq.artemis.core.message.LargeBodyReader;
+import org.apache.activemq.artemis.core.persistence.StorageManager;
+import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
+import org.apache.activemq.artemis.core.server.LargeServerMessage;
+import org.jboss.logging.Logger;
+
+public class LargeBody {
+
+   private static final Logger logger = Logger.getLogger(LargeBody.class);
+
+   private long bodySize = -1;
+
+   long NO_PENDING_ID = -1;
+
+   private long pendingRecordID = NO_PENDING_ID;
+
+   final StorageManager storageManager;
+
+   private long messageID = -1;
+
+   private LargeServerMessage message;
+
+   private boolean paged;
+
+   // This is to be used only for appending
+   private SequentialFile file;
+
+   public LargeBody(LargeServerMessage message, StorageManager storageManager) {
+      this.storageManager = storageManager;
+      this.message = message;
+   }
+
+   public LargeBody(LargeServerMessage message, StorageManager storageManager, SequentialFile file) {
+      this(message, storageManager);
+      this.file = file;
+   }
+
+   public StorageManager getStorageManager() {
+      return storageManager;
+   }
+
+   public ByteBuffer map() throws Exception {
+      ensureFileExists(true);
+      if (!file.isOpen()) {
+         file.open();
+      }
+      return file.map(0, file.size());
+   }
+
+   public LargeBody(long messageID, JournalStorageManager storageManager) {
+      this(null, storageManager);
+      this.messageID = messageID;
+   }
+
+   public void setMessage(LargeServerMessage message) {
+      this.message = message;
+
+   }
+
+   public void setPaged() {
+      this.paged = true;
+   }
+
+   public boolean isPaged() {
+      return paged;
+   }
+
+   public void clearFile() {
+      if (file != null && file.isOpen()) {
+         try {
+            file.close();
+         } catch (Exception e) {
+            // this shouldn't happen anyways, this close call is here just in case it ever happened
+            logger.warn(e.getMessage(), e);
+         }
+      }
+
+      file = null;
+   }
+
+   public synchronized void deleteFile() {
+      try {
+         validateFile();
+         releaseResources(false);
+         storageManager.deleteLargeMessageBody(message);
+      } catch (Exception e) {
+         storageManager.criticalError(e);
+      }
+   }
+
+   public long getMessageID() {
+      if (message == null) {
+         return messageID;
+      } else {
+         return message.getMessageID();
+      }
+   }
+
+   public synchronized void addBytes(final byte[] bytes) throws Exception {
+      validateFile();
+
+      if (!file.isOpen()) {
+         file.open();
+      }
+
+      storageManager.addBytesToLargeMessage(file, getMessageID(), bytes);
+
+      bodySize += bytes.length;
+   }
+
+   public synchronized void addBytes(final ActiveMQBuffer bytes) throws Exception {
+      validateFile();
+
+      if (!file.isOpen()) {
+         file.open();
+      }
+
+      final int readableBytes = bytes.readableBytes();
+
+      storageManager.addBytesToLargeMessage(file, getMessageID(), bytes);
+
+      bodySize += readableBytes;
+   }
+
+   public synchronized void validateFile() throws ActiveMQException {
+      this.ensureFileExists(true);
+   }
+
+   public synchronized void ensureFileExists(boolean toOpen) throws ActiveMQException {
+      try {
+         if (file == null) {
+            if (getMessageID() <= 0) {
+               throw new RuntimeException("MessageID not set on LargeMessage");
+            }
+
+            file = createFile();
+
+            if (toOpen) {
+               openFile();
+            }
+
+            bodySize = file.size();
+         }
+      } catch (Exception e) {
+         throw new ActiveMQInternalErrorException(e.getMessage(), e);
+      }
+   }
+
+   /**
+    * This will return the bodySize without trying to open the file, just returning what's currently stored
+    */
+   public long getStoredBodySize() {
+      return bodySize;
+   }
+
+   public void setBodySize(long size) {
+      this.bodySize = size;
+   }
+
+   public long getBodySize() throws ActiveMQException {
+
+      try {
+         if (bodySize <= 0) {
+            if (file != null) {
+               bodySize = file.size();
+            } else {
+               SequentialFile tmpFile = createFile();
+               bodySize = tmpFile.size();
+               tmpFile.close(false);
+            }
+         }
+         return bodySize;
+      } catch (Exception e) {
+         ActiveMQIOErrorException errorException = new ActiveMQIOErrorException();
+         errorException.initCause(e);
+         throw errorException;
+      }
+   }
+
+   public LargeBodyReader getLargeBodyReader() {
+      return new LargeBodyReaderImpl();
+   }
+
+   /**
+    * This will return its own File useful for reading the file on the large message while delivering, browsing.. etc
+    */
+   public SequentialFile getReadingFile() throws ActiveMQException {
+      ensureFileExists(false);
+      return file.cloneFile();
+   }
+
+   /** Meant for test-ability, be careful if you decide to use it.
+    *  and in case you use it for a real reason, please change the documentation here.
+    * @param file
+    */
+   public void replaceFile(SequentialFile file) {
+      this.file = file;
+   }
+
+   public SequentialFile getAppendFile() throws ActiveMQException {
+      validateFile();
+      return file;
+   }
+
+   public void checkDelete() {
+      if (message.toMessage().getRefCount() <= 0 && message.toMessage().getUsage() <= 0 && message.toMessage().getDurableCount() <= 0) {
+         if (logger.isTraceEnabled()) {
+            try {
+               logger.trace("Deleting file " + getAppendFile() + " as the usage was complete");
+            } catch (Exception e) {
+               // this is only after a trace, no need to do any special logging handling here
+               logger.warn(e.getMessage(), e);
+            }
+         }
+
+         deleteFile();
+      }
+   }
+
+   public void referenceOriginalMessage(final LargeBody original) {
+      if (original.isPaged()) {
+         this.setPaged();
+      }
+
+      if (this.paged) {
+         message.toMessage().removeAnnotation(Message.HDR_ORIG_MESSAGE_ID);
+      }
+   }
+
+   public ActiveMQBuffer getReadOnlyBodyBuffer() {
+      try {
+         validateFile();
+         file.open();
+         int fileSize = (int) file.size();
+         ByteBuffer buffer = ByteBuffer.allocate(fileSize);
+         file.read(buffer);
+         return new ChannelBufferWrapper(Unpooled.wrappedBuffer(buffer));
+      } catch (Exception e) {
+         throw new RuntimeException(e);
+      } finally {
+         try {
+            file.close(false);
+         } catch (Exception ignored) {
+         }
+      }
+   }
+
+   public int getBodyBufferSize() {
+      final boolean closeFile = file == null || !file.isOpen();
+      try {
+         openFile();
+         final long fileSize = file.size();
+         int fileSizeAsInt = (int) fileSize;
+         if (fileSizeAsInt < 0) {
+            logger.warnf("suspicious large message file size of %d bytes for %s, will use %d instead.", fileSize, file.getFileName(), Integer.MAX_VALUE);
+            fileSizeAsInt = Integer.MAX_VALUE;
+         }
+         return fileSizeAsInt;
+      } catch (Exception e) {
+         throw new RuntimeException(e);
+      } finally {
+         if (closeFile) {
+            try {
+               file.close(false);
+            } catch (Exception ignored) {
+            }
+         }
+      }
+   }
+
+   public synchronized void releaseResources(boolean sync) {
+      if (file != null && file.isOpen()) {
+         try {
+            if (sync) {
+               file.sync();
+            }
+            file.close(false);
+         } catch (Exception e) {
+            ActiveMQServerLogger.LOGGER.largeMessageErrorReleasingResources(e);
+         }
+      }
+   }
+
+   public void copyInto(LargeServerMessage newMessage) throws Exception {
+      //clone a SequentialFile to avoid concurrent access
+      SequentialFile cloneFile = getReadingFile();
+
+      try {
+         byte[] bufferBytes = new byte[100 * 1024];
+
+         ByteBuffer buffer = ByteBuffer.wrap(bufferBytes);
+
+         if (!cloneFile.isOpen()) {
+            cloneFile.open();
+         }
+
+         cloneFile.position(0);
+
+         for (; ; ) {
+            // The buffer is reused...
+            // We need to make sure we clear the limits and the buffer before reusing it
+            buffer.clear();
+            int bytesRead = cloneFile.read(buffer);
+
+            byte[] bufferToWrite;
+            if (bytesRead <= 0) {
+               break;
+            } else if (bytesRead == bufferBytes.length && this.storageManager instanceof JournalStorageManager && !((JournalStorageManager) this.storageManager).isReplicated()) {
+               // ARTEMIS-1220: We cannot reuse the same buffer if it's replicated
+               // otherwise there could be another thread still using the buffer on a
+               // replication.
+               bufferToWrite = bufferBytes;
+            } else {
+               bufferToWrite = new byte[bytesRead];
+               System.arraycopy(bufferBytes, 0, bufferToWrite, 0, bytesRead);
+            }
+
+            newMessage.addBytes(bufferToWrite);
+
+            if (bytesRead < bufferBytes.length) {
+               break;
+            }
+         }
+      } finally {
+         cloneFile.close();
+      }
+   }
+
+   public SequentialFile createFile() {
+      return storageManager.createFileForLargeMessage(getMessageID(), message.toMessage().isDurable());
+   }
+
+   protected void openFile() throws Exception {
+      if (file == null) {
+         validateFile();
+      } else if (!file.isOpen()) {
+         file.open();
+      }
+   }
+
+   class LargeBodyReaderImpl implements LargeBodyReader {
+
+      private SequentialFile cFile;
+
+      @Override
+      public void open() throws ActiveMQException {
+         try {
+            if (cFile != null && cFile.isOpen()) {
+               cFile.close(false);
+            }
+            cFile = getReadingFile();
+            cFile.open();
+         } catch (Exception e) {
+            throw new ActiveMQException(ActiveMQExceptionType.INTERNAL_ERROR, e.getMessage(), e);
+         }
+      }
+
+      @Override
+      public void position(long position) throws ActiveMQException {
+         try {
+            cFile.position(position);
+         } catch (Exception e) {
+            throw new ActiveMQException(ActiveMQExceptionType.INTERNAL_ERROR, e.getMessage(), e);
+         }
+      }
+
+      @Override
+      public long position() {
+         return cFile.position();
+      }
+
+      @Override
+      public void close() throws ActiveMQException {
+         try {
+            if (cFile != null) {
+               cFile.close(false);
+               cFile = null;
+            }
+         } catch (Exception e) {
+            throw new ActiveMQInternalErrorException(e.getMessage(), e);
+         }
+      }
+
+      @Override
+      public int readInto(final ByteBuffer bufferRead) throws ActiveMQException {
+         try {
+            return cFile.read(bufferRead);
+         } catch (Exception e) {
+            throw new ActiveMQInternalErrorException(e.getMessage(), e);
+         }
+      }
+
+      /* (non-Javadoc)
+       * @see org.apache.activemq.artemis.core.message.LargeBodyEncoder#getSize()
+       */
+      @Override
+      public long getSize() throws ActiveMQException {
+         return getBodySize();
+      }
+   }
+
+   public boolean hasPendingRecord() {
+      return pendingRecordID != NO_PENDING_ID;
+   }
+
+   public void clearPendingRecordID() {
+      setPendingRecordID(NO_PENDING_ID);
+   }
+
+
+   public long getPendingRecordID() {
+      return this.pendingRecordID;
+   }
+
+   public void setPendingRecordID(long pendingRecordID) {
+      this.pendingRecordID = pendingRecordID;
+   }
+
+
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/LargeMessageTXFailureCallback.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/LargeMessageTXFailureCallback.java
index 0a92bd2..4f62f1b 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/LargeMessageTXFailureCallback.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/LargeMessageTXFailureCallback.java
@@ -48,7 +48,7 @@ public class LargeMessageTXFailureCallback implements TransactionFailureCallback
 
             try {
                LargeServerMessage serverMessage = journalStorageManager.parseLargeMessage(buff);
-               serverMessage.decrementDelayDeletionCount();
+               serverMessage.toMessage().usageDown();
             } catch (Exception e) {
                ActiveMQServerLogger.LOGGER.journalError(e);
             }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/LargeServerMessageImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/LargeServerMessageImpl.java
index 85cb24c..3287974 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/LargeServerMessageImpl.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/LargeServerMessageImpl.java
@@ -16,36 +16,34 @@
  */
 package org.apache.activemq.artemis.core.persistence.impl.journal;
 
-import java.nio.ByteBuffer;
-import java.util.concurrent.atomic.AtomicInteger;
-
 import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
 import org.apache.activemq.artemis.api.core.ActiveMQException;
-import org.apache.activemq.artemis.api.core.ActiveMQExceptionType;
-import org.apache.activemq.artemis.api.core.ActiveMQIOErrorException;
-import org.apache.activemq.artemis.api.core.ActiveMQInternalErrorException;
 import org.apache.activemq.artemis.api.core.ICoreMessage;
 import org.apache.activemq.artemis.api.core.Message;
-import org.apache.activemq.artemis.api.core.RefCountMessageListener;
-import org.apache.activemq.artemis.core.buffers.impl.ChannelBufferWrapper;
 import org.apache.activemq.artemis.core.io.SequentialFile;
-import org.apache.activemq.artemis.core.message.LargeBodyEncoder;
+import org.apache.activemq.artemis.core.message.LargeBodyReader;
 import org.apache.activemq.artemis.core.message.impl.CoreMessage;
 import org.apache.activemq.artemis.core.persistence.StorageManager;
 import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
+import org.apache.activemq.artemis.core.server.CoreLargeServerMessage;
 import org.apache.activemq.artemis.core.server.LargeServerMessage;
 import org.apache.activemq.artemis.utils.DataConstants;
 import org.apache.activemq.artemis.utils.collections.TypedProperties;
 import org.jboss.logging.Logger;
 
-import io.netty.buffer.Unpooled;
+public final class LargeServerMessageImpl extends CoreMessage implements CoreLargeServerMessage {
 
-public final class LargeServerMessageImpl extends CoreMessage implements LargeServerMessage {
+   @Override
+   public Message toMessage() {
+      return this;
+   }
 
    // When a message is stored on the journal, it will contain some header and trail on the journal
    // we need to take that into consideration if that would fit the Journal TimedBuffer.
    private static final int ESTIMATE_RECORD_TRAIL = 512;
 
+   private final LargeBody largeBody;
+
    /** This will check if a regular message needs to be converted as large message */
    public static Message checkLargeMessage(Message message, StorageManager storageManager) throws Exception {
       if (message.isLargeMessage()) {
@@ -59,6 +57,11 @@ public final class LargeServerMessageImpl extends CoreMessage implements LargeSe
       }
    }
 
+   @Override
+   public void finishParse() throws Exception {
+
+   }
+
    private static Message asLargeMessage(Message message, StorageManager storageManager) throws Exception {
       ICoreMessage coreMessage = message.toCore();
       LargeServerMessage lsm = storageManager.createLargeMessage(storageManager.generateID(), coreMessage);
@@ -66,8 +69,8 @@ public final class LargeServerMessageImpl extends CoreMessage implements LargeSe
       final int readableBytes = buffer.readableBytes();
       lsm.addBytes(buffer);
       lsm.releaseResources(true);
-      lsm.putLongProperty(Message.HDR_LARGE_BODY_SIZE, readableBytes);
-      return lsm;
+      lsm.toMessage().putLongProperty(Message.HDR_LARGE_BODY_SIZE, readableBytes);
+      return lsm.toMessage();
    }
 
    // Constants -----------------------------------------------------
@@ -75,26 +78,20 @@ public final class LargeServerMessageImpl extends CoreMessage implements LargeSe
 
    // Attributes ----------------------------------------------------
 
-   private final JournalStorageManager storageManager;
-
-   private long pendingRecordID = NO_PENDING_ID;
-
-   private boolean paged;
-
-   // We should only use the NIO implementation on the Journal
-   private SequentialFile file;
-
-   private long bodySize = -1;
-
-   private final AtomicInteger delayDeletionCount = new AtomicInteger(0);
+   private final StorageManager storageManager;
 
    // We cache this
    private volatile int memoryEstimate = -1;
 
-   public LargeServerMessageImpl(final JournalStorageManager storageManager) {
+   public LargeServerMessageImpl(final StorageManager storageManager) {
+      largeBody = new LargeBody(this, storageManager);
       this.storageManager = storageManager;
    }
 
+   public long getBodySize() throws ActiveMQException {
+      return largeBody.getBodySize();
+   }
+
    /**
     * Copy constructor
     *
@@ -102,17 +99,28 @@ public final class LargeServerMessageImpl extends CoreMessage implements LargeSe
     * @param copy
     * @param fileCopy
     */
-   private LargeServerMessageImpl(final LargeServerMessageImpl copy,
+   public LargeServerMessageImpl(final LargeServerMessageImpl copy,
                                   TypedProperties properties,
                                   final SequentialFile fileCopy,
                                   final long newID) {
       super(copy, properties);
       storageManager = copy.storageManager;
-      file = fileCopy;
-      bodySize = copy.bodySize;
+      largeBody = new LargeBody(this, storageManager, fileCopy);
+      largeBody.setBodySize(copy.largeBody.getStoredBodySize());
       setMessageID(newID);
    }
 
+   public LargeServerMessageImpl(byte type,
+                                  long id,
+                                  StorageManager storageManager,
+                                  final SequentialFile fileCopy) {
+      super();
+      this.storageManager = storageManager;
+      setMessageID(id);
+      setType(type);
+      largeBody = new LargeBody(this, storageManager, fileCopy);
+   }
+
    private static String toDate(long timestamp) {
       if (timestamp == 0) {
          return "0";
@@ -123,13 +131,28 @@ public final class LargeServerMessageImpl extends CoreMessage implements LargeSe
    }
 
    @Override
+   public StorageManager getStorageManager() {
+      return storageManager;
+   }
+
+   @Override
    public boolean isServerMessage() {
       return true;
    }
 
    @Override
    public long getPendingRecordID() {
-      return this.pendingRecordID;
+      return largeBody.getPendingRecordID();
+   }
+
+   @Override
+   public void clearPendingRecordID() {
+      largeBody.clearPendingRecordID();
+   }
+
+   @Override
+   public boolean hasPendingRecord() {
+      return largeBody.hasPendingRecord();
    }
 
    /**
@@ -137,40 +160,22 @@ public final class LargeServerMessageImpl extends CoreMessage implements LargeSe
     */
    @Override
    public void setPendingRecordID(long pendingRecordID) {
-      this.pendingRecordID = pendingRecordID;
+      largeBody.setPendingRecordID(pendingRecordID);
    }
 
    @Override
    public void setPaged() {
-      paged = true;
+      largeBody.setPaged();
    }
 
    @Override
    public synchronized void addBytes(final byte[] bytes) throws Exception {
-      validateFile();
-
-      if (!file.isOpen()) {
-         file.open();
-      }
-
-      storageManager.addBytesToLargeMessage(file, getMessageID(), bytes);
-
-      bodySize += bytes.length;
+      largeBody.addBytes(bytes);
    }
 
    @Override
    public synchronized void addBytes(final ActiveMQBuffer bytes) throws Exception {
-      validateFile();
-
-      if (!file.isOpen()) {
-         file.open();
-      }
-
-      final int readableBytes = bytes.readableBytes();
-
-      storageManager.addBytesToLargeMessage(file, getMessageID(), bytes);
-
-      bodySize += readableBytes;
+      largeBody.addBytes(bytes);
    }
 
    @Override
@@ -183,79 +188,19 @@ public final class LargeServerMessageImpl extends CoreMessage implements LargeSe
    }
 
    public void decode(final ActiveMQBuffer buffer1) {
-      file = null;
-
+      largeBody.clearFile();
       super.decodeHeadersAndProperties(buffer1.byteBuf());
    }
 
    @Override
-   public synchronized void incrementDelayDeletionCount() {
-      delayDeletionCount.incrementAndGet();
-      try {
-         if (paged) {
-            RefCountMessageListener tmpContext = super.getContext();
-            setContext(null);
-            incrementRefCount();
-            setContext(tmpContext);
-         } else {
-            incrementRefCount();
-         }
-
-      } catch (Exception e) {
-         ActiveMQServerLogger.LOGGER.errorIncrementDelayDeletionCount(e);
-      }
-   }
-
-   @Override
-   public synchronized void decrementDelayDeletionCount() throws Exception {
-      int count = delayDeletionCount.decrementAndGet();
-
-      decrementRefCount();
-
-      if (count == 0) {
-         checkDelete();
-      }
+   public LargeBodyReader getLargeBodyReader() {
+      return largeBody.getLargeBodyReader();
    }
 
-   @Override
-   public LargeBodyEncoder getBodyEncoder() throws ActiveMQException {
-      validateFile();
-      return new DecodingContext();
-   }
-
-   private void checkDelete() throws Exception {
-      if (getRefCount() <= 0) {
-         if (logger.isTraceEnabled()) {
-            logger.trace("Deleting file " + file + " as the usage was complete");
-         }
-
-         try {
-            deleteFile();
-         } catch (Exception e) {
-            ActiveMQServerLogger.LOGGER.error(e.getMessage(), e);
-         }
-      }
-   }
 
    @Override
-   public synchronized int decrementRefCount() throws Exception {
-      int currentRefCount;
-      if (paged) {
-         RefCountMessageListener tmpContext = super.getContext();
-         setContext(null);
-         currentRefCount = super.decrementRefCount();
-         setContext(tmpContext);
-      } else {
-         currentRefCount = super.decrementRefCount();
-      }
-
-      // We use <= as this could be used by load.
-      // because of a failure, no references were loaded, so we have 0... and we still need to delete the associated
-      // files
-      if (delayDeletionCount.get() <= 0) {
-         checkDelete();
-      }
-      return currentRefCount;
+   protected void releaseComplete() {
+      largeBody.deleteFile();
    }
 
    // Even though not recommended, in certain instances
@@ -263,46 +208,13 @@ public final class LargeServerMessageImpl extends CoreMessage implements LargeSe
    // in a way you can convert
    @Override
    public ActiveMQBuffer getReadOnlyBodyBuffer() {
-      try {
-         validateFile();
-         file.open();
-         int fileSize = (int) file.size();
-         ByteBuffer buffer = ByteBuffer.allocate(fileSize);
-         file.read(buffer);
-         return new ChannelBufferWrapper(Unpooled.wrappedBuffer(buffer));
-      } catch (Exception e) {
-         throw new RuntimeException(e);
-      } finally {
-         try {
-            file.close(false);
-         } catch (Exception ignored) {
-         }
-      }
+
+      return largeBody.getReadOnlyBodyBuffer();
    }
 
    @Override
    public int getBodyBufferSize() {
-      final boolean closeFile = file == null || !file.isOpen();
-      try {
-         openFile();
-         final long fileSize = file.size();
-         int fileSizeAsInt = (int) fileSize;
-         if (fileSizeAsInt < 0) {
-            logger.warnf("suspicious large message file size of %d bytes for %s, will use %d instead.",
-                         fileSize, file.getFileName(), Integer.MAX_VALUE);
-            fileSizeAsInt = Integer.MAX_VALUE;
-         }
-         return fileSizeAsInt;
-      } catch (Exception e) {
-         throw new RuntimeException(e);
-      } finally {
-         if (closeFile) {
-            try {
-               file.close(false);
-            } catch (Exception ignored) {
-            }
-         }
-      }
+      return largeBody.getBodyBufferSize();
    }
 
    @Override
@@ -312,9 +224,7 @@ public final class LargeServerMessageImpl extends CoreMessage implements LargeSe
 
    @Override
    public synchronized void deleteFile() throws Exception {
-      validateFile();
-      releaseResources(false);
-      storageManager.deleteLargeMessageFile(this);
+      largeBody.deleteFile();
    }
 
    @Override
@@ -329,16 +239,7 @@ public final class LargeServerMessageImpl extends CoreMessage implements LargeSe
 
    @Override
    public synchronized void releaseResources(boolean sync) {
-      if (file != null && file.isOpen()) {
-         try {
-            if (sync) {
-               file.sync();
-            }
-            file.close(false);
-         } catch (Exception e) {
-            ActiveMQServerLogger.LOGGER.largeMessageErrorReleasingResources(e);
-         }
-      }
+      largeBody.releaseResources(sync);
    }
 
    @Override
@@ -347,11 +248,7 @@ public final class LargeServerMessageImpl extends CoreMessage implements LargeSe
       super.referenceOriginalMessage(original, originalQueue);
 
       if (original instanceof LargeServerMessageImpl) {
-         LargeServerMessageImpl otherLM = (LargeServerMessageImpl) original;
-         this.paged = otherLM.paged;
-         if (this.paged) {
-            this.removeAnnotation(Message.HDR_ORIG_MESSAGE_ID);
-         }
+         this.largeBody.referenceOriginalMessage(((LargeServerMessageImpl) original).largeBody);
       }
    }
 
@@ -364,58 +261,18 @@ public final class LargeServerMessageImpl extends CoreMessage implements LargeSe
    }
 
    @Override
+   public LargeBody getLargeBody() {
+      return largeBody;
+   }
+
+   @Override
    public Message copy(final long newID) {
       try {
          LargeServerMessage newMessage = storageManager.createLargeMessage(newID, this);
-
-         //clone a SequentialFile to avoid concurrent access
-         ensureFileExists(false);
-         SequentialFile cloneFile = file.cloneFile();
-
-         try {
-            byte[] bufferBytes = new byte[100 * 1024];
-
-            ByteBuffer buffer = ByteBuffer.wrap(bufferBytes);
-
-            if (!cloneFile.isOpen()) {
-               cloneFile.open();
-            }
-
-            cloneFile.position(0);
-
-            for (;;) {
-               // The buffer is reused...
-               // We need to make sure we clear the limits and the buffer before reusing it
-               buffer.clear();
-               int bytesRead = cloneFile.read(buffer);
-
-               byte[] bufferToWrite;
-               if (bytesRead <= 0) {
-                  break;
-               } else if (bytesRead == bufferBytes.length && !this.storageManager.isReplicated()) {
-                  // ARTEMIS-1220: We cannot reuse the same buffer if it's replicated
-                  // otherwise there could be another thread still using the buffer on a
-                  // replication.
-                  bufferToWrite = bufferBytes;
-               } else {
-                  bufferToWrite = new byte[bytesRead];
-                  System.arraycopy(bufferBytes, 0, bufferToWrite, 0, bytesRead);
-               }
-
-               newMessage.addBytes(bufferToWrite);
-
-               if (bytesRead < bufferBytes.length) {
-                  break;
-               }
-            }
-         } finally {
-            if (!file.isOpen()) {
-               newMessage.getFile().close();
-            }
-            cloneFile.close();
-         }
-
-         return newMessage;
+         largeBody.copyInto(newMessage);
+         newMessage.finishParse();
+         newMessage.releaseResources(true);
+         return newMessage.toMessage();
 
       } catch (Exception e) {
          ActiveMQServerLogger.LOGGER.lareMessageErrorCopying(e, this);
@@ -424,29 +281,8 @@ public final class LargeServerMessageImpl extends CoreMessage implements LargeSe
    }
 
    @Override
-   public SequentialFile getFile() throws ActiveMQException {
-      validateFile();
-      return file;
-   }
-
-   private long getBodySize() throws ActiveMQException {
-
-      try {
-         if (bodySize < 0) {
-            if (file != null) {
-               bodySize = file.size();
-            } else {
-               SequentialFile tmpFile = createFile();
-               bodySize = tmpFile.size();
-               tmpFile.close(false);
-            }
-         }
-         return bodySize;
-      } catch (Exception e) {
-         ActiveMQIOErrorException errorException = new ActiveMQIOErrorException();
-         errorException.initCause(e);
-         throw errorException;
-      }
+   public SequentialFile getAppendFile() throws ActiveMQException {
+      return largeBody.getAppendFile();
    }
 
    @Override
@@ -473,93 +309,8 @@ public final class LargeServerMessageImpl extends CoreMessage implements LargeSe
    }
 
    public synchronized void ensureFileExists(boolean toOpen) throws ActiveMQException {
-      try {
-         if (file == null) {
-            if (messageID <= 0) {
-               throw new RuntimeException("MessageID not set on LargeMessage");
-            }
-
-            file = createFile();
-
-            if (toOpen) {
-               openFile();
-            }
-
-            bodySize = file.size();
-         }
-      } catch (Exception e) {
-         // TODO: There is an IO_ERROR on trunk now, this should be used here instead
-         throw new ActiveMQInternalErrorException(e.getMessage(), e);
-      }
+      largeBody.ensureFileExists(toOpen);
    }
 
-   /**
-    *
-    */
-   protected SequentialFile createFile() {
-      return storageManager.createFileForLargeMessage(getMessageID(), durable);
-   }
-
-   protected void openFile() throws Exception {
-      if (file == null) {
-         validateFile();
-      } else if (!file.isOpen()) {
-         file.open();
-      }
-   }
-
-   protected void closeFile() throws Exception {
-      if (file != null && file.isOpen()) {
-         file.close();
-      }
-   }
-
-   // Inner classes -------------------------------------------------
-
-   class DecodingContext implements LargeBodyEncoder {
-
-      private SequentialFile cFile;
-
-      @Override
-      public void open() throws ActiveMQException {
-         try {
-            if (cFile != null && cFile.isOpen()) {
-               cFile.close(false);
-            }
-            cFile = file.cloneFile();
-            cFile.open();
-         } catch (Exception e) {
-            throw new ActiveMQException(ActiveMQExceptionType.INTERNAL_ERROR, e.getMessage(), e);
-         }
-      }
-
-      @Override
-      public void close() throws ActiveMQException {
-         try {
-            if (cFile != null) {
-               cFile.close(false);
-            }
-         } catch (Exception e) {
-            throw new ActiveMQInternalErrorException(e.getMessage(), e);
-         }
-      }
-
-      @Override
-      public int encode(final ByteBuffer bufferRead) throws ActiveMQException {
-         try {
-            return cFile.read(bufferRead);
-         } catch (Exception e) {
-            throw new ActiveMQInternalErrorException(e.getMessage(), e);
-         }
-      }
-
-      /* (non-Javadoc)
-       * @see org.apache.activemq.artemis.core.message.LargeBodyEncoder#getLargeBodySize()
-       */
-      @Override
-      public long getLargeBodySize() throws ActiveMQException {
-         return getBodySize();
-      }
-   }
 
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/LargeServerMessageInSync.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/LargeServerMessageInSync.java
index 3aeae45..62d5365 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/LargeServerMessageInSync.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/LargeServerMessageInSync.java
@@ -50,7 +50,7 @@ public final class LargeServerMessageInSync implements ReplicatedLargeMessage {
    public synchronized void joinSyncedData(ByteBuffer buffer) throws Exception {
       if (deleted)
          return;
-      SequentialFile mainSeqFile = mainLM.getFile();
+      SequentialFile mainSeqFile = mainLM.getAppendFile();
       if (!mainSeqFile.isOpen()) {
          mainSeqFile.open();
       }
@@ -80,19 +80,19 @@ public final class LargeServerMessageInSync implements ReplicatedLargeMessage {
    }
 
    public SequentialFile getSyncFile() throws ActiveMQException {
-      return mainLM.getFile();
+      return mainLM.getAppendFile();
    }
 
    @Override
    public Message setDurable(boolean durable) {
       mainLM.setDurable(durable);
-      return mainLM;
+      return mainLM.toMessage();
    }
 
    @Override
    public synchronized Message setMessageID(long id) {
       mainLM.setMessageID(id);
-      return mainLM;
+      return mainLM.toMessage();
    }
 
    @Override
@@ -159,6 +159,16 @@ public final class LargeServerMessageInSync implements ReplicatedLargeMessage {
    }
 
    @Override
+   public void clearPendingRecordID() {
+      mainLM.clearPendingRecordID();
+   }
+
+   @Override
+   public boolean hasPendingRecord() {
+      return mainLM.hasPendingRecord();
+   }
+
+   @Override
    public void setPendingRecordID(long pendingRecordID) {
       mainLM.setPendingRecordID(pendingRecordID);
    }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/codec/DeleteEncoding.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/codec/DeleteEncoding.java
index 3001edf..565d6fd 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/codec/DeleteEncoding.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/codec/DeleteEncoding.java
@@ -40,7 +40,7 @@ public class DeleteEncoding implements EncodingSupport {
    }
 
    /* (non-Javadoc)
-    * @see org.apache.activemq.artemis.core.journal.EncodingSupport#encode(org.apache.activemq.artemis.api.core.ActiveMQBuffer)
+    * @see org.apache.activemq.artemis.core.journal.EncodingSupport#readInto(org.apache.activemq.artemis.api.core.ActiveMQBuffer)
     */
    @Override
    public void encode(ActiveMQBuffer buffer) {
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/codec/LargeMessagePersister.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/codec/LargeMessagePersister.java
index 4e02f68..1e9b3d4 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/codec/LargeMessagePersister.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/codec/LargeMessagePersister.java
@@ -19,13 +19,22 @@ package org.apache.activemq.artemis.core.persistence.impl.journal.codec;
 
 import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
 import org.apache.activemq.artemis.core.message.impl.CoreMessage;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
 import org.apache.activemq.artemis.core.persistence.Persister;
 import org.apache.activemq.artemis.core.server.LargeServerMessage;
 
-public class LargeMessagePersister implements Persister<LargeServerMessage, LargeServerMessage> {
+import static org.apache.activemq.artemis.core.persistence.PersisterIDs.CoreLargeMessagePersister_ID;
+
+public class LargeMessagePersister implements Persister<LargeServerMessage> {
+
+   public static final byte ID = CoreLargeMessagePersister_ID;
 
    private static final LargeMessagePersister theInstance = new LargeMessagePersister();
 
+   @Override
+   public byte getID() {
+      return ID;
+   }
 
    public static LargeMessagePersister getInstance() {
       return theInstance;
@@ -38,7 +47,7 @@ public class LargeMessagePersister implements Persister<LargeServerMessage, Larg
     * @see org.apache.activemq.artemis.core.journal.EncodingSupport#decode(org.apache.activemq.artemis.spi.core.remoting.ActiveMQBuffer)
     */
    @Override
-   public LargeServerMessage decode(final ActiveMQBuffer buffer, LargeServerMessage message) {
+   public LargeServerMessage decode(final ActiveMQBuffer buffer, LargeServerMessage message, CoreMessageObjectPools objectPools) {
       ((CoreMessage)message).decodeHeadersAndProperties(buffer.byteBuf());
       return message;
    }
@@ -56,7 +65,7 @@ public class LargeMessagePersister implements Persister<LargeServerMessage, Larg
     */
    @Override
    public int getEncodeSize(LargeServerMessage message) {
-      return message.getEncodeSize();
+      return message.toMessage().getEncodeSize();
    }
 
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/codec/PendingLargeMessageEncoding.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/codec/PendingLargeMessageEncoding.java
index b3be308..3da1363 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/codec/PendingLargeMessageEncoding.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/codec/PendingLargeMessageEncoding.java
@@ -40,7 +40,7 @@ public class PendingLargeMessageEncoding implements EncodingSupport {
    }
 
    /* (non-Javadoc)
-    * @see org.apache.activemq.artemis.core.journal.EncodingSupport#encode(org.apache.activemq.artemis.spi.core.remoting.ActiveMQBuffer)
+    * @see org.apache.activemq.artemis.core.journal.EncodingSupport#readInto(org.apache.activemq.artemis.spi.core.remoting.ActiveMQBuffer)
     */
    @Override
    public void encode(final ActiveMQBuffer buffer) {
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/nullpm/NullStorageLargeServerMessage.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/nullpm/NullStorageLargeServerMessage.java
index 13c6ef5..76165e8 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/nullpm/NullStorageLargeServerMessage.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/nullpm/NullStorageLargeServerMessage.java
@@ -22,9 +22,11 @@ import org.apache.activemq.artemis.api.core.Message;
 import org.apache.activemq.artemis.core.buffers.impl.ChannelBufferWrapper;
 import org.apache.activemq.artemis.core.io.SequentialFile;
 import org.apache.activemq.artemis.core.message.impl.CoreMessage;
-import org.apache.activemq.artemis.core.server.LargeServerMessage;
+import org.apache.activemq.artemis.core.persistence.StorageManager;
+import org.apache.activemq.artemis.core.persistence.impl.journal.LargeBody;
+import org.apache.activemq.artemis.core.server.CoreLargeServerMessage;
 
-class NullStorageLargeServerMessage extends CoreMessage implements LargeServerMessage {
+class NullStorageLargeServerMessage extends CoreMessage implements CoreLargeServerMessage {
 
    NullStorageLargeServerMessage() {
       super();
@@ -39,6 +41,21 @@ class NullStorageLargeServerMessage extends CoreMessage implements LargeServerMe
    }
 
    @Override
+   public LargeBody getLargeBody() {
+      return null;
+   }
+
+   @Override
+   public StorageManager getStorageManager() {
+      return null;
+   }
+
+   @Override
+   public Message toMessage() {
+      return this;
+   }
+
+   @Override
    public synchronized void addBytes(final byte[] bytes) {
       if (buffer == null) {
          buffer = Unpooled.buffer(bytes.length);
@@ -49,6 +66,11 @@ class NullStorageLargeServerMessage extends CoreMessage implements LargeServerMe
    }
 
    @Override
+   public void finishParse() throws Exception {
+
+   }
+
+   @Override
    public synchronized void addBytes(ActiveMQBuffer bytes) {
       final int readableBytes = bytes.readableBytes();
       if (buffer == null) {
@@ -83,15 +105,6 @@ class NullStorageLargeServerMessage extends CoreMessage implements LargeServerMe
       return true;
    }
 
-   @Override
-   public void decrementDelayDeletionCount() {
-
-   }
-
-   @Override
-   public void incrementDelayDeletionCount() {
-
-   }
 
    @Override
    public boolean isServerMessage() {
@@ -124,12 +137,22 @@ class NullStorageLargeServerMessage extends CoreMessage implements LargeServerMe
    }
 
    @Override
+   public void clearPendingRecordID() {
+
+   }
+
+   @Override
+   public boolean hasPendingRecord() {
+      return false;
+   }
+
+   @Override
    public long getPendingRecordID() {
       return -1;
    }
 
    @Override
-   public SequentialFile getFile() {
+   public SequentialFile getAppendFile() {
       return null;
    }
 
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/nullpm/NullStorageManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/nullpm/NullStorageManager.java
index fd765e1..d1a2ebe 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/nullpm/NullStorageManager.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/nullpm/NullStorageManager.java
@@ -26,6 +26,8 @@ import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
 
+import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
+import org.apache.activemq.artemis.api.core.ActiveMQException;
 import org.apache.activemq.artemis.api.core.Message;
 import org.apache.activemq.artemis.api.core.Pair;
 import org.apache.activemq.artemis.api.core.SimpleString;
@@ -550,6 +552,10 @@ public class NullStorageManager implements StorageManager {
       // no-op
    }
 
+   @Override
+   public void deleteLargeMessageBody(LargeServerMessage largeServerMessage) throws ActiveMQException {
+
+   }
 
    @Override
    public boolean addToPage(PagingStore store,
@@ -585,6 +591,11 @@ public class NullStorageManager implements StorageManager {
    }
 
    @Override
+   public void addBytesToLargeMessage(SequentialFile file, long messageId, ActiveMQBuffer bytes) throws Exception {
+
+   }
+
+   @Override
    public void beforePageRead() throws Exception {
    }
 
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/postoffice/PostOffice.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/postoffice/PostOffice.java
index 977e678..7cc7974 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/postoffice/PostOffice.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/postoffice/PostOffice.java
@@ -159,7 +159,20 @@ public interface PostOffice extends ActiveMQComponent {
                        boolean rejectDuplicates,
                        Binding binding) throws Exception;
 
-   MessageReference reroute(Message message, Queue queue, Transaction tx) throws Exception;
+   /**
+    * This method was renamed as reload, use the new method instead
+    * @param message
+    * @param queue
+    * @param tx
+    * @return
+    * @throws Exception
+    */
+   @Deprecated
+   default MessageReference reroute(Message message, Queue queue, Transaction tx) throws Exception {
+      return reload(message, queue, tx);
+   }
+
+   MessageReference reload(Message message, Queue queue, Transaction tx) throws Exception;
 
    Pair<RoutingContext, Message> redistribute(Message message,
                                                     Queue originatingQueue,
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/postoffice/impl/PostOfficeImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/postoffice/impl/PostOfficeImpl.java
index f35e590..bbebfae 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/postoffice/impl/PostOfficeImpl.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/postoffice/impl/PostOfficeImpl.java
@@ -1042,8 +1042,6 @@ public class PostOfficeImpl implements PostOffice, NotificationListener, Binding
 
       final SimpleString address = context.getAddress(message);
 
-      setPagingStore(address, message);
-
       AtomicBoolean startedTX = new AtomicBoolean(false);
 
       applyExpiryDelay(message, address);
@@ -1175,9 +1173,7 @@ public class PostOfficeImpl implements PostOffice, NotificationListener, Binding
    }
 
    @Override
-   public MessageReference reroute(final Message message, final Queue queue, final Transaction tx) throws Exception {
-
-      setPagingStore(queue.getAddress(), message);
+   public MessageReference reload(final Message message, final Queue queue, final Transaction tx) throws Exception {
 
       MessageReference reference = MessageReference.Factory.createReference(message, queue);
 
@@ -1189,9 +1185,7 @@ public class PostOfficeImpl implements PostOffice, NotificationListener, Binding
          }
       }
 
-      message.incrementDurableRefCount();
-
-      message.incrementRefCount();
+      queue.durableUp(message);
 
       if (tx == null) {
          queue.reload(reference);
@@ -1229,7 +1223,7 @@ public class PostOfficeImpl implements PostOffice, NotificationListener, Binding
                   try {
                      //this will cause large message file to be
                      //cleaned up
-                     copyRedistribute.decrementRefCount();
+                     // copyRedistribute.refDown();
                   } catch (Exception e) {
                      logger.warn("Failed to clean up message: " + copyRedistribute);
                   }
@@ -1387,12 +1381,6 @@ public class PostOfficeImpl implements PostOffice, NotificationListener, Binding
 
    // Private -----------------------------------------------------------------
 
-   private void setPagingStore(SimpleString address, Message message) throws Exception {
-      PagingStore store = pagingManager.getPageStore(CompositeAddress.extractAddressName(address));
-
-      message.setContext(store);
-   }
-
    private void routeQueueInfo(final Message message, final Queue queue, final boolean applyFilters) throws Exception {
       if (!applyFilters || queue.getFilter() == null || queue.getFilter().match(message)) {
          RoutingContext context = new RoutingContextImpl(null);
@@ -1462,7 +1450,7 @@ public class PostOfficeImpl implements PostOffice, NotificationListener, Binding
             }
             refs.add(reference);
 
-            message.incrementRefCount();
+            queue.refUp(message);
          }
 
          Iterator<Queue> iter = entry.getValue().getDurableQueues().iterator();
@@ -1485,7 +1473,7 @@ public class PostOfficeImpl implements PostOffice, NotificationListener, Binding
             refs.add(reference);
 
             if (message.isDurable()) {
-               int durableRefCount = message.incrementDurableRefCount();
+               int durableRefCount = queue.durableUp(message);
 
                if (durableRefCount == 1) {
                   if (tx != null) {
@@ -1514,9 +1502,9 @@ public class PostOfficeImpl implements PostOffice, NotificationListener, Binding
                      storageManager.updateScheduledDeliveryTime(reference);
                   }
                }
+            } else {
+               queue.refUp(message);
             }
-
-            message.incrementRefCount();
          }
       }
 
@@ -1623,7 +1611,7 @@ public class PostOfficeImpl implements PostOffice, NotificationListener, Binding
          if (!cacheBridge.atomicVerify(bridgeDupBytes, context.getTransaction())) {
             context.getTransaction().rollback();
             startedTX.set(false);
-            message.decrementRefCount();
+            message.usageDown(); // this will cause large message delete
             return false;
          }
       } else {
@@ -1649,7 +1637,7 @@ public class PostOfficeImpl implements PostOffice, NotificationListener, Binding
                   context.getTransaction().markAsRollbackOnly(new ActiveMQDuplicateIdException(warnMessage));
                }
 
-               message.decrementRefCount();
+               message.usageDown(); // this will cause large message delete
 
                return false;
             }
@@ -1836,10 +1824,10 @@ public class PostOfficeImpl implements PostOffice, NotificationListener, Binding
             Message message = ref.getMessage();
 
             if (message.isDurable() && ref.getQueue().isDurable()) {
-               message.decrementDurableRefCount();
+               ref.getQueue().durableDown(message);
+            } else {
+               ref.getQueue().refDown(message);
             }
-
-            message.decrementRefCount();
          }
       }
 
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/ServerSessionPacketHandler.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/ServerSessionPacketHandler.java
index 5a731fb..058d636 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/ServerSessionPacketHandler.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/ServerSessionPacketHandler.java
@@ -26,6 +26,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQExceptionType;
 import org.apache.activemq.artemis.api.core.ActiveMQIOErrorException;
 import org.apache.activemq.artemis.api.core.ActiveMQInternalErrorException;
 import org.apache.activemq.artemis.api.core.ActiveMQQueueMaxConsumerLimitReached;
+import org.apache.activemq.artemis.api.core.ICoreMessage;
 import org.apache.activemq.artemis.api.core.Message;
 import org.apache.activemq.artemis.api.core.RoutingType;
 import org.apache.activemq.artemis.api.core.SimpleString;
@@ -699,7 +700,7 @@ public class ServerSessionPacketHandler implements ChannelHandler {
          try {
             final SessionSendMessage message = (SessionSendMessage) packet;
             requiresResponse = message.isRequiresResponse();
-            this.session.send(EmbedMessageUtil.extractEmbedded(message.getMessage()), this.direct);
+            this.session.send(EmbedMessageUtil.extractEmbedded(message.getMessage(), storageManager), this.direct);
             if (requiresResponse) {
                response = createNullResponseMessage(packet);
             }
@@ -1026,12 +1027,12 @@ public class ServerSessionPacketHandler implements ChannelHandler {
             currentLargeMessage.releaseResources(true);
 
             if (messageBodySize >= 0) {
-               currentLargeMessage.putLongProperty(Message.HDR_LARGE_BODY_SIZE, messageBodySize);
+               currentLargeMessage.toMessage().putLongProperty(Message.HDR_LARGE_BODY_SIZE, messageBodySize);
             }
 
             LargeServerMessage message = currentLargeMessage;
             currentLargeMessage = null;
-            session.doSend(session.getCurrentTransaction(), message, null, false, false);
+            session.doSend(session.getCurrentTransaction(), EmbedMessageUtil.extractEmbedded((ICoreMessage)message.toMessage(), storageManager), null, false, false);
          }
       }
    }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/CoreProtocolManagerFactory.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/CoreProtocolManagerFactory.java
index 6d5e352..7590924 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/CoreProtocolManagerFactory.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/CoreProtocolManagerFactory.java
@@ -23,7 +23,6 @@ import org.apache.activemq.artemis.api.core.BaseInterceptor;
 import org.apache.activemq.artemis.api.core.Interceptor;
 import org.apache.activemq.artemis.api.core.Message;
 import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
-import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
 import org.apache.activemq.artemis.core.message.impl.CoreMessagePersister;
 import org.apache.activemq.artemis.core.persistence.Persister;
 import org.apache.activemq.artemis.core.server.ActiveMQServer;
@@ -38,7 +37,7 @@ public class CoreProtocolManagerFactory extends AbstractProtocolManagerFactory<I
    private static final String MODULE_NAME = "artemis-server";
 
    @Override
-   public Persister<Message, CoreMessageObjectPools>[] getPersister() {
+   public Persister<Message>[] getPersister() {
       return new Persister[]{CoreMessagePersister.getInstance()};
    }
 
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/CoreSessionCallback.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/CoreSessionCallback.java
index f53d028..ad0c320 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/CoreSessionCallback.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/CoreSessionCallback.java
@@ -18,7 +18,7 @@ package org.apache.activemq.artemis.core.protocol.core.impl;
 
 import org.apache.activemq.artemis.api.core.Message;
 import org.apache.activemq.artemis.api.core.SimpleString;
-import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
 import org.apache.activemq.artemis.core.protocol.core.Channel;
 import org.apache.activemq.artemis.core.protocol.core.Packet;
 import org.apache.activemq.artemis.core.protocol.core.ServerSessionPacketHandler;
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/replication/ReplicatedLargeMessage.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/replication/ReplicatedLargeMessage.java
index 06fb196..d63167f 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/replication/ReplicatedLargeMessage.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/replication/ReplicatedLargeMessage.java
@@ -52,8 +52,12 @@ public interface ReplicatedLargeMessage {
     */
    void addBytes(byte[] body) throws Exception;
 
+   void clearPendingRecordID();
+
    void setPendingRecordID(long pendingRecordID);
 
    long getPendingRecordID();
 
+   boolean hasPendingRecord();
+
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/MessageConverter.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/CoreLargeServerMessage.java
similarity index 61%
copy from artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/MessageConverter.java
copy to artemis-server/src/main/java/org/apache/activemq/artemis/core/server/CoreLargeServerMessage.java
index 2c81343..7794228 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/MessageConverter.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/CoreLargeServerMessage.java
@@ -14,15 +14,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.activemq.artemis.spi.core.protocol;
 
-import org.apache.activemq.artemis.api.core.ICoreMessage;
-import org.apache.activemq.artemis.api.core.Message;
-import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
+package org.apache.activemq.artemis.core.server;
 
-public interface MessageConverter<ProtocolMessage extends Message> {
-
-   ICoreMessage toCore(ProtocolMessage pureMessage, CoreMessageObjectPools coreMessageObjectPools) throws Exception;
+/**
+ * This is a tagging interface,
+ * as we need to make sure the LargeMessage is a core large message is certain places.
+ */
+public interface CoreLargeServerMessage extends LargeServerMessage {
 
-   ProtocolMessage fromCore(ICoreMessage coreMessage) throws Exception;
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/LargeServerMessage.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/LargeServerMessage.java
index 8fc3444..9175781 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/LargeServerMessage.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/LargeServerMessage.java
@@ -18,19 +18,26 @@ package org.apache.activemq.artemis.core.server;
 
 import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
 import org.apache.activemq.artemis.api.core.ActiveMQException;
-import org.apache.activemq.artemis.api.core.ICoreMessage;
+import org.apache.activemq.artemis.api.core.Message;
 import org.apache.activemq.artemis.core.io.SequentialFile;
+import org.apache.activemq.artemis.core.message.LargeBodyReader;
+import org.apache.activemq.artemis.core.persistence.StorageManager;
+import org.apache.activemq.artemis.core.persistence.impl.journal.LargeBody;
 import org.apache.activemq.artemis.core.replication.ReplicatedLargeMessage;
 
-public interface LargeServerMessage extends ReplicatedLargeMessage, ICoreMessage {
+public interface LargeServerMessage extends ReplicatedLargeMessage {
 
-   long NO_PENDING_ID = -1;
+   Message toMessage();
+
+   StorageManager getStorageManager();
 
    @Override
    void addBytes(byte[] bytes) throws Exception;
 
    void addBytes(ActiveMQBuffer bytes) throws Exception;
 
+   long getMessageID();
+
    /**
     * We have to copy the large message content in case of DLQ and paged messages
     * For that we need to pre-mark the LargeMessage with a flag when it is paged
@@ -46,13 +53,16 @@ public interface LargeServerMessage extends ReplicatedLargeMessage, ICoreMessage
    @Override
    void deleteFile() throws Exception;
 
-   void incrementDelayDeletionCount();
-
-   void decrementDelayDeletionCount() throws Exception;
-
    /**
+    * This will return the File suitable for appending the message
     * @return
     * @throws ActiveMQException
     */
-   SequentialFile getFile() throws ActiveMQException;
+   SequentialFile getAppendFile() throws ActiveMQException;
+
+   LargeBodyReader getLargeBodyReader() throws ActiveMQException;
+
+   LargeBody getLargeBody();
+
+   void finishParse() throws Exception;
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/Queue.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/Queue.java
index 4d769f2..ee37024 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/Queue.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/Queue.java
@@ -27,6 +27,7 @@ import org.apache.activemq.artemis.api.core.Pair;
 import org.apache.activemq.artemis.api.core.RoutingType;
 import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.core.filter.Filter;
+import org.apache.activemq.artemis.core.paging.PagingStore;
 import org.apache.activemq.artemis.core.paging.cursor.PageSubscription;
 import org.apache.activemq.artemis.core.persistence.OperationContext;
 import org.apache.activemq.artemis.core.postoffice.Binding;
@@ -48,6 +49,8 @@ public interface Queue extends Bindable,CriticalComponent {
 
    void setFilter(Filter filter);
 
+   PagingStore getPagingStore();
+
    PageSubscription getPageSubscription();
 
    RoutingType getRoutingType();
@@ -60,6 +63,15 @@ public interface Queue extends Bindable,CriticalComponent {
 
    boolean isDurable();
 
+   int durableUp(Message message);
+
+   int durableDown(Message message);
+
+   void refUp(Message message);
+
+   void refDown(Message message);
+
+
    /**
     * The queue definition could be durable, but the messages could eventually be considered non durable.
     * (e.g. purgeOnNoConsumers)
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/QueueConfig.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/QueueConfig.java
index e5b9bb6..74aa5d4 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/QueueConfig.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/QueueConfig.java
@@ -31,6 +31,7 @@ public final class QueueConfig {
    private final SimpleString address;
    private final SimpleString name;
    private final Filter filter;
+   final PagingStore pagingStore;
    private final PageSubscription pageSubscription;
    private final SimpleString user;
    private final boolean durable;
@@ -259,10 +260,11 @@ public final class QueueConfig {
        * @throws IllegalStateException if the creation of {@link PageSubscription} fails
        */
       public QueueConfig build() {
+         final PagingStore pageStore;
          final PageSubscription pageSubscription;
          if (pagingManager != null && !FilterUtils.isTopicIdentification(filter)) {
             try {
-               final PagingStore pageStore = this.pagingManager.getPageStore(address);
+               pageStore = this.pagingManager.getPageStore(address);
                if (pageStore != null) {
                   pageSubscription = pageStore.getCursorProvider().createSubscription(id, filter, durable);
                } else {
@@ -273,8 +275,9 @@ public final class QueueConfig {
             }
          } else {
             pageSubscription = null;
+            pageStore = null;
          }
-         return new QueueConfig(id, address, name, filter, pageSubscription, user, durable, temporary, autoCreated, routingType, maxConsumers, exclusive, lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, purgeOnNoConsumers, groupRebalance, groupBuckets, groupFirstKey, autoDelete, autoDeleteDelay, autoDeleteMessageCount, ringSize, configurationManaged);
+         return new QueueConfig(id, address, name, filter, pageStore, pageSubscription, user, durable, temporary, autoCreated, routingType, maxConsumers, exclusive, lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, purgeOnNoConsumers, groupRebalance, groupBuckets, groupFirstKey, autoDelete, autoDeleteDelay, autoDeleteMessageCount, ringSize, configurationManaged);
       }
 
    }
@@ -311,6 +314,7 @@ public final class QueueConfig {
                        final SimpleString address,
                        final SimpleString name,
                        final Filter filter,
+                       final PagingStore pagingStore,
                        final PageSubscription pageSubscription,
                        final SimpleString user,
                        final boolean durable,
@@ -337,6 +341,7 @@ public final class QueueConfig {
       this.address = address;
       this.name = name;
       this.filter = filter;
+      this.pagingStore = pagingStore;
       this.pageSubscription = pageSubscription;
       this.user = user;
       this.durable = durable;
@@ -465,6 +470,10 @@ public final class QueueConfig {
       return ringSize;
    }
 
+   public PagingStore getPagingStore() {
+      return pagingStore;
+   }
+
    @Override
    public boolean equals(Object o) {
       if (this == o)
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/cluster/impl/BridgeImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/cluster/impl/BridgeImpl.java
index c43610e..8a34ca3 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/cluster/impl/BridgeImpl.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/cluster/impl/BridgeImpl.java
@@ -32,6 +32,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQExceptionType;
 import org.apache.activemq.artemis.api.core.ActiveMQInterruptedException;
 import org.apache.activemq.artemis.api.core.Message;
 import org.apache.activemq.artemis.api.core.Pair;
+import org.apache.activemq.artemis.api.core.RefCountMessage;
 import org.apache.activemq.artemis.api.core.RoutingType;
 import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.api.core.TransportConfiguration;
@@ -552,6 +553,7 @@ public class BridgeImpl implements Bridge, SessionFailureListener, SendAcknowled
    /* Hook for processing message before forwarding */
    protected Message beforeForward(Message message, final SimpleString forwardingAddress) {
       message = message.copy();
+      ((RefCountMessage)message).setParentRef((RefCountMessage)message);
 
       return beforeForwardingNoCopy(message, forwardingAddress);
    }
@@ -662,10 +664,10 @@ public class BridgeImpl implements Bridge, SessionFailureListener, SendAcknowled
             final HandleStatus status;
             if (message.isLargeMessage()) {
                deliveringLargeMessage = true;
-               deliverLargeMessage(dest, ref, (LargeServerMessage) message);
+               deliverLargeMessage(dest, ref, (LargeServerMessage) message, ref.getMessage());
                status = HandleStatus.HANDLED;
             } else {
-               status = deliverStandardMessage(dest, ref, message);
+               status = deliverStandardMessage(dest, ref, message, ref.getMessage());
             }
 
             //Only increment messages pending acknowledgement if handled by bridge
@@ -764,12 +766,13 @@ public class BridgeImpl implements Bridge, SessionFailureListener, SendAcknowled
 
    private void deliverLargeMessage(final SimpleString dest,
                                     final MessageReference ref,
-                                    final LargeServerMessage message) {
+                                    final LargeServerMessage message,
+                                    final Message originalMessage) {
       executor.execute(new Runnable() {
          @Override
          public void run() {
             try {
-               producer.send(dest, message);
+               producer.send(dest, message.toMessage());
 
                // as soon as we are done sending the large message
                // we unset the delivery flag and we will call the deliveryAsync on the queue
@@ -794,7 +797,7 @@ public class BridgeImpl implements Bridge, SessionFailureListener, SendAcknowled
     * @param message
     * @return
     */
-   private HandleStatus deliverStandardMessage(SimpleString dest, final MessageReference ref, Message message) {
+   private HandleStatus deliverStandardMessage(SimpleString dest, final MessageReference ref, Message message, Message originalMessage) {
       // if we failover during send then there is a chance that the
       // that this will throw a disconnect, we need to remove the message
       // from the acks so it will get resent, duplicate detection will cope
@@ -821,6 +824,8 @@ public class BridgeImpl implements Bridge, SessionFailureListener, SendAcknowled
          connectionFailed(e, false);
 
          return HandleStatus.BUSY;
+      } finally {
+         originalMessage.usageDown();
       }
 
       return HandleStatus.HANDLED;
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/LastValueQueue.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/LastValueQueue.java
index 63044b6..5a269ab 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/LastValueQueue.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/LastValueQueue.java
@@ -28,6 +28,7 @@ import org.apache.activemq.artemis.api.core.Message;
 import org.apache.activemq.artemis.api.core.RoutingType;
 import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.core.filter.Filter;
+import org.apache.activemq.artemis.core.paging.PagingStore;
 import org.apache.activemq.artemis.core.paging.cursor.PageSubscription;
 import org.apache.activemq.artemis.core.persistence.StorageManager;
 import org.apache.activemq.artemis.core.postoffice.PostOffice;
@@ -60,6 +61,7 @@ public class LastValueQueue extends QueueImpl {
                          final SimpleString address,
                          final SimpleString name,
                          final Filter filter,
+                         final PagingStore pagingStore,
                          final PageSubscription pageSubscription,
                          final SimpleString user,
                          final boolean durable,
@@ -87,7 +89,7 @@ public class LastValueQueue extends QueueImpl {
                          final ArtemisExecutor executor,
                          final ActiveMQServer server,
                          final QueueFactory factory) {
-      super(persistenceID, address, name, filter, pageSubscription, user, durable, temporary, autoCreated, routingType, maxConsumers, exclusive, groupRebalance, groupBuckets, groupFirstKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, purgeOnNoConsumers, autoDelete, autoDeleteDelay, autoDeleteMessageCount, configurationManaged, scheduledExecutor, postOffice, storageManager, addressSettingsRepository, executor, server, factory);
+      super(persistenceID, address, name, filter, pagingStore, pageSubscription, user, durable, temporary, autoCreated, routingType, maxConsumers, exclusive, groupRebalance, groupBuckets, groupFirstKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, purgeOnNoConsumers, autoDelete, autoDeleteDelay, autoDeleteMessageCount, configurationManaged, scheduledExecutor, postOffice, storageManager, addressSettingsRepository, executor, server, factory);
       this.lastValueKey = lastValueKey;
    }
 
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/PostOfficeJournalLoader.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/PostOfficeJournalLoader.java
index c18835c..26be7dc 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/PostOfficeJournalLoader.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/PostOfficeJournalLoader.java
@@ -238,7 +238,7 @@ public class PostOfficeJournalLoader implements JournalLoader {
                record.getMessage().setScheduledDeliveryTime(scheduledDeliveryTime);
             }
 
-            MessageReference ref = postOffice.reroute(record.getMessage(), queue, null);
+            MessageReference ref = postOffice.reload(record.getMessage(), queue, null);
 
             ref.setDeliveryCount(record.getDeliveryCount());
 
@@ -252,7 +252,7 @@ public class PostOfficeJournalLoader implements JournalLoader {
    @Override
    public void handleNoMessageReferences(Map<Long, Message> messages) {
       for (Message msg : messages.values()) {
-         if (msg.getRefCount() == 0) {
+         if (msg.getRefCount() == 0 && msg.getDurableCount() == 0) {
             ActiveMQServerLogger.LOGGER.journalUnreferencedMessage(msg.getMessageID());
             try {
                storageManager.deleteMessage(msg.getMessageID());
@@ -308,7 +308,7 @@ public class PostOfficeJournalLoader implements JournalLoader {
          ActiveMQServerLogger.LOGGER.journalMessageInPreparedTX(queueID);
          return;
       }
-      postOffice.reroute(message, queue, tx);
+      postOffice.reload(message, queue, tx);
    }
 
    @Override
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/QueueFactoryImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/QueueFactoryImpl.java
index f4ad91d..ecfc86d 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/QueueFactoryImpl.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/QueueFactoryImpl.java
@@ -75,9 +75,9 @@ public class QueueFactoryImpl implements QueueFactory {
    public Queue createQueueWith(final QueueConfig config) {
       final Queue queue;
       if (lastValueKey(config) != null) {
-         queue = new LastValueQueue(config.id(), config.address(), config.name(), config.filter(), config.pageSubscription(), config.user(), config.isDurable(), config.isTemporary(), config.isAutoCreated(), config.deliveryMode(), config.maxConsumers(), config.isExclusive(), config.isGroupRebalance(), config.getGroupBuckets(), config.getGroupFirstKey(), config.consumersBeforeDispatch(), config.delayBeforeDispatch(), config.isPurgeOnNoConsumers(), lastValueKey(config), config.isNonDestruct [...]
+         queue = new LastValueQueue(config.id(), config.address(), config.name(), config.filter(), config.getPagingStore(), config.pageSubscription(), config.user(), config.isDurable(), config.isTemporary(), config.isAutoCreated(), config.deliveryMode(), config.maxConsumers(), config.isExclusive(), config.isGroupRebalance(), config.getGroupBuckets(), config.getGroupFirstKey(), config.consumersBeforeDispatch(), config.delayBeforeDispatch(), config.isPurgeOnNoConsumers(), lastValueKey(conf [...]
       } else {
-         queue = new QueueImpl(config.id(), config.address(), config.name(), config.filter(), config.pageSubscription(), config.user(), config.isDurable(), config.isTemporary(), config.isAutoCreated(), config.deliveryMode(), config.maxConsumers(), config.isExclusive(), config.isGroupRebalance(), config.getGroupBuckets(), config.getGroupFirstKey(), config.isNonDestructive(), config.consumersBeforeDispatch(), config.delayBeforeDispatch(), config.isPurgeOnNoConsumers(), config.isAutoDelete( [...]
+         queue = new QueueImpl(config.id(), config.address(), config.name(), config.filter(), config.getPagingStore(), config.pageSubscription(), config.user(), config.isDurable(), config.isTemporary(), config.isAutoCreated(), config.deliveryMode(), config.maxConsumers(), config.isExclusive(), config.isGroupRebalance(), config.getGroupBuckets(), config.getGroupFirstKey(), config.isNonDestructive(), config.consumersBeforeDispatch(), config.delayBeforeDispatch(), config.isPurgeOnNoConsumer [...]
       }
       server.getCriticalAnalyzer().add(queue);
       return queue;
@@ -102,9 +102,9 @@ public class QueueFactoryImpl implements QueueFactory {
 
       Queue queue;
       if (lastValueKey(addressSettings) != null) {
-         queue = new LastValueQueue(persistenceID, address, name, filter, pageSubscription, user, durable, temporary, autoCreated, ActiveMQDefaultConfiguration.getDefaultRoutingType(), ActiveMQDefaultConfiguration.getDefaultMaxQueueConsumers(), ActiveMQDefaultConfiguration.getDefaultExclusive(), ActiveMQDefaultConfiguration.getDefaultGroupRebalance(), ActiveMQDefaultConfiguration.getDefaultGroupBuckets(), ActiveMQDefaultConfiguration.getDefaultGroupFirstKey(), ActiveMQDefaultConfiguratio [...]
+         queue = new LastValueQueue(persistenceID, address, name, filter, pageSubscription == null ? null : pageSubscription.getPagingStore(), pageSubscription, user, durable, temporary, autoCreated, ActiveMQDefaultConfiguration.getDefaultRoutingType(), ActiveMQDefaultConfiguration.getDefaultMaxQueueConsumers(), ActiveMQDefaultConfiguration.getDefaultExclusive(), ActiveMQDefaultConfiguration.getDefaultGroupRebalance(), ActiveMQDefaultConfiguration.getDefaultGroupBuckets(), ActiveMQDefaul [...]
       } else {
-         queue = new QueueImpl(persistenceID, address, name, filter, pageSubscription, user, durable, temporary, autoCreated, scheduledExecutor, postOffice, storageManager, addressSettingsRepository, executorFactory.getExecutor(), server, this);
+         queue = new QueueImpl(persistenceID, address, name, filter, pageSubscription == null ? null : pageSubscription.getPagingStore(), pageSubscription, user, durable, temporary, autoCreated, scheduledExecutor, postOffice, storageManager, addressSettingsRepository, executorFactory.getExecutor(), server, this);
       }
 
       server.getCriticalAnalyzer().add(queue);
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/QueueImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/QueueImpl.java
index b21e1a2..53b701e 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/QueueImpl.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/QueueImpl.java
@@ -56,6 +56,7 @@ import org.apache.activemq.artemis.api.core.management.ResourceNames;
 import org.apache.activemq.artemis.core.PriorityAware;
 import org.apache.activemq.artemis.core.filter.Filter;
 import org.apache.activemq.artemis.core.io.IOCallback;
+import org.apache.activemq.artemis.core.paging.PagingStore;
 import org.apache.activemq.artemis.core.paging.cursor.PageIterator;
 import org.apache.activemq.artemis.core.paging.cursor.PagePosition;
 import org.apache.activemq.artemis.core.paging.cursor.PageSubscription;
@@ -168,6 +169,8 @@ public class QueueImpl extends CriticalComponentImpl implements Queue {
 
    private volatile boolean queueDestroyed = false;
 
+   private final PagingStore pagingStore;
+
    private final PageSubscription pageSubscription;
 
    private ReferenceCounter refCountForConsumers;
@@ -370,32 +373,34 @@ public class QueueImpl extends CriticalComponentImpl implements Queue {
                      final ArtemisExecutor executor,
                      final ActiveMQServer server,
                      final QueueFactory factory) {
-      this(id, address, name, filter, null, user, durable, temporary, autoCreated, scheduledExecutor, postOffice, storageManager, addressSettingsRepository, executor, server, factory);
+      this(id, address, name, filter, null, null, user, durable, temporary, autoCreated, scheduledExecutor, postOffice, storageManager, addressSettingsRepository, executor, server, factory);
    }
 
    public QueueImpl(final long id,
-                     final SimpleString address,
-                     final SimpleString name,
-                     final Filter filter,
-                     final PageSubscription pageSubscription,
-                     final SimpleString user,
-                     final boolean durable,
-                     final boolean temporary,
-                     final boolean autoCreated,
-                     final ScheduledExecutorService scheduledExecutor,
-                     final PostOffice postOffice,
-                     final StorageManager storageManager,
-                     final HierarchicalRepository<AddressSettings> addressSettingsRepository,
-                     final ArtemisExecutor executor,
-                     final ActiveMQServer server,
-                     final QueueFactory factory) {
-      this(id, address, name, filter, pageSubscription, user, durable, temporary, autoCreated, RoutingType.MULTICAST, null, null, scheduledExecutor, postOffice, storageManager, addressSettingsRepository, executor, server, factory);
+                    final SimpleString address,
+                    final SimpleString name,
+                    final Filter filter,
+                    final PagingStore pagingStore,
+                    final PageSubscription pageSubscription,
+                    final SimpleString user,
+                    final boolean durable,
+                    final boolean temporary,
+                    final boolean autoCreated,
+                    final ScheduledExecutorService scheduledExecutor,
+                    final PostOffice postOffice,
+                    final StorageManager storageManager,
+                    final HierarchicalRepository<AddressSettings> addressSettingsRepository,
+                    final ArtemisExecutor executor,
+                    final ActiveMQServer server,
+                    final QueueFactory factory) {
+      this(id, address, name, filter, pagingStore, pageSubscription, user, durable, temporary, autoCreated, RoutingType.MULTICAST, null, null, scheduledExecutor, postOffice, storageManager, addressSettingsRepository, executor, server, factory);
    }
 
    public QueueImpl(final long id,
                      final SimpleString address,
                      final SimpleString name,
                      final Filter filter,
+                     final PagingStore pagingStore,
                      final PageSubscription pageSubscription,
                      final SimpleString user,
                      final boolean durable,
@@ -411,13 +416,14 @@ public class QueueImpl extends CriticalComponentImpl implements Queue {
                      final ArtemisExecutor executor,
                      final ActiveMQServer server,
                      final QueueFactory factory) {
-      this(id, address, name, filter, pageSubscription, user, durable, temporary, autoCreated, routingType, maxConsumers, null, purgeOnNoConsumers, scheduledExecutor, postOffice, storageManager, addressSettingsRepository, executor, server, factory);
+      this(id, address, name, filter, pagingStore, pageSubscription, user, durable, temporary, autoCreated, routingType, maxConsumers, null, purgeOnNoConsumers, scheduledExecutor, postOffice, storageManager, addressSettingsRepository, executor, server, factory);
    }
 
    public QueueImpl(final long id,
                      final SimpleString address,
                      final SimpleString name,
                      final Filter filter,
+                     final PagingStore pagingStore,
                      final PageSubscription pageSubscription,
                      final SimpleString user,
                      final boolean durable,
@@ -434,13 +440,14 @@ public class QueueImpl extends CriticalComponentImpl implements Queue {
                      final ArtemisExecutor executor,
                      final ActiveMQServer server,
                      final QueueFactory factory) {
-      this(id, address, name, filter, pageSubscription, user, durable, temporary, autoCreated, routingType, maxConsumers, exclusive, null, null, false, null, null, purgeOnNoConsumers, null, null, null, false, scheduledExecutor, postOffice, storageManager, addressSettingsRepository, executor, server, factory);
+      this(id, address, name, filter, pagingStore, pageSubscription, user, durable, temporary, autoCreated, routingType, maxConsumers, exclusive, null, null, false, null, null, purgeOnNoConsumers, null, null, null, false, scheduledExecutor, postOffice, storageManager, addressSettingsRepository, executor, server, factory);
    }
 
    public QueueImpl(final long id,
                     final SimpleString address,
                     final SimpleString name,
                     final Filter filter,
+                    final PagingStore pagingStore,
                     final PageSubscription pageSubscription,
                     final SimpleString user,
                     final boolean durable,
@@ -466,13 +473,14 @@ public class QueueImpl extends CriticalComponentImpl implements Queue {
                     final ArtemisExecutor executor,
                     final ActiveMQServer server,
                     final QueueFactory factory) {
-      this(id, address, name, filter, pageSubscription, user, durable, temporary, autoCreated, routingType, maxConsumers, exclusive, groupRebalance, groupBuckets, null, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, purgeOnNoConsumers, autoDelete, autoDeleteDelay, autoDeleteMessageCount, configurationManaged, scheduledExecutor, postOffice, storageManager, addressSettingsRepository, executor, server, factory);
+      this(id, address, name, filter, pagingStore, pageSubscription, user, durable, temporary, autoCreated, routingType, maxConsumers, exclusive, groupRebalance, groupBuckets, null, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, purgeOnNoConsumers, autoDelete, autoDeleteDelay, autoDeleteMessageCount, configurationManaged, scheduledExecutor, postOffice, storageManager, addressSettingsRepository, executor, server, factory);
    }
 
    public QueueImpl(final long id,
                     final SimpleString address,
                     final SimpleString name,
                     final Filter filter,
+                    final PagingStore pagingStore,
                     final PageSubscription pageSubscription,
                     final SimpleString user,
                     final boolean durable,
@@ -499,13 +507,14 @@ public class QueueImpl extends CriticalComponentImpl implements Queue {
                     final ArtemisExecutor executor,
                     final ActiveMQServer server,
                     final QueueFactory factory) {
-      this(id, address, name, filter, pageSubscription, user, durable, temporary, autoCreated, routingType, maxConsumers, exclusive, groupRebalance, groupBuckets, groupFirstKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, purgeOnNoConsumers, autoDelete, autoDeleteDelay, autoDeleteMessageCount, configurationManaged, null, scheduledExecutor, postOffice, storageManager, addressSettingsRepository, executor, server, factory);
+      this(id, address, name, filter, pagingStore, pageSubscription, user, durable, temporary, autoCreated, routingType, maxConsumers, exclusive, groupRebalance, groupBuckets, groupFirstKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, purgeOnNoConsumers, autoDelete, autoDeleteDelay, autoDeleteMessageCount, configurationManaged, null, scheduledExecutor, postOffice, storageManager, addressSettingsRepository, executor, server, factory);
    }
 
    public QueueImpl(final long id,
                     final SimpleString address,
                     final SimpleString name,
                     final Filter filter,
+                    final PagingStore pagingStore,
                     final PageSubscription pageSubscription,
                     final SimpleString user,
                     final boolean durable,
@@ -547,6 +556,8 @@ public class QueueImpl extends CriticalComponentImpl implements Queue {
 
       this.filter = filter;
 
+      this.pagingStore = pagingStore;
+
       this.pageSubscription = pageSubscription;
 
       this.propertyDurable = durable;
@@ -868,6 +879,47 @@ public class QueueImpl extends CriticalComponentImpl implements Queue {
       return id;
    }
 
+
+   @Override
+   public int durableUp(Message message) {
+      int count = message.durableUp();
+      if (pagingStore != null) {
+         pagingStore.durableUp(message, count);
+      }
+      return count;
+   }
+
+   @Override
+   public int durableDown(Message message) {
+      int count = message.durableDown();
+      if (pagingStore != null) {
+         pagingStore.durableDown(message, count);
+      }
+      return count;
+   }
+
+   @Override
+   public void refUp(Message message) {
+      int count = message.refUp();
+      if (pagingStore != null) {
+         pagingStore.refUp(message, count);
+      }
+
+   }
+
+   @Override
+   public void refDown(Message message) {
+      int count = message.refDown();
+      if (pagingStore != null) {
+         pagingStore.refDown(message, count);
+      }
+   }
+
+   @Override
+   public PagingStore getPagingStore() {
+      return pagingStore;
+   }
+
    @Override
    public PageSubscription getPageSubscription() {
       return pageSubscription;
@@ -3654,14 +3706,8 @@ public class QueueImpl extends CriticalComponentImpl implements Queue {
 
       boolean durableRef = message.isDurable() && queue.isDurable();
 
-      try {
-         message.decrementRefCount();
-      } catch (Exception e) {
-         ActiveMQServerLogger.LOGGER.errorDecrementingRefCount(e);
-      }
-
       if (durableRef) {
-         int count = message.decrementDurableRefCount();
+         int count = queue.durableDown(message);
 
          if (count == 0) {
             // Note - we MUST store the delete after the preceding ack has been committed to storage, we cannot combine
@@ -3684,6 +3730,8 @@ public class QueueImpl extends CriticalComponentImpl implements Queue {
                ActiveMQServerLogger.LOGGER.errorRemovingMessage(e, message.getMessageID());
             }
          }
+      } else {
+         queue.refDown(message);
       }
    }
 
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/RefsOperation.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/RefsOperation.java
index d3cd425..355bcd9 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/RefsOperation.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/RefsOperation.java
@@ -23,7 +23,6 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.activemq.artemis.api.core.Message;
-import org.apache.activemq.artemis.core.paging.cursor.NonExistentPage;
 import org.apache.activemq.artemis.core.paging.cursor.PagedReference;
 import org.apache.activemq.artemis.core.persistence.StorageManager;
 import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
@@ -134,7 +133,7 @@ public class RefsOperation extends TransactionOperationAbstract {
             for (MessageReference ref : ackedRefs) {
                Message message = ref.getMessage();
                if (message.isDurable()) {
-                  int durableRefCount = message.incrementDurableRefCount();
+                  int durableRefCount = ref.getQueue().durableUp(ref.getMessage());
 
                   if (durableRefCount == 1) {
                      storageManager.storeMessageTransactional(ackedTX.getID(), message);
@@ -146,7 +145,8 @@ public class RefsOperation extends TransactionOperationAbstract {
                   ackedTX.setContainsPersistent();
                }
 
-               message.incrementRefCount();
+               // TODO-NOW: THIS MUST BE SOLVED BEFORE MERGED, DO NOT LET ME COMMIT THIS WITHOUT REVIEW
+               queue.refUp(message);
             }
             ackedTX.commit(true);
          } catch (Exception e) {
@@ -190,7 +190,7 @@ public class RefsOperation extends TransactionOperationAbstract {
          for (MessageReference refmsg : pagedMessagesToPostACK) {
             ((PagedReference)refmsg).removePendingFlag();
             if (((PagedReference) refmsg).isLargeMessage()) {
-               decrementRefCount(refmsg);
+               refmsg.getQueue().refDown(refmsg.getMessage());
             }
          }
       }
@@ -202,17 +202,6 @@ public class RefsOperation extends TransactionOperationAbstract {
       }
    }
 
-   private void decrementRefCount(MessageReference refmsg) {
-      try {
-         refmsg.getMessage().decrementRefCount();
-      } catch (NonExistentPage e) {
-         // This could happen on after commit, since the page could be deleted on file earlier by another thread
-         logger.debug(e);
-      } catch (Exception e) {
-         ActiveMQServerLogger.LOGGER.failedToDecrementMessageReferenceCount(e);
-      }
-   }
-
    @Override
    public synchronized List<MessageReference> getRelatedMessageReferences() {
       List<MessageReference> listRet = new LinkedList<>();
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ServerConsumerImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ServerConsumerImpl.java
index bb611cb..2f4078e 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ServerConsumerImpl.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ServerConsumerImpl.java
@@ -39,7 +39,7 @@ import org.apache.activemq.artemis.api.core.management.CoreNotificationType;
 import org.apache.activemq.artemis.api.core.management.ManagementHelper;
 import org.apache.activemq.artemis.core.client.impl.ClientConsumerImpl;
 import org.apache.activemq.artemis.core.filter.Filter;
-import org.apache.activemq.artemis.core.message.LargeBodyEncoder;
+import org.apache.activemq.artemis.core.message.LargeBodyReader;
 import org.apache.activemq.artemis.core.message.impl.CoreMessage;
 import org.apache.activemq.artemis.core.persistence.StorageManager;
 import org.apache.activemq.artemis.core.postoffice.Binding;
@@ -49,6 +49,7 @@ import org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl;
 import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle;
 import org.apache.activemq.artemis.core.server.ActiveMQServer;
 import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
+import org.apache.activemq.artemis.core.server.CoreLargeServerMessage;
 import org.apache.activemq.artemis.core.server.HandleStatus;
 import org.apache.activemq.artemis.core.server.LargeServerMessage;
 import org.apache.activemq.artemis.core.server.MessageReference;
@@ -112,7 +113,7 @@ public class ServerConsumerImpl implements ServerConsumer, ReadyListener {
 
    private boolean started;
 
-   private volatile LargeMessageDeliverer largeMessageDeliverer = null;
+   private volatile CoreLargeMessageDeliverer largeMessageDeliverer = null;
 
    @Override
    public String debug() {
@@ -458,10 +459,16 @@ public class ServerConsumerImpl implements ServerConsumer, ReadyListener {
                }
             }
 
+            // The deliverer will increase the usageUp, so the preAck has to be done after this is created
+            // otherwise we may have a removed message early on
+            if (message instanceof CoreLargeServerMessage && this.supportLargeMessage) {
+               largeMessageDeliverer = new CoreLargeMessageDeliverer((LargeServerMessage) message, ref);
+            }
+
             if (preAcknowledge) {
                if (message.isLargeMessage()) {
                   // we must hold one reference, or the file will be deleted before it could be delivered
-                  ((LargeServerMessage) message).incrementDelayDeletionCount();
+                  ((LargeServerMessage) message).toMessage().usageUp();
                }
 
                // With pre-ack, we ack *before* sending to the client
@@ -469,10 +476,6 @@ public class ServerConsumerImpl implements ServerConsumer, ReadyListener {
                acks++;
             }
 
-            if (message.isLargeMessage() && this.supportLargeMessage) {
-               largeMessageDeliverer = new LargeMessageDeliverer((LargeServerMessage) message, ref);
-            }
-
          }
 
          pendingDelivery.countUp();
@@ -490,12 +493,12 @@ public class ServerConsumerImpl implements ServerConsumer, ReadyListener {
             server.callBrokerMessagePlugins(plugin -> plugin.beforeDeliver(this, reference));
          }
 
-         if (message.isLargeMessage() && supportLargeMessage) {
+         if (message instanceof CoreLargeServerMessage && supportLargeMessage) {
             if (largeMessageDeliverer == null) {
                // This can't really happen as handle had already crated the deliverer
                // instead of throwing an exception in weird cases there is no problem on just go ahead and create it
                // again here
-               largeMessageDeliverer = new LargeMessageDeliverer((LargeServerMessage) message, reference);
+               largeMessageDeliverer = new CoreLargeMessageDeliverer((LargeServerMessage) message, reference);
             }
             // The deliverer was prepared during handle, as we can't have more than one pending large message
             // as it would return busy if there is anything pending
@@ -550,7 +553,7 @@ public class ServerConsumerImpl implements ServerConsumer, ReadyListener {
 
       setStarted(false);
 
-      LargeMessageDeliverer del = largeMessageDeliverer;
+      CoreLargeMessageDeliverer del = largeMessageDeliverer;
 
       if (del != null) {
          del.finish();
@@ -688,7 +691,7 @@ public class ServerConsumerImpl implements ServerConsumer, ReadyListener {
       boolean performACK = lastConsumedAsDelivered;
 
       try {
-         LargeMessageDeliverer pendingLargeMessageDeliverer = largeMessageDeliverer;
+         CoreLargeMessageDeliverer pendingLargeMessageDeliverer = largeMessageDeliverer;
          if (pendingLargeMessageDeliverer != null) {
             pendingLargeMessageDeliverer.finish();
          }
@@ -1220,7 +1223,7 @@ public class ServerConsumerImpl implements ServerConsumer, ReadyListener {
     * Internal encapsulation of the logic on sending LargeMessages.
     * This Inner class was created to avoid a bunch of loose properties about the current LargeMessage being sent
     */
-   private final class LargeMessageDeliverer {
+   private final class CoreLargeMessageDeliverer {
 
       private long sizePendingLargeMessage;
 
@@ -1235,14 +1238,14 @@ public class ServerConsumerImpl implements ServerConsumer, ReadyListener {
        */
       private long positionPendingLargeMessage;
 
-      private LargeBodyEncoder context;
+      private LargeBodyReader context;
 
       private ByteBuffer chunkBytes;
 
-      private LargeMessageDeliverer(final LargeServerMessage message, final MessageReference ref) throws Exception {
+      private CoreLargeMessageDeliverer(final LargeServerMessage message, final MessageReference ref) throws Exception {
          largeMessage = message;
 
-         largeMessage.incrementDelayDeletionCount();
+         largeMessage.toMessage().usageUp();
 
          this.ref = ref;
 
@@ -1289,15 +1292,15 @@ public class ServerConsumerImpl implements ServerConsumer, ReadyListener {
             }
 
             if (!sentInitialPacket) {
-               context = currentLargeMessage.getBodyEncoder();
+               context = currentLargeMessage.getLargeBodyReader();
 
-               sizePendingLargeMessage = context.getLargeBodySize();
+               sizePendingLargeMessage = context.getSize();
 
                context.open();
 
                sentInitialPacket = true;
 
-               int packetSize = callback.sendLargeMessage(ref, currentLargeMessage, ServerConsumerImpl.this, context.getLargeBodySize(), ref.getDeliveryCount());
+               int packetSize = callback.sendLargeMessage(ref, currentLargeMessage.toMessage(), ServerConsumerImpl.this, context.getSize(), ref.getDeliveryCount());
 
                if (availableCredits != null) {
                   final int credits = availableCredits.addAndGet(-packetSize);
@@ -1337,7 +1340,7 @@ public class ServerConsumerImpl implements ServerConsumer, ReadyListener {
 
                assert bodyBuffer.remaining() == localChunkLen;
 
-               final int readBytes = context.encode(bodyBuffer);
+               final int readBytes = context.readInto(bodyBuffer);
 
                assert readBytes == localChunkLen;
 
@@ -1406,12 +1409,7 @@ public class ServerConsumerImpl implements ServerConsumer, ReadyListener {
 
             largeMessage.releaseResources(false);
 
-            largeMessage.decrementDelayDeletionCount();
-
-            if (preAcknowledge && !browseOnly) {
-               // PreAck will have an extra reference
-               largeMessage.decrementDelayDeletionCount();
-            }
+            largeMessage.toMessage().usageDown();
 
             largeMessageDeliverer = null;
 
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/transformer/ServerMessageImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/transformer/ServerMessageImpl.java
index 0e0220e..d8a26c8 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/transformer/ServerMessageImpl.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/transformer/ServerMessageImpl.java
@@ -73,22 +73,22 @@ public class ServerMessageImpl extends MessageInternalImpl implements ServerMess
    }
 
    @Override
-   public int incrementRefCount() throws Exception {
+   public int refUp() {
       throw new UnsupportedOperationException();
    }
 
    @Override
-   public int decrementRefCount() throws Exception {
+   public int refDown() {
       throw new UnsupportedOperationException();
    }
 
    @Override
-   public int incrementDurableRefCount() {
+   public int durableUp() {
       throw new UnsupportedOperationException();
    }
 
    @Override
-   public int decrementDurableRefCount() {
+   public int durableDown() {
       throw new UnsupportedOperationException();
    }
 
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/EmbedMessageUtil.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/EmbedMessageUtil.java
index 53eb27b..310971f 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/EmbedMessageUtil.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/EmbedMessageUtil.java
@@ -18,14 +18,23 @@
 package org.apache.activemq.artemis.spi.core.protocol;
 
 import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
+import org.apache.activemq.artemis.api.core.ActiveMQBuffers;
 import org.apache.activemq.artemis.api.core.ICoreMessage;
 import org.apache.activemq.artemis.api.core.Message;
+import org.apache.activemq.artemis.api.core.RefCountMessage;
 import org.apache.activemq.artemis.core.message.impl.CoreMessage;
 import org.apache.activemq.artemis.core.persistence.Persister;
+import org.apache.activemq.artemis.core.persistence.StorageManager;
+import org.apache.activemq.artemis.core.persistence.impl.journal.AbstractJournalStorageManager;
+import org.apache.activemq.artemis.core.persistence.impl.journal.LargeServerMessageImpl;
+import org.apache.activemq.artemis.core.server.LargeServerMessage;
 import org.jboss.logging.Logger;
 
 public class EmbedMessageUtil {
 
+
+   private static final String AMQP_ENCODE_PROPERTY = "_AMQP_EMBED_LARGE";
+
    private static final byte[] signature = new byte[]{(byte) 'E', (byte) 'M', (byte) 'B'};
 
    private static final Logger logger = Logger.getLogger(EmbedMessageUtil.class);
@@ -35,35 +44,76 @@ public class EmbedMessageUtil {
       if (source instanceof ICoreMessage) {
          return (ICoreMessage) source;
       } else {
-         Persister persister = source.getPersister();
 
-         CoreMessage message = new CoreMessage(source.getMessageID(), persister.getEncodeSize(source) + signature.length + CoreMessage.BODY_OFFSET).setType(Message.EMBEDDED_TYPE);
+         if (source.isLargeMessage()) {
+            LargeServerMessage largeSource = (LargeServerMessage) source;
 
-         ActiveMQBuffer buffer = message.getBodyBuffer();
-         buffer.writeBytes(signature);
-         persister.encode(buffer, source);
-         return message;
-      }
-   }
+            LargeServerMessageImpl largeServerMessage = new LargeServerMessageImpl(Message.LARGE_EMBEDDED_TYPE, source.getMessageID(), largeSource.getStorageManager(), largeSource.getLargeBody().createFile());
+            largeServerMessage.setDurable(source.isDurable());
+            int size = source.getPersister().getEncodeSize(source);
+            byte[] arrayByte = new byte[size];
+            ActiveMQBuffer buffer = ActiveMQBuffers.wrappedBuffer(arrayByte);
+            buffer.resetWriterIndex();
+            source.getPersister().encode(buffer, source);
+            largeServerMessage.toMessage().putBytesProperty(AMQP_ENCODE_PROPERTY, arrayByte);
+            largeServerMessage.setParentRef((RefCountMessage)source);
+            return (ICoreMessage) largeServerMessage.toMessage();
+         } else {
+            Persister persister = source.getPersister();
 
-   public static Message extractEmbedded(ICoreMessage message) {
-      if (message.getType() == Message.EMBEDDED_TYPE) {
-         ActiveMQBuffer buffer = message.getReadOnlyBodyBuffer();
+            CoreMessage message = new CoreMessage(source.getMessageID(), persister.getEncodeSize(source) + signature.length + CoreMessage.BODY_OFFSET).setType(Message.EMBEDDED_TYPE);
+            message.setDurable(source.isDurable());
 
-         if (buffer.readableBytes() < signature.length || !checkSignature(buffer)) {
-            logger.tracef("Message type %d was used for something other than embed messages, ignoring content and treating as a regular message", Message.EMBEDDED_TYPE);
+            ActiveMQBuffer buffer = message.getBodyBuffer();
+            buffer.writeBytes(signature);
+            persister.encode(buffer, source);
             return message;
          }
+      }
+   }
 
-         try {
-            return MessagePersister.getInstance().decode(buffer, null);
-         } catch (Exception e) {
-            logger.warn(e.getMessage(), e);
+   public static Message extractEmbedded(ICoreMessage message, StorageManager storageManager) {
+      switch (message.getType()) {
+         case Message.EMBEDDED_TYPE:
+            return extractRegularMessage(message, storageManager);
+         case Message.LARGE_EMBEDDED_TYPE:
+            return extractLargeMessage(message, storageManager);
+         default:
             return message;
-         }
-      } else {
+      }
+   }
+
+   private static Message extractRegularMessage(ICoreMessage message, StorageManager storageManager) {
+      ActiveMQBuffer buffer = message.getReadOnlyBodyBuffer();
+
+      if (buffer.readableBytes() < signature.length || !checkSignature(buffer)) {
+         logger.tracef("Message type %d was used for something other than embed messages, ignoring content and treating as a regular message", Message.EMBEDDED_TYPE);
          return message;
       }
+
+      return readEncoded(message, storageManager, buffer);
+   }
+
+   private static Message readEncoded(ICoreMessage message, StorageManager storageManager, ActiveMQBuffer buffer) {
+
+
+      AbstractJournalStorageManager.setupThreadLocal(storageManager);
+      try {
+         Message returnMessage = MessagePersister.getInstance().decode(buffer, null, null);
+         returnMessage.setMessageID(message.getMessageID());
+         return returnMessage;
+      } catch (Exception e) {
+         logger.warn(e.getMessage(), e);
+         return message;
+      } finally {
+         AbstractJournalStorageManager.setupThreadLocal(null);
+      }
+   }
+
+   private static Message extractLargeMessage(ICoreMessage message, StorageManager storageManager) {
+      ActiveMQBuffer buffer = ActiveMQBuffers.wrappedBuffer(message.getBytesProperty(AMQP_ENCODE_PROPERTY));
+
+      return readEncoded(message, storageManager, buffer);
    }
 
    private static boolean checkSignature(final ActiveMQBuffer buffer) {
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/MessageConverter.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/MessageConverter.java
index 2c81343..5420fd6 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/MessageConverter.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/MessageConverter.java
@@ -18,11 +18,12 @@ package org.apache.activemq.artemis.spi.core.protocol;
 
 import org.apache.activemq.artemis.api.core.ICoreMessage;
 import org.apache.activemq.artemis.api.core.Message;
-import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.StorageManager;
 
 public interface MessageConverter<ProtocolMessage extends Message> {
 
    ICoreMessage toCore(ProtocolMessage pureMessage, CoreMessageObjectPools coreMessageObjectPools) throws Exception;
 
-   ProtocolMessage fromCore(ICoreMessage coreMessage) throws Exception;
+   ProtocolMessage fromCore(ICoreMessage coreMessage, StorageManager storageManager) throws Exception;
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/MessagePersister.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/MessagePersister.java
index 2fddc56..808d094 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/MessagePersister.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/MessagePersister.java
@@ -21,20 +21,26 @@ import java.util.ServiceLoader;
 
 import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
 import org.apache.activemq.artemis.api.core.Message;
-import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
 import org.apache.activemq.artemis.core.message.impl.CoreMessagePersister;
 import org.apache.activemq.artemis.core.persistence.Persister;
 import org.jboss.logging.Logger;
 
-public class MessagePersister implements Persister<Message, CoreMessageObjectPools> {
+import static org.apache.activemq.artemis.core.persistence.PersisterIDs.MAX_PERSISTERS;
+
+public class MessagePersister implements Persister<Message> {
+
+   @Override
+   public byte getID() {
+      return 0;
+   }
 
    private static final Logger logger = Logger.getLogger(MessagePersister.class);
 
    private static final MessagePersister theInstance = new MessagePersister();
 
    /** This will be used for reading messages */
-   private static final int MAX_PERSISTERS = 3;
-   private static final Persister<Message, CoreMessageObjectPools>[] persisters = new Persister[MAX_PERSISTERS];
+   private static final Persister<Message>[] persisters = new Persister[MAX_PERSISTERS];
 
    static {
       CoreMessagePersister persister = CoreMessagePersister.getInstance();
@@ -47,7 +53,7 @@ public class MessagePersister implements Persister<Message, CoreMessageObjectPoo
    }
 
    public static void registerProtocol(ProtocolManagerFactory manager) {
-      Persister<Message, CoreMessageObjectPools>[] messagePersisters = manager.getPersister();
+      Persister<Message>[] messagePersisters = manager.getPersister();
       if (messagePersisters == null || messagePersisters.length == 0) {
          logger.debug("Cannot find persister for " + manager);
       } else {
@@ -70,7 +76,7 @@ public class MessagePersister implements Persister<Message, CoreMessageObjectPoo
       return persisters[id - 1];
    }
 
-   public static void registerPersister(Persister<Message, CoreMessageObjectPools> persister) {
+   public static void registerPersister(Persister<Message> persister) {
       if (persister != null) {
          assert persister.getID() <= MAX_PERSISTERS : "You must update MessagePersister::MAX_PERSISTERS to a higher number";
          persisters[persister.getID() - 1] = persister;
@@ -98,12 +104,12 @@ public class MessagePersister implements Persister<Message, CoreMessageObjectPoo
    }
 
    @Override
-   public Message decode(ActiveMQBuffer buffer, CoreMessageObjectPools pools) {
+   public Message decode(ActiveMQBuffer buffer, Message record, CoreMessageObjectPools pools) {
       byte protocol = buffer.readByte();
-      Persister<Message, CoreMessageObjectPools> persister = getPersister(protocol);
+      Persister<Message> persister = getPersister(protocol);
       if (persister == null) {
          throw new NullPointerException("couldn't find factory for type=" + protocol);
       }
-      return persister.decode(buffer, pools);
+      return persister.decode(buffer, record, pools);
    }
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/ProtocolManagerFactory.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/ProtocolManagerFactory.java
index 77c66f9..4ab34eb 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/ProtocolManagerFactory.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/ProtocolManagerFactory.java
@@ -21,13 +21,12 @@ import java.util.Map;
 
 import org.apache.activemq.artemis.api.core.BaseInterceptor;
 import org.apache.activemq.artemis.api.core.Message;
-import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
 import org.apache.activemq.artemis.core.persistence.Persister;
 import org.apache.activemq.artemis.core.server.ActiveMQServer;
 
 public interface ProtocolManagerFactory<P extends BaseInterceptor> {
 
-   default Persister<Message, CoreMessageObjectPools>[] getPersister() {
+   default Persister<Message>[] getPersister() {
       return new Persister[]{};
    }
 
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/QueueImplTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/QueueImplTest.java
index a2b42db..b39c79a 100644
--- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/QueueImplTest.java
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/QueueImplTest.java
@@ -76,7 +76,7 @@ public class QueueImplTest {
          return null;
       }).when(storageManager).afterCompleteOperations(Mockito.any(IOCallback.class));
 
-      QueueImpl queue = new QueueImpl(0, address, address, null, pageSubscription, null, false,
+      QueueImpl queue = new QueueImpl(0, address, address, null, null, pageSubscription, null, false,
                                       false, false, Mockito.mock(ScheduledExecutorService.class),
                                       Mockito.mock(PostOffice.class), storageManager, null,
                                       Mockito.mock(ArtemisExecutor.class), Mockito.mock(ActiveMQServer.class),
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/ScheduledDeliveryHandlerTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/ScheduledDeliveryHandlerTest.java
index d0ed6f8..988fa49 100644
--- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/ScheduledDeliveryHandlerTest.java
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/ScheduledDeliveryHandlerTest.java
@@ -36,12 +36,12 @@ import org.apache.activemq.artemis.api.core.ActiveMQException;
 import org.apache.activemq.artemis.api.core.ActiveMQPropertyConversionException;
 import org.apache.activemq.artemis.api.core.Message;
 import org.apache.activemq.artemis.api.core.Pair;
-import org.apache.activemq.artemis.api.core.RefCountMessage;
 import org.apache.activemq.artemis.api.core.RoutingType;
 import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.core.filter.Filter;
 import org.apache.activemq.artemis.core.message.impl.CoreMessage;
-import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
+import org.apache.activemq.artemis.core.paging.PagingStore;
 import org.apache.activemq.artemis.core.paging.cursor.PageSubscription;
 import org.apache.activemq.artemis.core.persistence.OperationContext;
 import org.apache.activemq.artemis.core.persistence.Persister;
@@ -290,7 +290,7 @@ public class ScheduledDeliveryHandlerTest extends Assert {
       }
    }
 
-   class FakeMessage extends RefCountMessage {
+   class FakeMessage implements Message {
 
       @Override
       public SimpleString getReplyTo() {
@@ -318,6 +318,11 @@ public class ScheduledDeliveryHandlerTest extends Assert {
       }
 
       @Override
+      public int getDurableCount() {
+         return 0;
+      }
+
+      @Override
       public Long getScheduledDeliveryTime() {
          return null;
       }
@@ -328,7 +333,7 @@ public class ScheduledDeliveryHandlerTest extends Assert {
       }
 
       @Override
-      public Persister<Message, CoreMessageObjectPools> getPersister() {
+      public Persister<Message> getPersister() {
          return null;
       }
 
@@ -363,22 +368,12 @@ public class ScheduledDeliveryHandlerTest extends Assert {
       }
 
       @Override
-      public int incrementRefCount() throws Exception {
+      public int durableUp() {
          return 0;
       }
 
       @Override
-      public int decrementRefCount() throws Exception {
-         return 0;
-      }
-
-      @Override
-      public int incrementDurableRefCount() {
-         return 0;
-      }
-
-      @Override
-      public int decrementDurableRefCount() {
+      public int durableDown() {
          return 0;
       }
 
@@ -777,6 +772,31 @@ public class ScheduledDeliveryHandlerTest extends Assert {
       }
 
       @Override
+      public int getUsage() {
+         return 0;
+      }
+
+      @Override
+      public int usageUp() {
+         return 0;
+      }
+
+      @Override
+      public int usageDown() {
+         return 0;
+      }
+
+      @Override
+      public int refUp() {
+         return 0;
+      }
+
+      @Override
+      public int refDown() {
+         return 0;
+      }
+
+      @Override
       public void sendBuffer(ByteBuf buffer, int count) {
 
       }
@@ -795,6 +815,31 @@ public class ScheduledDeliveryHandlerTest extends Assert {
       }
 
       @Override
+      public PagingStore getPagingStore() {
+         return null;
+      }
+
+      @Override
+      public int durableUp(Message message) {
+         return 1;
+      }
+
+      @Override
+      public int durableDown(Message message) {
+         return 1;
+      }
+
+      @Override
+      public void refUp(Message message) {
+
+      }
+
+      @Override
+      public void refDown(Message message) {
+
+      }
+
+      @Override
       public void removeAddress() throws Exception {
       }
 
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/transaction/impl/TransactionImplTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/transaction/impl/TransactionImplTest.java
index 7c0ce44..471885c 100644
--- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/transaction/impl/TransactionImplTest.java
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/transaction/impl/TransactionImplTest.java
@@ -26,6 +26,7 @@ import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
 import org.apache.activemq.artemis.api.core.ActiveMQException;
 import org.apache.activemq.artemis.api.core.Message;
 import org.apache.activemq.artemis.api.core.Pair;
@@ -241,6 +242,16 @@ public class TransactionImplTest extends ActiveMQTestBase {
       }
 
       @Override
+      public void deleteLargeMessageBody(LargeServerMessage largeServerMessage) throws ActiveMQException {
+
+      }
+
+      @Override
+      public void addBytesToLargeMessage(SequentialFile file, long messageId, ActiveMQBuffer bytes) throws Exception {
+
+      }
+
+      @Override
       public void pageClosed(SimpleString storeName, int pageNumber) {
 
       }
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/tests/util/ActiveMQTestBase.java b/artemis-server/src/test/java/org/apache/activemq/artemis/tests/util/ActiveMQTestBase.java
index 1c86271..1af3400 100644
--- a/artemis-server/src/test/java/org/apache/activemq/artemis/tests/util/ActiveMQTestBase.java
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/tests/util/ActiveMQTestBase.java
@@ -1942,26 +1942,16 @@ public abstract class ActiveMQTestBase extends Assert {
       return false;
    }
 
+   protected int getNumberOfFiles(File directory) {
+      return directory.listFiles().length;
+   }
    /**
     * Deleting a file on LargeDir is an asynchronous process. We need to keep looking for a while if
     * the file hasn't been deleted yet.
     */
    protected void validateNoFilesOnLargeDir(final String directory, final int expect) throws Exception {
       File largeMessagesFileDir = new File(directory);
-
-      // Deleting the file is async... we keep looking for a period of the time until the file is really gone
-      long timeout = System.currentTimeMillis() + 5000;
-      while (timeout > System.currentTimeMillis() && largeMessagesFileDir.listFiles().length != expect) {
-         Thread.sleep(100);
-      }
-
-      if (expect != largeMessagesFileDir.listFiles().length) {
-         for (File file : largeMessagesFileDir.listFiles()) {
-            System.out.println("File " + file + " still on ");
-         }
-      }
-
-      Assert.assertEquals(expect, largeMessagesFileDir.listFiles().length);
+      Wait.assertEquals(expect, () -> getNumberOfFiles(largeMessagesFileDir));
    }
 
    /**
diff --git a/docs/user-manual/en/large-messages.md b/docs/user-manual/en/large-messages.md
index 26188af..37b03c4 100644
--- a/docs/user-manual/en/large-messages.md
+++ b/docs/user-manual/en/large-messages.md
@@ -1,24 +1,11 @@
 # Large Messages
... 1520 lines suppressed ...