activemq-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From clebertsuco...@apache.org
Subject [activemq-artemis] 02/03: ARTEMIS-2306 Make key used for group first for consumer configurable
Date Tue, 16 Apr 2019 19:43:26 GMT
This is an automated email from the ASF dual-hosted git repository.

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

commit 98b3177e6e21875def5157d1a419674cafee45dd
Author: Michael André Pearce <michael.andre.pearce@me.com>
AuthorDate: Tue Apr 16 13:47:19 2019 +0100

    ARTEMIS-2306 Make key used for group first for consumer configurable
    
    Add tests
    Add implementation details
---
 .../activemq/artemis/api/core/QueueAttributes.java |  13 ++
 .../artemis/api/core/client/ClientSession.java     |   2 +
 .../api/core/management/ActiveMQServerControl.java |  74 +++++++++++
 .../artemis/core/client/impl/QueueQueryImpl.java   |  37 ++++++
 .../protocol/core/impl/ActiveMQSessionContext.java |   3 +-
 .../impl/wireformat/CreateQueueMessage_V2.java     |  24 ++++
 .../wireformat/CreateSharedQueueMessage_V2.java    |  23 +++-
 .../SessionQueueQueryResponseMessage_V3.java       |  31 ++++-
 .../artemis/core/server/QueueQueryResult.java      |   9 ++
 .../core/config/CoreQueueConfiguration.java        |  20 +++
 .../deployers/impl/FileConfigurationParser.java    |   8 ++
 .../management/impl/ActiveMQServerControlImpl.java |  69 ++++++++++-
 .../artemis/core/persistence/QueueBindingInfo.java |   2 +
 .../journal/AbstractJournalStorageManager.java     |   2 +-
 .../codec/PersistentQueueBindingEncoding.java      |  21 +++-
 .../artemis/core/postoffice/PostOffice.java        |   1 +
 .../core/postoffice/impl/PostOfficeImpl.java       |   5 +
 .../protocol/core/ServerSessionPacketHandler.java  |   4 +-
 .../artemis/core/server/ActiveMQServer.java        |  52 ++++++++
 .../apache/activemq/artemis/core/server/Queue.java |   4 +
 .../activemq/artemis/core/server/QueueConfig.java  |  20 ++-
 .../artemis/core/server/ServerSession.java         |  42 +++++++
 .../core/server/impl/ActiveMQServerImpl.java       | 132 ++++++++++++++++++--
 .../artemis/core/server/impl/LastValueQueue.java   |   3 +-
 .../core/server/impl/PostOfficeJournalLoader.java  |   1 +
 .../artemis/core/server/impl/QueueFactoryImpl.java |   6 +-
 .../artemis/core/server/impl/QueueImpl.java        |  46 ++++++-
 .../core/server/impl/ServerSessionImpl.java        |  66 ++++++++--
 .../core/settings/impl/AddressSettings.java        |  18 +++
 .../resources/schema/artemis-configuration.xsd     |  10 ++
 .../server/impl/ScheduledDeliveryHandlerTest.java  |  10 ++
 .../src/test/resources/artemis-configuration.xsd   |  10 ++
 .../tests/integration/jms/client/GroupingTest.java | 136 +++++++++++++++++++++
 .../ActiveMQServerControlUsingCoreTest.java        |  12 +-
 .../tests/unit/core/postoffice/impl/FakeQueue.java |  10 ++
 .../core/server/impl/fakes/FakePostOffice.java     |   1 +
 36 files changed, 886 insertions(+), 41 deletions(-)

diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/QueueAttributes.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/QueueAttributes.java
index b65a3c3..17da20f 100644
--- a/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/QueueAttributes.java
+++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/QueueAttributes.java
@@ -28,6 +28,7 @@ public class QueueAttributes implements Serializable {
    public static final String EXCLUSIVE = "exclusive";
    public static final String GROUP_REBALANCE = "group-rebalance";
    public static final String GROUP_BUCKETS = "group-buckets";
+   public static final String GROUP_FIRST_KEY = "group-first-key";
    public static final String LAST_VALUE = "last-value";
    public static final String LAST_VALUE_KEY = "last-value-key";
    public static final String NON_DESTRUCTIVE = "non-destructive";
@@ -46,6 +47,7 @@ public class QueueAttributes implements Serializable {
    private Boolean exclusive;
    private Boolean groupRebalance;
    private Integer groupBuckets;
+   private SimpleString groupFirstKey;
    private Boolean lastValue;
    private SimpleString lastValueKey;
    private Boolean nonDestructive;
@@ -88,6 +90,8 @@ public class QueueAttributes implements Serializable {
             setGroupRebalance(Boolean.valueOf(value));
          } else if (key.equals(GROUP_BUCKETS)) {
             setGroupBuckets(Integer.valueOf(value));
+         } else if (key.equals(GROUP_FIRST_KEY)) {
+            setGroupFirstKey(SimpleString.toSimpleString(value));
          } else if (key.equals(AUTO_DELETE)) {
             setAutoDelete(Boolean.valueOf(value));
          } else if (key.equals(AUTO_DELETE_DELAY)) {
@@ -224,6 +228,15 @@ public class QueueAttributes implements Serializable {
       return this;
    }
 
+   public SimpleString getGroupFirstKey() {
+      return groupFirstKey;
+   }
+
+   public QueueAttributes setGroupFirstKey(SimpleString groupFirstKey) {
+      this.groupFirstKey = groupFirstKey;
+      return this;
+   }
+
    public Boolean getAutoDelete() {
       return autoDelete;
    }
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/client/ClientSession.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/client/ClientSession.java
index 70bbf65..f0f988c 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/client/ClientSession.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/client/ClientSession.java
@@ -163,6 +163,8 @@ public interface ClientSession extends XAResource, AutoCloseable {
 
       Integer getGroupBuckets();
 
+      SimpleString getGroupFirstKey();
+
       Boolean isAutoDelete();
 
       Long getAutoDeleteDelay();
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQServerControl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQServerControl.java
index 2002935..e179d5c 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQServerControl.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQServerControl.java
@@ -665,6 +665,50 @@ public interface ActiveMQServerControl {
     * @param durable            is the queue durable?
     * @param maxConsumers       the maximum number of consumers allowed on this queue at any one time
     * @param purgeOnNoConsumers delete this queue when the last consumer disconnects
+    * @param exclusive if the queue should route exclusively to one consumer
+    * @param lastValue use last-value semantics
+    * @param consumersBeforeDispatch number of consumers needed before dispatch can start
+    * @param delayBeforeDispatch delay to wait before dispatching if number of consumers before dispatch is not met
+    * @param autoCreateAddress  create an address with default values should a matching address not be found
+    * @return a textual summary of the queue
+    * @throws Exception
+    */
+   @Operation(desc = "Create a queue", impact = MBeanOperationInfo.ACTION)
+   String createQueue(@Parameter(name = "address", desc = "Address of the queue") String address,
+                      @Parameter(name = "routingType", desc = "The routing type used for this address, MULTICAST or ANYCAST") String routingType,
+                      @Parameter(name = "name", desc = "Name of the queue") String name,
+                      @Parameter(name = "filter", desc = "Filter of the queue") String filterStr,
+                      @Parameter(name = "durable", desc = "Is the queue durable?") boolean durable,
+                      @Parameter(name = "maxConsumers", desc = "The maximum number of consumers allowed on this queue at any one time") int maxConsumers,
+                      @Parameter(name = "purgeOnNoConsumers", desc = "Delete this queue when the last consumer disconnects") boolean purgeOnNoConsumers,
+                      @Parameter(name = "exclusive", desc = "If the queue should route exclusively to one consumer") boolean exclusive,
+                      @Parameter(name = "groupRebalance", desc = "If the queue should rebalance groups when a consumer is added") boolean groupRebalance,
+                      @Parameter(name = "groupBuckets", desc = "Number of buckets that should be used for message groups, -1 (default) is unlimited, and groups by raw key instead") int groupBuckets,
+                      @Parameter(name = "groupFirstKey", desc = "Key used to mark a message is first in a group for a consumer") String groupFirstKey,
+                      @Parameter(name = "lastValue", desc = "Use last-value semantics") boolean lastValue,
+                      @Parameter(name = "lastValueKey", desc = "Use the specified property key for the last value") String lastValueKey,
+                      @Parameter(name = "nonDestructive", desc = "If the queue is non-destructive") boolean nonDestructive,
+                      @Parameter(name = "consumersBeforeDispatch", desc = "Number of consumers needed before dispatch can start") int consumersBeforeDispatch,
+                      @Parameter(name = "delayBeforeDispatch", desc = "Delay to wait before dispatching if number of consumers before dispatch is not met") long delayBeforeDispatch,
+                      @Parameter(name = "autoDelete", desc = "If the queue should be deleted once no consumers") boolean autoDelete,
+                      @Parameter(name = "autoDeleteDelay", desc = "How long to wait (in milliseconds) before deleting auto-created queues after the queue has 0 consumers") long autoDeleteDelay,
+                      @Parameter(name = "autoDeleteMessageCount", desc = "The message count the queue must be at or below before it can be evaluated to be auto deleted, 0 waits until empty queue (default) and -1 disables this check") long autoDeleteMessageCount,
+                      @Parameter(name = "autoCreateAddress", desc = "Create an address with default values should a matching address not be found") boolean autoCreateAddress) throws Exception;
+
+   /**
+    * Create a queue.
+    * <br>
+    * If {@code address} is {@code null} it will be defaulted to {@code name}.
+    * <br>
+    * This method throws a {@link org.apache.activemq.artemis.api.core.ActiveMQQueueExistsException}) exception if the queue already exits.
+    *
+    * @param address            address to bind the queue to
+    * @param routingType        the routing type used for this address, {@code MULTICAST} or {@code ANYCAST}
+    * @param name               name of the queue
+    * @param filterStr          filter of the queue
+    * @param durable            is the queue durable?
+    * @param maxConsumers       the maximum number of consumers allowed on this queue at any one time
+    * @param purgeOnNoConsumers delete this queue when the last consumer disconnects
     * @param autoCreateAddress  create an address with default values should a matching address not be found
     * @return a textual summary of the queue
     * @throws Exception
@@ -766,6 +810,36 @@ public interface ActiveMQServerControl {
                       @Parameter(name = "user", desc = "The user associated with this queue") String user) throws Exception;
 
    /**
+    * Update a queue
+    *
+    * @param name               name of the queue
+    * @param routingType        the routing type used for this address, {@code MULTICAST} or {@code ANYCAST}
+    * @param maxConsumers       the maximum number of consumers allowed on this queue at any one time
+    * @param purgeOnNoConsumers delete this queue when the last consumer disconnects
+    * @param exclusive          if the queue should route exclusively to one consumer
+    * @param nonDestructive     If the queue is non-destructive
+    * @param consumersBeforeDispatch number of consumers needed before dispatch can start
+    * @param delayBeforeDispatch delay to wait before dispatching if number of consumers before dispatch is not met
+    * @param user               the user associated with this queue
+    * @return
+    * @throws Exception
+    */
+   @Operation(desc = "Update a queue", impact = MBeanOperationInfo.ACTION)
+   String updateQueue(@Parameter(name = "name", desc = "Name of the queue") String name,
+                      @Parameter(name = "routingType", desc = "The routing type used for this address, MULTICAST or ANYCAST") String routingType,
+                      @Parameter(name = "filter", desc = "The filter to use on the queue") String filter,
+                      @Parameter(name = "maxConsumers", desc = "The maximum number of consumers allowed on this queue at any one time") Integer maxConsumers,
+                      @Parameter(name = "purgeOnNoConsumers", desc = "Delete this queue when the last consumer disconnects") Boolean purgeOnNoConsumers,
+                      @Parameter(name = "exclusive", desc = "If the queue should route exclusively to one consumer") Boolean exclusive,
+                      @Parameter(name = "groupRebalance", desc = "If the queue should rebalance groups when a consumer is added") Boolean groupRebalance,
+                      @Parameter(name = "groupBuckets", desc = "Number of buckets that should be used for message groups, -1 (default) is unlimited, and groups by raw key instead") Integer groupBuckets,
+                      @Parameter(name = "groupFirstKey", desc = "Key used to mark a message is first in a group for a consumer") String groupFirstKey,
+                      @Parameter(name = "nonDestructive", desc = "If the queue is non-destructive") Boolean nonDestructive,
+                      @Parameter(name = "consumersBeforeDispatch", desc = "Number of consumers needed before dispatch can start") Integer consumersBeforeDispatch,
+                      @Parameter(name = "delayBeforeDispatch", desc = "Delay to wait before dispatching if number of consumers before dispatch is not met") Long delayBeforeDispatch,
+                      @Parameter(name = "user", desc = "The user associated with this queue") String user) throws Exception;
+
+   /**
     * Deploy a durable queue.
     * <br>
     * If {@code address} is {@code null} it will be defaulted to {@code name}.
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/QueueQueryImpl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/QueueQueryImpl.java
index e017584..7213313 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/QueueQueryImpl.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/QueueQueryImpl.java
@@ -54,6 +54,8 @@ public class QueueQueryImpl implements ClientSession.QueueQuery {
 
    private final Integer groupBuckets;
 
+   private final SimpleString groupFirstKey;
+
    private final Boolean lastValue;
 
    private final SimpleString lastValueKey;
@@ -156,6 +158,35 @@ public class QueueQueryImpl implements ClientSession.QueueQuery {
                          final Long autoDeleteDelay,
                          final Long autoDeleteMessageCount,
                          final Integer defaultConsumerWindowSize) {
+      this(durable, temporary, consumerCount, messageCount, filterString, address, name, exists, autoCreateQueues, maxConsumers, autoCreated, purgeOnNoConsumers, routingType, exclusive, groupRebalance, groupBuckets, null, lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, autoDelete, autoDeleteDelay, autoDeleteMessageCount, defaultConsumerWindowSize);
+   }
+
+   public QueueQueryImpl(final boolean durable,
+                         final boolean temporary,
+                         final int consumerCount,
+                         final long messageCount,
+                         final SimpleString filterString,
+                         final SimpleString address,
+                         final SimpleString name,
+                         final boolean exists,
+                         final boolean autoCreateQueues,
+                         final int maxConsumers,
+                         final boolean autoCreated,
+                         final boolean purgeOnNoConsumers,
+                         final RoutingType routingType,
+                         final Boolean exclusive,
+                         final Boolean groupRebalance,
+                         final Integer groupBuckets,
+                         final SimpleString groupFirstKey,
+                         final Boolean lastValue,
+                         final SimpleString lastValueKey,
+                         final Boolean nonDestructive,
+                         final Integer consumersBeforeDispatch,
+                         final Long delayBeforeDispatch,
+                         final Boolean autoDelete,
+                         final Long autoDeleteDelay,
+                         final Long autoDeleteMessageCount,
+                         final Integer defaultConsumerWindowSize) {
       this.durable = durable;
       this.temporary = temporary;
       this.consumerCount = consumerCount;
@@ -172,6 +203,7 @@ public class QueueQueryImpl implements ClientSession.QueueQuery {
       this.exclusive = exclusive;
       this.groupRebalance = groupRebalance;
       this.groupBuckets = groupBuckets;
+      this.groupFirstKey = groupFirstKey;
       this.lastValue = lastValue;
       this.lastValueKey = lastValueKey;
       this.nonDestructive = nonDestructive;
@@ -294,6 +326,11 @@ public class QueueQueryImpl implements ClientSession.QueueQuery {
    }
 
    @Override
+   public SimpleString getGroupFirstKey() {
+      return groupFirstKey;
+   }
+
+   @Override
    public Boolean isAutoDelete() {
       return autoDelete;
    }
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQSessionContext.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQSessionContext.java
index 6f16762..1709a3a 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQSessionContext.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQSessionContext.java
@@ -316,6 +316,7 @@ public class ActiveMQSessionContext extends SessionContext {
               queueAttributes.getExclusive(),
               queueAttributes.getGroupRebalance(),
               queueAttributes.getGroupBuckets(),
+              queueAttributes.getGroupFirstKey(),
               queueAttributes.getLastValue(),
               queueAttributes.getLastValueKey(),
               queueAttributes.getNonDestructive(),
@@ -876,7 +877,7 @@ public class ActiveMQSessionContext extends SessionContext {
       // We try to recreate any non-durable or auto-created queues, since they might not be there on failover/reconnect.
       // This allows e.g. JMS non durable subs and temporary queues to continue to be used after failover/reconnection
       if (!queueInfo.isDurable() || queueInfo.isAutoCreated()) {
-         CreateQueueMessage_V2 createQueueRequest = new CreateQueueMessage_V2(queueInfo.getAddress(), queueInfo.getName(), queueInfo.getRoutingType(), queueInfo.getFilterString(), queueInfo.isDurable(), queueInfo.isTemporary(), queueInfo.getMaxConsumers(), queueInfo.isPurgeOnNoConsumers(), queueInfo.isAutoCreated(), false, queueInfo.isExclusive(), queueInfo.isGroupRebalance(), queueInfo.getGroupBuckets(), queueInfo.isLastValue(), queueInfo.getLastValueKey(), queueInfo.isNonDestructive(), [...]
+         CreateQueueMessage_V2 createQueueRequest = new CreateQueueMessage_V2(queueInfo.getAddress(), queueInfo.getName(), queueInfo.getRoutingType(), queueInfo.getFilterString(), queueInfo.isDurable(), queueInfo.isTemporary(), queueInfo.getMaxConsumers(), queueInfo.isPurgeOnNoConsumers(), queueInfo.isAutoCreated(), false, queueInfo.isExclusive(), queueInfo.isGroupRebalance(), queueInfo.getGroupBuckets(), queueInfo.getGroupFirstKey(), queueInfo.isLastValue(), queueInfo.getLastValueKey(), [...]
 
          sendPacketWithoutLock(sessionChannel, createQueueRequest);
       }
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CreateQueueMessage_V2.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CreateQueueMessage_V2.java
index ef8cce2..0c84267 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CreateQueueMessage_V2.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CreateQueueMessage_V2.java
@@ -38,6 +38,8 @@ public class CreateQueueMessage_V2 extends CreateQueueMessage {
 
    private Integer groupBuckets;
 
+   private SimpleString groupFirstKey;
+
    private Boolean lastValue;
 
    private SimpleString lastValueKey;
@@ -74,6 +76,7 @@ public class CreateQueueMessage_V2 extends CreateQueueMessage {
          queueAttributes.getExclusive(),
          queueAttributes.getGroupRebalance(),
          queueAttributes.getGroupBuckets(),
+         queueAttributes.getGroupFirstKey(),
          queueAttributes.getLastValue(),
          queueAttributes.getLastValueKey(),
          queueAttributes.getNonDestructive(),
@@ -98,6 +101,7 @@ public class CreateQueueMessage_V2 extends CreateQueueMessage {
                                 final Boolean exclusive,
                                 final Boolean groupRebalance,
                                 final Integer groupBuckets,
+                                final SimpleString groupFirstKey,
                                 final Boolean lastValue,
                                 final SimpleString lastValueKey,
                                 final Boolean nonDestructive,
@@ -121,6 +125,7 @@ public class CreateQueueMessage_V2 extends CreateQueueMessage {
       this.exclusive = exclusive;
       this.groupRebalance = groupRebalance;
       this.groupBuckets = groupBuckets;
+      this.groupFirstKey = groupFirstKey;
       this.lastValue = lastValue;
       this.lastValueKey = lastValueKey;
       this.nonDestructive = nonDestructive;
@@ -147,6 +152,7 @@ public class CreateQueueMessage_V2 extends CreateQueueMessage {
       buff.append(", exclusive=" + exclusive);
       buff.append(", groupRebalance=" + groupRebalance);
       buff.append(", groupBuckets=" + groupBuckets);
+      buff.append(", groupFirstKey=" + groupFirstKey);
       buff.append(", lastValue=" + lastValue);
       buff.append(", lastValueKey=" + lastValue);
       buff.append(", nonDestructive=" + nonDestructive);
@@ -256,6 +262,14 @@ public class CreateQueueMessage_V2 extends CreateQueueMessage {
       this.groupBuckets = groupBuckets;
    }
 
+   public SimpleString getGroupFirstKey() {
+      return groupFirstKey;
+   }
+
+   public void setGroupFirstKey(SimpleString groupFirstKey) {
+      this.groupFirstKey = groupFirstKey;
+   }
+
    public Boolean isAutoDelete() {
       return autoDelete;
    }
@@ -298,6 +312,7 @@ public class CreateQueueMessage_V2 extends CreateQueueMessage {
       BufferHelper.writeNullableBoolean(buffer, autoDelete);
       BufferHelper.writeNullableLong(buffer, autoDeleteDelay);
       BufferHelper.writeNullableLong(buffer, autoDeleteMessageCount);
+      buffer.writeNullableSimpleString(groupFirstKey);
 
    }
 
@@ -323,6 +338,9 @@ public class CreateQueueMessage_V2 extends CreateQueueMessage {
          autoDeleteDelay = BufferHelper.readNullableLong(buffer);
          autoDeleteMessageCount = BufferHelper.readNullableLong(buffer);
       }
+      if (buffer.readableBytes() > 0) {
+         groupFirstKey = buffer.readNullableSimpleString();
+      }
    }
 
    @Override
@@ -336,6 +354,7 @@ public class CreateQueueMessage_V2 extends CreateQueueMessage {
       result = prime * result + (exclusive == null ? 0 : exclusive ? 1231 : 1237);
       result = prime * result + (groupRebalance == null ? 0 : groupRebalance ? 1231 : 1237);
       result = prime * result + (groupBuckets == null ? 0 : groupBuckets.hashCode());
+      result = prime * result + (groupFirstKey == null ? 0 : groupFirstKey.hashCode());
       result = prime * result + (lastValue == null ? 0 : lastValue ? 1231 : 1237);
       result = prime * result + (lastValueKey == null ? 0 : lastValueKey.hashCode());
       result = prime * result + (nonDestructive == null ? 0 : nonDestructive ? 1231 : 1237);
@@ -377,6 +396,11 @@ public class CreateQueueMessage_V2 extends CreateQueueMessage {
             return false;
       } else if (!groupBuckets.equals(other.groupBuckets))
          return false;
+      if (groupFirstKey == null) {
+         if (other.groupFirstKey != null)
+            return false;
+      } else if (!groupFirstKey.equals(other.groupFirstKey))
+         return false;
       if (lastValue == null) {
          if (other.lastValue != null)
             return false;
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CreateSharedQueueMessage_V2.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CreateSharedQueueMessage_V2.java
index f1cff91..015b759 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CreateSharedQueueMessage_V2.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CreateSharedQueueMessage_V2.java
@@ -29,6 +29,7 @@ public class CreateSharedQueueMessage_V2 extends CreateSharedQueueMessage {
    private Boolean exclusive;
    private Boolean groupRebalance;
    private Integer groupBuckets;
+   private SimpleString groupFirstKey;
    private Boolean lastValue;
    private SimpleString lastValueKey;
    private Boolean nonDestructive;
@@ -48,6 +49,7 @@ public class CreateSharedQueueMessage_V2 extends CreateSharedQueueMessage {
                                       final Boolean exclusive,
                                       final Boolean groupRebalance,
                                       final Integer groupBuckets,
+                                      final SimpleString groupFirstKey,
                                       final Boolean lastValue,
                                       final SimpleString lastValueKey,
                                       final Boolean nonDestructive,
@@ -69,6 +71,7 @@ public class CreateSharedQueueMessage_V2 extends CreateSharedQueueMessage {
       this.exclusive = exclusive;
       this.groupRebalance = groupRebalance;
       this.groupBuckets = groupBuckets;
+      this.groupFirstKey = groupFirstKey;
       this.lastValue = lastValue;
       this.lastValueKey = lastValueKey;
       this.nonDestructive = nonDestructive;
@@ -172,6 +175,14 @@ public class CreateSharedQueueMessage_V2 extends CreateSharedQueueMessage {
       this.groupBuckets = groupBuckets;
    }
 
+   public SimpleString getGroupFirstKey() {
+      return groupFirstKey;
+   }
+
+   public void setGroupBuckets(SimpleString groupFirstKey) {
+      this.groupFirstKey = groupFirstKey;
+   }
+
    public Boolean isAutoDelete() {
       return autoDelete;
    }
@@ -209,6 +220,7 @@ public class CreateSharedQueueMessage_V2 extends CreateSharedQueueMessage {
       buff.append(", exclusive=" + exclusive);
       buff.append(", groupRebalance=" + groupRebalance);
       buff.append(", groupBuckets=" + groupBuckets);
+      buff.append(", groupFirstKey=" + groupFirstKey);
       buff.append(", lastValue=" + lastValue);
       buff.append(", lastValueKey=" + lastValueKey);
       buff.append(", nonDestructive=" + nonDestructive);
@@ -243,7 +255,7 @@ public class CreateSharedQueueMessage_V2 extends CreateSharedQueueMessage {
       BufferHelper.writeNullableBoolean(buffer, autoDelete);
       BufferHelper.writeNullableLong(buffer, autoDeleteDelay);
       BufferHelper.writeNullableLong(buffer, autoDeleteMessageCount);
-
+      buffer.writeNullableSimpleString(groupFirstKey);
    }
 
    @Override
@@ -271,6 +283,9 @@ public class CreateSharedQueueMessage_V2 extends CreateSharedQueueMessage {
          autoDeleteDelay = BufferHelper.readNullableLong(buffer);
          autoDeleteMessageCount = BufferHelper.readNullableLong(buffer);
       }
+      if (buffer.readableBytes() > 0) {
+         groupFirstKey = buffer.readNullableSimpleString();
+      }
    }
 
    @Override
@@ -288,6 +303,7 @@ public class CreateSharedQueueMessage_V2 extends CreateSharedQueueMessage {
       result = prime * result + (exclusive == null ? 0 : exclusive ? 1231 : 1237);
       result = prime * result + (groupRebalance == null ? 0 : groupRebalance ? 1231 : 1237);
       result = prime * result + (groupBuckets == null ? 0 : groupBuckets.hashCode());
+      result = prime * result + (groupFirstKey == null ? 0 : groupFirstKey.hashCode());
       result = prime * result + (lastValue == null ? 0 : lastValue ? 1231 : 1237);
       result = prime * result + (lastValueKey == null ? 0 : lastValueKey.hashCode());
       result = prime * result + (nonDestructive == null ? 0 : nonDestructive ? 1231 : 1237);
@@ -355,6 +371,11 @@ public class CreateSharedQueueMessage_V2 extends CreateSharedQueueMessage {
             return false;
       } else if (!groupBuckets.equals(other.groupBuckets))
          return false;
+      if (groupFirstKey == null) {
+         if (other.groupFirstKey != null)
+            return false;
+      } else if (!groupFirstKey.equals(other.groupFirstKey))
+         return false;
       if (lastValue == null) {
          if (other.lastValue != null)
             return false;
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/SessionQueueQueryResponseMessage_V3.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/SessionQueueQueryResponseMessage_V3.java
index 1a3a903..bf7ab05 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/SessionQueueQueryResponseMessage_V3.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/SessionQueueQueryResponseMessage_V3.java
@@ -40,6 +40,8 @@ public class SessionQueueQueryResponseMessage_V3 extends SessionQueueQueryRespon
 
    protected Integer groupBuckets;
 
+   protected SimpleString groupFirstKey;
+
    protected Boolean lastValue;
 
    protected SimpleString lastValueKey;
@@ -59,11 +61,11 @@ public class SessionQueueQueryResponseMessage_V3 extends SessionQueueQueryRespon
    protected Integer defaultConsumerWindowSize;
 
    public SessionQueueQueryResponseMessage_V3(final QueueQueryResult result) {
-      this(result.getName(), result.getAddress(), result.isDurable(), result.isTemporary(), result.getFilterString(), result.getConsumerCount(), result.getMessageCount(), result.isExists(), result.isAutoCreateQueues(), result.isAutoCreated(), result.isPurgeOnNoConsumers(), result.getRoutingType(), result.getMaxConsumers(), result.isExclusive(), result.isGroupRebalance(), result.getGroupBuckets(), result.isLastValue(), result.getLastValueKey(), result.isNonDestructive(), result.getConsume [...]
+      this(result.getName(), result.getAddress(), result.isDurable(), result.isTemporary(), result.getFilterString(), result.getConsumerCount(), result.getMessageCount(), result.isExists(), result.isAutoCreateQueues(), result.isAutoCreated(), result.isPurgeOnNoConsumers(), result.getRoutingType(), result.getMaxConsumers(), result.isExclusive(), result.isGroupRebalance(), result.getGroupBuckets(), result.getGroupFirstKey(), result.isLastValue(), result.getLastValueKey(), result.isNonDestr [...]
    }
 
    public SessionQueueQueryResponseMessage_V3() {
-      this(null, null, false, false, null, 0, 0, false, false, false, false, RoutingType.MULTICAST, -1, null, null,null, null, null, null, null, null, null, null, null, null);
+      this(null, null, false, false, null, 0, 0, false, false, false, false, RoutingType.MULTICAST, -1, null, null,null, null, null, null, null, null, null, null, null, null, null);
    }
 
    private SessionQueueQueryResponseMessage_V3(final SimpleString name,
@@ -82,6 +84,7 @@ public class SessionQueueQueryResponseMessage_V3 extends SessionQueueQueryRespon
                                                final Boolean exclusive,
                                                final Boolean groupRebalance,
                                                final Integer groupBuckets,
+                                               final SimpleString groupFirstKey,
                                                final Boolean lastValue,
                                                final SimpleString lastValueKey,
                                                final Boolean nonDestructive,
@@ -125,6 +128,8 @@ public class SessionQueueQueryResponseMessage_V3 extends SessionQueueQueryRespon
 
       this.groupBuckets = groupBuckets;
 
+      this.groupFirstKey = groupFirstKey;
+
       this.lastValue = lastValue;
 
       this.lastValueKey = lastValueKey;
@@ -248,6 +253,14 @@ public class SessionQueueQueryResponseMessage_V3 extends SessionQueueQueryRespon
       this.groupBuckets = groupBuckets;
    }
 
+   public SimpleString getGroupFirstKey() {
+      return groupFirstKey;
+   }
+
+   public void setGroupFirstKey(SimpleString groupFirstKey) {
+      this.groupFirstKey = groupFirstKey;
+   }
+
    public Boolean isAutoDelete() {
       return autoDelete;
    }
@@ -279,6 +292,7 @@ public class SessionQueueQueryResponseMessage_V3 extends SessionQueueQueryRespon
       BufferHelper.writeNullableBoolean(buffer, autoDelete);
       BufferHelper.writeNullableLong(buffer, autoDeleteDelay);
       BufferHelper.writeNullableLong(buffer, autoDeleteMessageCount);
+      buffer.writeNullableSimpleString(groupFirstKey);
 
    }
 
@@ -306,7 +320,9 @@ public class SessionQueueQueryResponseMessage_V3 extends SessionQueueQueryRespon
          autoDelete = BufferHelper.readNullableBoolean(buffer);
          autoDeleteDelay = BufferHelper.readNullableLong(buffer);
          autoDeleteMessageCount = BufferHelper.readNullableLong(buffer);
-
+      }
+      if (buffer.readableBytes() > 0) {
+         groupFirstKey = buffer.readNullableSimpleString();
       }
    }
 
@@ -321,6 +337,7 @@ public class SessionQueueQueryResponseMessage_V3 extends SessionQueueQueryRespon
       result = prime * result + (exclusive == null ? 0 : exclusive ? 1231 : 1237);
       result = prime * result + (groupRebalance == null ? 0 : groupRebalance ? 1231 : 1237);
       result = prime * result + (groupBuckets == null ? 0 : groupBuckets.hashCode());
+      result = prime * result + (groupFirstKey == null ? 0 : groupFirstKey.hashCode());
       result = prime * result + (lastValue == null ? 0 : lastValue ? 1231 : 1237);
       result = prime * result + (lastValueKey == null ? 0 : lastValueKey.hashCode());
       result = prime * result + (nonDestructive == null ? 0 : nonDestructive ? 1231 : 1237);
@@ -350,6 +367,7 @@ public class SessionQueueQueryResponseMessage_V3 extends SessionQueueQueryRespon
       buff.append(", exclusive=" + exclusive);
       buff.append(", groupRebalance=" + groupRebalance);
       buff.append(", groupBuckets=" + groupBuckets);
+      buff.append(", groupFirstKey=" + groupFirstKey);
       buff.append(", lastValue=" + lastValue);
       buff.append(", lastValueKey=" + lastValueKey);
       buff.append(", nonDestructive=" + nonDestructive);
@@ -364,7 +382,7 @@ public class SessionQueueQueryResponseMessage_V3 extends SessionQueueQueryRespon
 
    @Override
    public ClientSession.QueueQuery toQueueQuery() {
-      return new QueueQueryImpl(isDurable(), isTemporary(), getConsumerCount(), getMessageCount(), getFilterString(), getAddress(), getName(), isExists(), isAutoCreateQueues(), getMaxConsumers(), isAutoCreated(), isPurgeOnNoConsumers(), getRoutingType(), isExclusive(), isGroupRebalance(), getGroupBuckets(), isLastValue(), getLastValueKey(), isNonDestructive(), getConsumersBeforeDispatch(), getDelayBeforeDispatch(), isAutoDelete(), getAutoDeleteDelay(), getAutoDeleteMessageCount(), getDef [...]
+      return new QueueQueryImpl(isDurable(), isTemporary(), getConsumerCount(), getMessageCount(), getFilterString(), getAddress(), getName(), isExists(), isAutoCreateQueues(), getMaxConsumers(), isAutoCreated(), isPurgeOnNoConsumers(), getRoutingType(), isExclusive(), isGroupRebalance(), getGroupBuckets(), getGroupFirstKey(), isLastValue(), getLastValueKey(), isNonDestructive(), getConsumersBeforeDispatch(), getDelayBeforeDispatch(), isAutoDelete(), getAutoDeleteDelay(), getAutoDeleteMe [...]
    }
 
    @Override
@@ -395,6 +413,11 @@ public class SessionQueueQueryResponseMessage_V3 extends SessionQueueQueryRespon
             return false;
       } else if (!groupBuckets.equals(other.groupBuckets))
          return false;
+      if (groupFirstKey == null) {
+         if (other.groupFirstKey != null)
+            return false;
+      } else if (!groupFirstKey.equals(other.groupFirstKey))
+         return false;
       if (lastValue == null) {
          if (other.lastValue != null)
             return false;
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/server/QueueQueryResult.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/server/QueueQueryResult.java
index 1d84f3e..5493f8e 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/server/QueueQueryResult.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/server/QueueQueryResult.java
@@ -53,6 +53,8 @@ public class QueueQueryResult {
 
    private Integer groupBuckets;
 
+   private SimpleString groupFirstKey;
+
    private Boolean lastValue;
 
    private SimpleString lastValueKey;
@@ -87,6 +89,7 @@ public class QueueQueryResult {
                            final Boolean exclusive,
                            final Boolean groupRebalance,
                            final Integer groupBuckets,
+                           final SimpleString groupFirstKey,
                            final Boolean lastValue,
                            final SimpleString lastValueKey,
                            final Boolean nonDestructive,
@@ -128,6 +131,8 @@ public class QueueQueryResult {
 
       this.groupBuckets = groupBuckets;
 
+      this.groupFirstKey = groupFirstKey;
+
       this.lastValue = lastValue;
 
       this.lastValueKey = lastValueKey;
@@ -239,6 +244,10 @@ public class QueueQueryResult {
       return groupBuckets;
    }
 
+   public SimpleString getGroupFirstKey() {
+      return groupFirstKey;
+   }
+
    public Boolean isAutoDelete() {
       return autoDelete;
    }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/CoreQueueConfiguration.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/CoreQueueConfiguration.java
index 0a3fde8..07ce8a6 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/CoreQueueConfiguration.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/CoreQueueConfiguration.java
@@ -41,6 +41,8 @@ public class CoreQueueConfiguration implements Serializable {
 
    private Integer groupBuckets;
 
+   private String groupFirstKey;
+
    private Boolean lastValue;
 
    private String lastValueKey;
@@ -92,6 +94,10 @@ public class CoreQueueConfiguration implements Serializable {
       return groupBuckets;
    }
 
+   public String getGroupFirstKey() {
+      return groupFirstKey;
+   }
+
    public Boolean isLastValue() {
       return lastValue;
    }
@@ -199,6 +205,11 @@ public class CoreQueueConfiguration implements Serializable {
       return this;
    }
 
+   public CoreQueueConfiguration setGroupFirstKey(String groupFirstKey) {
+      this.groupFirstKey = groupFirstKey;
+      return this;
+   }
+
    public CoreQueueConfiguration setLastValue(Boolean lastValue) {
       this.lastValue = lastValue;
       return this;
@@ -244,6 +255,7 @@ public class CoreQueueConfiguration implements Serializable {
       result = prime * result + ((exclusive == null) ? 0 : exclusive.hashCode());
       result = prime * result + ((groupRebalance == null) ? 0 : groupRebalance.hashCode());
       result = prime * result + ((groupBuckets == null) ? 0 : groupBuckets.hashCode());
+      result = prime * result + ((groupFirstKey == null) ? 0 : groupFirstKey.hashCode());
       result = prime * result + ((lastValue == null) ? 0 : lastValue.hashCode());
       result = prime * result + ((lastValueKey == null) ? 0 : lastValueKey.hashCode());
       result = prime * result + ((nonDestructive == null) ? 0 : nonDestructive.hashCode());
@@ -311,6 +323,13 @@ public class CoreQueueConfiguration implements Serializable {
          return false;
       }
 
+      if (groupFirstKey == null) {
+         if (other.groupFirstKey != null)
+            return false;
+      } else if (!groupFirstKey.equals(other.groupFirstKey)) {
+         return false;
+      }
+
       if (lastValue == null) {
          if (other.lastValue != null)
             return false;
@@ -363,6 +382,7 @@ public class CoreQueueConfiguration implements Serializable {
          ", exclusive=" + exclusive +
          ", groupRebalance=" + groupRebalance +
          ", groupBuckets=" + groupBuckets +
+         ", groupFirstKey=" + groupFirstKey +
          ", lastValue=" + lastValue +
          ", lastValueKey=" + lastValueKey +
          ", nonDestructive=" + nonDestructive +
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java
index ffd598d..30a3ab0 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java
@@ -196,6 +196,8 @@ public final class FileConfigurationParser extends XMLConfigurationUtil {
 
    private static final String DEFAULT_GROUP_BUCKETS = "default-group-buckets";
 
+   private static final String DEFAULT_GROUP_FIRST_KEY = "default-group-first-key";
+
    private static final String DEFAULT_CONSUMERS_BEFORE_DISPATCH = "default-consumers-before-dispatch";
 
    private static final String DEFAULT_DELAY_BEFORE_DISPATCH = "default-delay-before-dispatch";
@@ -1043,6 +1045,8 @@ public final class FileConfigurationParser extends XMLConfigurationUtil {
             addressSettings.setDefaultGroupRebalance(XMLUtil.parseBoolean(child));
          } else if (DEFAULT_GROUP_BUCKETS.equalsIgnoreCase(name)) {
             addressSettings.setDefaultGroupBuckets(XMLUtil.parseInt(child));
+         } else if (DEFAULT_GROUP_FIRST_KEY.equalsIgnoreCase(name)) {
+            addressSettings.setDefaultGroupFirstKey(SimpleString.toSimpleString(getTrimmedTextContent(child)));
          } else if (MAX_DELIVERY_ATTEMPTS.equalsIgnoreCase(name)) {
             addressSettings.setMaxDeliveryAttempts(XMLUtil.parseInt(child));
          } else if (REDISTRIBUTION_DELAY_NODE_NAME.equalsIgnoreCase(name)) {
@@ -1165,6 +1169,7 @@ public final class FileConfigurationParser extends XMLConfigurationUtil {
       Boolean exclusive = null;
       Boolean groupRebalance = null;
       Integer groupBuckets = null;
+      String groupFirstKey = null;
       Boolean lastValue = null;
       String lastValueKey = null;
       Boolean nonDestructive = null;
@@ -1185,6 +1190,8 @@ public final class FileConfigurationParser extends XMLConfigurationUtil {
             groupRebalance = Boolean.parseBoolean(item.getNodeValue());
          } else if (item.getNodeName().equals("group-buckets")) {
             groupBuckets = Integer.parseInt(item.getNodeValue());
+         } else if (item.getNodeName().equals("group-first-key")) {
+            groupFirstKey = item.getNodeValue();
          } else if (item.getNodeName().equals("last-value")) {
             lastValue = Boolean.parseBoolean(item.getNodeValue());
          } else if (item.getNodeName().equals("last-value-key")) {
@@ -1224,6 +1231,7 @@ public final class FileConfigurationParser extends XMLConfigurationUtil {
               .setExclusive(exclusive)
               .setGroupRebalance(groupRebalance)
               .setGroupBuckets(groupBuckets)
+              .setGroupFirstKey(groupFirstKey)
               .setLastValue(lastValue)
               .setLastValueKey(lastValueKey)
               .setNonDestructive(nonDestructive)
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ActiveMQServerControlImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ActiveMQServerControlImpl.java
index 042279c..db34645 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ActiveMQServerControlImpl.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ActiveMQServerControlImpl.java
@@ -1022,6 +1022,52 @@ public class ActiveMQServerControlImpl extends AbstractControl implements Active
                              long autoDeleteDelay,
                              long autoDeleteMessageCount,
                              boolean autoCreateAddress) throws Exception {
+      AddressSettings addressSettings = server.getAddressSettingsRepository().getMatch(address == null ? name : address);
+      return createQueue(
+            address,
+            routingType,
+            name,
+            filterStr,
+            durable,
+            maxConsumers,
+            purgeOnNoConsumers,
+            exclusive,
+            groupRebalance,
+            groupBuckets,
+            addressSettings.getDefaultGroupFirstKey() == null ? null : addressSettings.getDefaultGroupFirstKey().toString(),
+            lastValue,
+            lastValueKey,
+            nonDestructive,
+            consumersBeforeDispatch,
+            delayBeforeDispatch,
+            autoDelete,
+            autoDeleteDelay,
+            autoDeleteMessageCount,
+            autoCreateAddress
+      );
+   }
+
+   @Override
+   public String createQueue(String address,
+                             String routingType,
+                             String name,
+                             String filterStr,
+                             boolean durable,
+                             int maxConsumers,
+                             boolean purgeOnNoConsumers,
+                             boolean exclusive,
+                             boolean groupRebalance,
+                             int groupBuckets,
+                             String groupFirstKey,
+                             boolean lastValue,
+                             String lastValueKey,
+                             boolean nonDestructive,
+                             int consumersBeforeDispatch,
+                             long delayBeforeDispatch,
+                             boolean autoDelete,
+                             long autoDeleteDelay,
+                             long autoDeleteMessageCount,
+                             boolean autoCreateAddress) throws Exception {
       if (AuditLogger.isEnabled()) {
          AuditLogger.createQueue(this.server, address, routingType, name, filterStr, durable,
                   maxConsumers, purgeOnNoConsumers, exclusive, groupBuckets, lastValue,
@@ -1038,7 +1084,7 @@ public class ActiveMQServerControlImpl extends AbstractControl implements Active
             filter = new SimpleString(filterStr);
          }
 
-         final Queue queue = server.createQueue(SimpleString.toSimpleString(address), RoutingType.valueOf(routingType.toUpperCase()), SimpleString.toSimpleString(name), filter, durable, false, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, lastValue, SimpleString.toSimpleString(lastValueKey), nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, autoDelete, autoDeleteDelay, autoDeleteMessageCount, autoCreateAddress);
+         final Queue queue = server.createQueue(SimpleString.toSimpleString(address), RoutingType.valueOf(routingType.toUpperCase()), SimpleString.toSimpleString(name), filter, durable, false, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, SimpleString.toSimpleString(groupFirstKey), lastValue, SimpleString.toSimpleString(lastValueKey), nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, autoDelete, autoDeleteDelay, autoDeleteMessageCount, autoCreateA [...]
          return QueueTextFormatter.Long.format(queue, new StringBuilder()).toString();
       } catch (ActiveMQException e) {
          throw new IllegalStateException(e.getMessage());
@@ -1089,16 +1135,33 @@ public class ActiveMQServerControlImpl extends AbstractControl implements Active
                              Integer consumersBeforeDispatch,
                              Long delayBeforeDispatch,
                              String user) throws Exception {
+      return updateQueue(name, routingType, filter, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, null, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, user);
+   }
+
+   @Override
+   public String updateQueue(String name,
+                             String routingType,
+                             String filter,
+                             Integer maxConsumers,
+                             Boolean purgeOnNoConsumers,
+                             Boolean exclusive,
+                             Boolean groupRebalance,
+                             Integer groupBuckets,
+                             String groupFirstKey,
+                             Boolean nonDestructive,
+                             Integer consumersBeforeDispatch,
+                             Long delayBeforeDispatch,
+                             String user) throws Exception {
       if (AuditLogger.isEnabled()) {
          AuditLogger.updateQueue(this.server, name, routingType, filter, maxConsumers, purgeOnNoConsumers,
-                  exclusive, groupRebalance, groupBuckets, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, user);
+                  exclusive, groupRebalance, groupBuckets, groupFirstKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, user);
       }
       checkStarted();
 
       clearIO();
 
       try {
-         final Queue queue = server.updateQueue(name, routingType != null ? RoutingType.valueOf(routingType) : null, filter, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, user);
+         final Queue queue = server.updateQueue(name, routingType != null ? RoutingType.valueOf(routingType) : null, filter, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, groupFirstKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, user);
          if (queue == null) {
             throw ActiveMQMessageBundle.BUNDLE.noSuchQueue(new SimpleString(name));
          }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/QueueBindingInfo.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/QueueBindingInfo.java
index ec2823e..45d8309 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/QueueBindingInfo.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/QueueBindingInfo.java
@@ -90,6 +90,8 @@ public interface QueueBindingInfo {
 
    int getGroupBuckets();
 
+   SimpleString getGroupFirstKey();
+
    boolean isAutoDelete();
 
    long getAutoDeleteDelay();
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 a6e47ee..1b92e86 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
@@ -1297,7 +1297,7 @@ public abstract class AbstractJournalStorageManager extends CriticalComponentImp
 
       SimpleString filterString = filter == null ? null : filter.getFilterString();
 
-      PersistentQueueBindingEncoding bindingEncoding = new PersistentQueueBindingEncoding(queue.getName(), binding.getAddress(), filterString, queue.getUser(), queue.isAutoCreated(), queue.getMaxConsumers(), queue.isPurgeOnNoConsumers(), queue.isExclusive(), queue.isGroupRebalance(), queue.getGroupBuckets(), queue.isLastValue(), queue.getLastValueKey(), queue.isNonDestructive(), queue.getConsumersBeforeDispatch(), queue.getDelayBeforeDispatch(), queue.isAutoDelete(), queue.getAutoDeleteD [...]
+      PersistentQueueBindingEncoding bindingEncoding = new PersistentQueueBindingEncoding(queue.getName(), binding.getAddress(), filterString, queue.getUser(), queue.isAutoCreated(), queue.getMaxConsumers(), queue.isPurgeOnNoConsumers(), queue.isExclusive(), queue.isGroupRebalance(), queue.getGroupBuckets(), queue.getGroupFirstKey(), queue.isLastValue(), queue.getLastValueKey(), queue.isNonDestructive(), queue.getConsumersBeforeDispatch(), queue.getDelayBeforeDispatch(), queue.isAutoDele [...]
 
       readLock();
       try {
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/codec/PersistentQueueBindingEncoding.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/codec/PersistentQueueBindingEncoding.java
index 1e104c1..8278fa1 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/codec/PersistentQueueBindingEncoding.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/codec/PersistentQueueBindingEncoding.java
@@ -66,6 +66,8 @@ public class PersistentQueueBindingEncoding implements EncodingSupport, QueueBin
 
    public int groupBuckets;
 
+   public SimpleString groupFirstKey;
+
    public boolean autoDelete;
 
    public long autoDeleteDelay;
@@ -112,6 +114,8 @@ public class PersistentQueueBindingEncoding implements EncodingSupport, QueueBin
          groupRebalance +
          ", groupBuckets=" +
          groupBuckets +
+         ", groupFirstKey=" +
+         groupFirstKey +
          ", autoDelete=" +
          autoDelete +
          ", autoDeleteDelay=" +
@@ -131,6 +135,7 @@ public class PersistentQueueBindingEncoding implements EncodingSupport, QueueBin
                                          final boolean exclusive,
                                          final boolean groupRebalance,
                                          final int groupBuckets,
+                                         final SimpleString groupFirstKey,
                                          final boolean lastValue,
                                          final SimpleString lastValueKey,
                                          final boolean nonDestructive,
@@ -151,6 +156,7 @@ public class PersistentQueueBindingEncoding implements EncodingSupport, QueueBin
       this.exclusive = exclusive;
       this.groupRebalance = groupRebalance;
       this.groupBuckets = groupBuckets;
+      this.groupFirstKey = groupFirstKey;
       this.lastValue = lastValue;
       this.lastValueKey = lastValueKey;
       this.nonDestructive = nonDestructive;
@@ -326,6 +332,11 @@ public class PersistentQueueBindingEncoding implements EncodingSupport, QueueBin
    }
 
    @Override
+   public SimpleString getGroupFirstKey() {
+      return groupFirstKey;
+   }
+
+   @Override
    public boolean isAutoDelete() {
       return autoDelete;
    }
@@ -431,6 +442,11 @@ public class PersistentQueueBindingEncoding implements EncodingSupport, QueueBin
       } else {
          autoDeleteMessageCount = ActiveMQDefaultConfiguration.getDefaultQueueAutoDeleteMessageCount();
       }
+      if (buffer.readableBytes() > 0) {
+         groupFirstKey = buffer.readNullableSimpleString();
+      } else {
+         groupFirstKey = ActiveMQDefaultConfiguration.getDefaultGroupFirstKey();
+      }
    }
 
    @Override
@@ -455,6 +471,7 @@ public class PersistentQueueBindingEncoding implements EncodingSupport, QueueBin
       buffer.writeBoolean(autoDelete);
       buffer.writeLong(autoDeleteDelay);
       buffer.writeLong(autoDeleteMessageCount);
+      buffer.writeNullableSimpleString(groupFirstKey);
    }
 
    @Override
@@ -476,8 +493,8 @@ public class PersistentQueueBindingEncoding implements EncodingSupport, QueueBin
          DataConstants.SIZE_INT +
          DataConstants.SIZE_BOOLEAN +
          DataConstants.SIZE_LONG +
-         DataConstants.SIZE_LONG;
-
+         DataConstants.SIZE_LONG +
+         SimpleString.sizeofNullableString(groupFirstKey);
    }
 
    private SimpleString createMetadata() {
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 0d7c6c2..9d95955 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
@@ -73,6 +73,7 @@ public interface PostOffice extends ActiveMQComponent {
                             Boolean exclusive,
                             Boolean groupRebalance,
                             Integer groupBuckets,
+                            SimpleString groupFirstKey,
                             Boolean nonDestructive,
                             Integer consumersBeforeDispatch,
                             Long delayBeforeDispatch,
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 5425c1b..b88c6a0 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
@@ -478,6 +478,7 @@ public class PostOfficeImpl implements PostOffice, NotificationListener, Binding
                                    Boolean exclusive,
                                    Boolean groupRebalance,
                                    Integer groupBuckets,
+                                   SimpleString groupFirstKey,
                                    Boolean nonDestructive,
                                    Integer consumersBeforeDispatch,
                                    Long delayBeforeDispatch,
@@ -538,6 +539,10 @@ public class PostOfficeImpl implements PostOffice, NotificationListener, Binding
                changed = true;
                queue.setGroupBuckets(groupBuckets);
             }
+            if (groupFirstKey != null && !groupFirstKey.equals(queue.getGroupFirstKey())) {
+               changed = true;
+               queue.setGroupFirstKey(groupFirstKey);
+            }
             if (nonDestructive != null && queue.isNonDestructive() != nonDestructive.booleanValue()) {
                changed = true;
                queue.setNonDestructive(nonDestructive);
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 b1bd4b2..6730b15 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
@@ -357,7 +357,7 @@ public class ServerSessionPacketHandler implements ChannelHandler {
                   CreateQueueMessage_V2 request = (CreateQueueMessage_V2) packet;
                   requiresResponse = request.isRequiresResponse();
                   session.createQueue(request.getAddress(), request.getQueueName(), request.getRoutingType(), request.getFilterString(), request.isTemporary(), request.isDurable(), request.getMaxConsumers(), request.isPurgeOnNoConsumers(),
-                                      request.isExclusive(), request.isGroupRebalance(), request.getGroupBuckets(), request.isLastValue(), request.getLastValueKey(), request.isNonDestructive(), request.getConsumersBeforeDispatch(), request.getDelayBeforeDispatch(),
+                                      request.isExclusive(), request.isGroupRebalance(), request.getGroupBuckets(), request.getGroupFirstKey(), request.isLastValue(), request.getLastValueKey(), request.isNonDestructive(), request.getConsumersBeforeDispatch(), request.getDelayBeforeDispatch(),
                                       request.isAutoDelete(), request.getAutoDeleteDelay(), request.getAutoDeleteMessageCount(), request.isAutoCreated());
                   if (requiresResponse) {
                      response = createNullResponseMessage(packet);
@@ -382,7 +382,7 @@ public class ServerSessionPacketHandler implements ChannelHandler {
                   QueueQueryResult result = session.executeQueueQuery(request.getQueueName());
                   if (!(result.isExists() && Objects.equals(result.getAddress(), request.getAddress()) && Objects.equals(result.getFilterString(), request.getFilterString()))) {
                      session.createSharedQueue(request.getAddress(), request.getQueueName(), request.getRoutingType(), request.getFilterString(), request.isDurable(), request.getMaxConsumers(), request.isPurgeOnNoConsumers(),
-                                               request.isExclusive(), request.isGroupRebalance(), request.getGroupBuckets(), request.isLastValue(), request.getLastValueKey(), request.isNonDestructive(), request.getConsumersBeforeDispatch(), request.getDelayBeforeDispatch(),
+                                               request.isExclusive(), request.isGroupRebalance(), request.getGroupBuckets(), request.getGroupFirstKey(), request.isLastValue(), request.getLastValueKey(), request.isNonDestructive(), request.getConsumersBeforeDispatch(), request.getDelayBeforeDispatch(),
                                                request.isAutoDelete(), request.getAutoDeleteDelay(), request.getAutoDeleteMessageCount());
                   }
                   if (requiresResponse) {
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServer.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServer.java
index 11e1a15..5d918d1 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServer.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServer.java
@@ -419,6 +419,12 @@ public interface ActiveMQServer extends ServiceComponent {
                      boolean autoDelete, long autoDeleteDelay, long autoDeleteMessageCount, boolean autoCreateAddress) throws Exception;
 
    Queue createQueue(SimpleString address, RoutingType routingType, SimpleString queueName, SimpleString filter,
+                     boolean durable, boolean temporary, int maxConsumers, boolean purgeOnNoConsumers, boolean exclusive, boolean groupRebalance, int groupBuckets, SimpleString groupFirstKey,
+                     boolean lastValue, SimpleString lastValueKey, boolean nonDestructive, int consumersBeforeDispatch, long delayBeforeDispatch,
+                     boolean autoDelete, long autoDeleteDelay, long autoDeleteMessageCount, boolean autoCreateAddress) throws Exception;
+
+
+   Queue createQueue(SimpleString address, RoutingType routingType, SimpleString queueName, SimpleString filter,
                      SimpleString user, boolean durable, boolean temporary, boolean autoCreated, Integer maxConsumers,
                      Boolean purgeOnNoConsumers, boolean autoCreateAddress) throws Exception;
 
@@ -435,6 +441,11 @@ public interface ActiveMQServer extends ServiceComponent {
                      Boolean purgeOnNoConsumers, Boolean exclusive, Boolean groupRebalance, Integer groupBuckets, Boolean lastValue, SimpleString lastValueKey, Boolean nonDestructive,
                      Integer consumersBeforeDispatch, Long delayBeforeDispatch, Boolean autoDelete, Long autoDeleteDelay, Long autoDeleteMessageCount, boolean autoCreateAddress) throws Exception;
 
+   Queue createQueue(AddressInfo addressInfo, SimpleString queueName, SimpleString filter,
+                     SimpleString user, boolean durable, boolean temporary, boolean autoCreated, Integer maxConsumers,
+                     Boolean purgeOnNoConsumers, Boolean exclusive, Boolean groupRebalance, Integer groupBuckets, SimpleString groupFirstKey, Boolean lastValue, SimpleString lastValueKey, Boolean nonDestructive,
+                     Integer consumersBeforeDispatch, Long delayBeforeDispatch, Boolean autoDelete, Long autoDeleteDelay, Long autoDeleteMessageCount, boolean autoCreateAddress) throws Exception;
+
    Queue createQueue(SimpleString address, RoutingType routingType, SimpleString queueName, SimpleString filter,
                      SimpleString user, boolean durable, boolean temporary, boolean ignoreIfExists, boolean transientQueue,
                      boolean autoCreated, int maxConsumers, boolean purgeOnNoConsumers, boolean autoCreateAddress) throws Exception;
@@ -449,6 +460,12 @@ public interface ActiveMQServer extends ServiceComponent {
                      int groupBuckets, boolean lastValue, SimpleString lastValueKey, boolean nonDestructive,
                      int consumersBeforeDispatch, long delayBeforeDispatch, boolean autoDelete, long autoDeleteDelay, long autoDeleteMessageCount, boolean autoCreateAddress) throws Exception;
 
+   Queue createQueue(SimpleString address, RoutingType routingType, SimpleString queueName, SimpleString filter,
+                     SimpleString user, boolean durable, boolean temporary, boolean ignoreIfExists, boolean transientQueue,
+                     boolean autoCreated, int maxConsumers, boolean purgeOnNoConsumers, boolean exclusive, boolean groupRebalance,
+                     int groupBuckets, SimpleString groupFirstKey, boolean lastValue, SimpleString lastValueKey, boolean nonDestructive,
+                     int consumersBeforeDispatch, long delayBeforeDispatch, boolean autoDelete, long autoDeleteDelay, long autoDeleteMessageCount, boolean autoCreateAddress) throws Exception;
+
    @Deprecated
    Queue createQueue(SimpleString address, SimpleString queueName, SimpleString filter, boolean durable, boolean temporary) throws Exception;
 
@@ -458,6 +475,27 @@ public interface ActiveMQServer extends ServiceComponent {
    @Deprecated
    Queue deployQueue(SimpleString address, SimpleString queue, SimpleString filter, boolean durable, boolean temporary) throws Exception;
 
+   void createSharedQueue(SimpleString address,
+                          RoutingType routingType,
+                          SimpleString name,
+                          SimpleString filterString,
+                          SimpleString user,
+                          boolean durable,
+                          int maxConsumers,
+                          boolean purgeOnNoConsumers,
+                          boolean exclusive,
+                          boolean groupRebalance,
+                          int groupBuckets,
+                          SimpleString groupFirstKey,
+                          boolean lastValue,
+                          SimpleString lastValueKey,
+                          boolean nonDestructive,
+                          int consumersBeforeDispatch,
+                          long delayBeforeDispatch,
+                          boolean autoDelete,
+                          long autoDeleteDelay,
+                          long autoDeleteMessageCount) throws Exception;
+
    Queue locateQueue(SimpleString queueName);
 
    BindingQueryResult bindingQuery(SimpleString address) throws Exception;
@@ -567,6 +605,20 @@ public interface ActiveMQServer extends ServiceComponent {
                      Long delayBeforeDispatch,
                      String user) throws Exception;
 
+   Queue updateQueue(String name,
+                     RoutingType routingType,
+                     String filterString,
+                     Integer maxConsumers,
+                     Boolean purgeOnNoConsumers,
+                     Boolean exclusive,
+                     Boolean groupRebalance,
+                     Integer groupBuckets,
+                     String groupFirstQueue,
+                     Boolean nonDestructive,
+                     Integer consumersBeforeDispatch,
+                     Long delayBeforeDispatch,
+                     String user) throws Exception;
+
    /*
             * add a ProtocolManagerFactory to be used. Note if @see Configuration#isResolveProtocols is tur then this factory will
             * replace any factories with the same protocol
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 b722b5f..bf0a22d 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
@@ -118,6 +118,10 @@ public interface Queue extends Bindable,CriticalComponent {
 
    void setGroupRebalance(boolean groupRebalance);
 
+   SimpleString getGroupFirstKey();
+
+   void setGroupFirstKey(SimpleString groupFirstKey);
+
    boolean isConfigurationManaged();
 
    void setConfigurationManaged(boolean configurationManaged);
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 0d8e4da..9135e18 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
@@ -45,6 +45,7 @@ public final class QueueConfig {
    private final long delayBeforeDispatch;
    private final boolean groupRebalance;
    private final int groupBuckets;
+   private final SimpleString groupFirstKey;
    private final boolean configurationManaged;
    private final SimpleString lastValueKey;
    private final boolean nonDestructive;
@@ -74,6 +75,7 @@ public final class QueueConfig {
       private long delayBeforeDispatch;
       private boolean groupRebalance;
       private int groupBuckets;
+      private SimpleString groupFirstKey;
       private boolean autoDelete;
       private long autoDeleteDelay;
       private long autoDeleteMessageCount;
@@ -104,6 +106,7 @@ public final class QueueConfig {
          this.delayBeforeDispatch = ActiveMQDefaultConfiguration.getDefaultDelayBeforeDispatch();
          this.groupRebalance = ActiveMQDefaultConfiguration.getDefaultGroupRebalance();
          this.groupBuckets = ActiveMQDefaultConfiguration.getDefaultGroupBuckets();
+         this.groupFirstKey = ActiveMQDefaultConfiguration.getDefaultGroupFirstKey();
          this.autoDelete = ActiveMQDefaultConfiguration.getDefaultQueueAutoDelete(autoCreated);
          this.autoDeleteDelay = ActiveMQDefaultConfiguration.getDefaultQueueAutoDeleteDelay();
          this.autoDeleteMessageCount = ActiveMQDefaultConfiguration.getDefaultQueueAutoDeleteMessageCount();
@@ -226,6 +229,11 @@ public final class QueueConfig {
          return this;
       }
 
+      public Builder groupFirstKey(final SimpleString groupFirstKey) {
+         this.groupFirstKey = groupFirstKey;
+         return this;
+      }
+
 
       public Builder routingType(RoutingType routingType) {
          this.routingType = routingType;
@@ -258,7 +266,7 @@ public final class QueueConfig {
          } else {
             pageSubscription = null;
          }
-         return new QueueConfig(id, address, name, filter, pageSubscription, user, durable, temporary, autoCreated, routingType, maxConsumers, exclusive, lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, purgeOnNoConsumers, groupRebalance, groupBuckets, autoDelete, autoDeleteDelay, autoDeleteMessageCount, configurationManaged);
+         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, configurationManaged);
       }
 
    }
@@ -311,6 +319,7 @@ public final class QueueConfig {
                        final boolean purgeOnNoConsumers,
                        final boolean groupRebalance,
                        final int groupBuckets,
+                       final SimpleString groupFirstKey,
                        final boolean autoDelete,
                        final long autoDeleteDelay,
                        final long autoDeleteMessageCount,
@@ -335,6 +344,7 @@ public final class QueueConfig {
       this.delayBeforeDispatch = delayBeforeDispatch;
       this.groupRebalance = groupRebalance;
       this.groupBuckets = groupBuckets;
+      this.groupFirstKey = groupFirstKey;
       this.autoDelete = autoDelete;
       this.autoDeleteDelay = autoDeleteDelay;
       this.autoDeleteMessageCount = autoDeleteMessageCount;
@@ -421,6 +431,10 @@ public final class QueueConfig {
       return groupBuckets;
    }
 
+   public SimpleString getGroupFirstKey() {
+      return groupFirstKey;
+   }
+
    public boolean isConfigurationManaged() {
       return configurationManaged;
    }
@@ -486,6 +500,8 @@ public final class QueueConfig {
          return false;
       if (groupBuckets != that.groupBuckets)
          return false;
+      if (groupFirstKey != null ? !groupFirstKey.equals(that.groupFirstKey) : that.groupFirstKey != null)
+         return false;
       if (autoDelete != that.autoDelete)
          return false;
       if (autoDeleteDelay != that.autoDeleteDelay)
@@ -520,6 +536,7 @@ public final class QueueConfig {
       result = 31 * result + (purgeOnNoConsumers ? 1 : 0);
       result = 31 * result + (groupRebalance ? 1 : 0);
       result = 31 * result + groupBuckets;
+      result = 31 * result + (groupFirstKey != null ? groupFirstKey.hashCode() : 0);
       result = 31 * result + (autoDelete ? 1 : 0);
       result = 31 * result + Long.hashCode(autoDeleteDelay);
       result = 31 * result + Long.hashCode(autoDeleteMessageCount);
@@ -550,6 +567,7 @@ public final class QueueConfig {
          + ", purgeOnNoConsumers=" + purgeOnNoConsumers
          + ", groupRebalance=" + groupRebalance
          + ", groupBuckets=" + groupBuckets
+         + ", groupFirstKey=" + groupFirstKey
          + ", autoDelete=" + autoDelete
          + ", autoDeleteDelay=" + autoDeleteDelay
          + ", autoDeleteMessageCount=" + autoDeleteMessageCount
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ServerSession.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ServerSession.java
index 6f3708e..e7848f0 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ServerSession.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ServerSession.java
@@ -199,6 +199,28 @@ public interface ServerSession extends SecurityAuth {
                      SimpleString filterString,
                      boolean temporary,
                      boolean durable,
+                     int maxConsumers,
+                     boolean purgeOnNoConsumers,
+                     Boolean exclusive,
+                     Boolean groupRebalance,
+                     Integer groupBuckets,
+                     SimpleString groupFirstKey,
+                     Boolean lastValue,
+                     SimpleString lastValueKey,
+                     Boolean nonDestructive,
+                     Integer consumersBeforeDispatch,
+                     Long delayBeforeDispatch,
+                     Boolean autoDelete,
+                     Long autoDeleteDelay,
+                     Long autoDeleteMessageCount,
+                     boolean autoCreated) throws Exception;
+
+   Queue createQueue(SimpleString address,
+                     SimpleString name,
+                     RoutingType routingType,
+                     SimpleString filterString,
+                     boolean temporary,
+                     boolean durable,
                      boolean autoCreated) throws Exception;
 
    Queue createQueue(AddressInfo addressInfo,
@@ -354,6 +376,26 @@ public interface ServerSession extends SecurityAuth {
    void createSharedQueue(SimpleString address,
                           SimpleString name,
                           RoutingType routingType,
+                          SimpleString filterString,
+                          boolean durable,
+                          Integer maxConsumers,
+                          Boolean purgeOnNoConsumers,
+                          Boolean exclusive,
+                          Boolean groupRebalance,
+                          Integer groupBuckets,
+                          SimpleString groupFirstKey,
+                          Boolean lastValue,
+                          SimpleString lastValueKey,
+                          Boolean nonDestructive,
+                          Integer consumersBeforeDispatch,
+                          Long delayBeforeDispatch,
+                          Boolean autoDelete,
+                          Long autoDeleteDelay,
+                          Long autoDeleteMessageCount) throws Exception;
+
+   void createSharedQueue(SimpleString address,
+                          SimpleString name,
+                          RoutingType routingType,
                           boolean durable,
                           SimpleString filterString) throws Exception;
 
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java
index c33fedf..5855c46 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java
@@ -957,6 +957,7 @@ public class ActiveMQServerImpl implements ActiveMQServer {
       int defaultConsumerWindowSize = addressSettings.getDefaultConsumerWindowSize();
       boolean defaultGroupRebalance = addressSettings.isDefaultGroupRebalance();
       int defaultGroupBuckets = addressSettings.getDefaultGroupBuckets();
+      SimpleString defaultGroupFirstKey = addressSettings.getDefaultGroupFirstKey();
       long autoDeleteQueuesDelay = addressSettings.getAutoDeleteQueuesDelay();
       long autoDeleteQueuesMessageCount = addressSettings.getAutoDeleteQueuesMessageCount();
 
@@ -969,12 +970,12 @@ public class ActiveMQServerImpl implements ActiveMQServer {
 
          SimpleString filterString = filter == null ? null : filter.getFilterString();
 
-         response = new QueueQueryResult(realName, binding.getAddress(), queue.isDurable(), queue.isTemporary(), filterString, queue.getConsumerCount(), queue.getMessageCount(), autoCreateQueues, true, queue.isAutoCreated(), queue.isPurgeOnNoConsumers(), queue.getRoutingType(), queue.getMaxConsumers(), queue.isExclusive(), queue.isGroupRebalance(), queue.getGroupBuckets(), queue.isLastValue(), queue.getLastValueKey(), queue.isNonDestructive(), queue.getConsumersBeforeDispatch(), queue.ge [...]
+         response = new QueueQueryResult(realName, binding.getAddress(), queue.isDurable(), queue.isTemporary(), filterString, queue.getConsumerCount(), queue.getMessageCount(), autoCreateQueues, true, queue.isAutoCreated(), queue.isPurgeOnNoConsumers(), queue.getRoutingType(), queue.getMaxConsumers(), queue.isExclusive(), queue.isGroupRebalance(), queue.getGroupBuckets(), queue.getGroupFirstKey(), queue.isLastValue(), queue.getLastValueKey(), queue.isNonDestructive(), queue.getConsumers [...]
       } else if (realName.equals(managementAddress)) {
          // make an exception for the management address (see HORNETQ-29)
-         response = new QueueQueryResult(realName, managementAddress, true, false, null, -1, -1, autoCreateQueues, true, false, false, RoutingType.MULTICAST, -1, false, false, null, null, null, null, null, null, null, null, null, defaultConsumerWindowSize);
+         response = new QueueQueryResult(realName, managementAddress, true, false, null, -1, -1, autoCreateQueues, true, false, false, RoutingType.MULTICAST, -1, false, false, null, null, null,null, null, null, null, null, null, null, defaultConsumerWindowSize);
       } else {
-         response = new QueueQueryResult(realName, addressName, true, false, null, 0, 0, autoCreateQueues, false, false, defaultPurgeOnNoConsumers, RoutingType.MULTICAST, defaultMaxConsumers, defaultExclusiveQueue, defaultGroupRebalance, defaultGroupBuckets, defaultLastValueQueue, defaultLastValueKey, defaultNonDestructive, defaultConsumersBeforeDispatch, defaultDelayBeforeDispatch, isAutoDelete(false, addressSettings), autoDeleteQueuesDelay, autoDeleteQueuesMessageCount, defaultConsumer [...]
+         response = new QueueQueryResult(realName, addressName, true, false, null, 0, 0, autoCreateQueues, false, false, defaultPurgeOnNoConsumers, RoutingType.MULTICAST, defaultMaxConsumers, defaultExclusiveQueue, defaultGroupRebalance, defaultGroupBuckets, defaultGroupFirstKey, defaultLastValueQueue, defaultLastValueKey, defaultNonDestructive, defaultConsumersBeforeDispatch, defaultDelayBeforeDispatch, isAutoDelete(false, addressSettings), autoDeleteQueuesDelay, autoDeleteQueuesMessage [...]
       }
 
       return response;
@@ -1783,6 +1784,31 @@ public class ActiveMQServerImpl implements ActiveMQServer {
    }
 
    @Override
+   public Queue createQueue(final SimpleString address,
+                            final RoutingType routingType,
+                            final SimpleString queueName,
+                            final SimpleString filter,
+                            final boolean durable,
+                            final boolean temporary,
+                            final int maxConsumers,
+                            final boolean purgeOnNoConsumers,
+                            final boolean exclusive,
+                            final boolean groupRebalance,
+                            final int groupBuckets,
+                            final SimpleString groupFirstKey,
+                            final boolean lastValue,
+                            final SimpleString lastValueKey,
+                            final boolean nonDestructive,
+                            final int consumersBeforeDispatch,
+                            final long delayBeforeDispatch,
+                            final boolean autoDelete,
+                            final long autoDeleteDelay,
+                            final long autoDeleteMessageCount,
+                            final boolean autoCreateAddress) throws Exception {
+      return createQueue(address, routingType, queueName, filter, null, durable, temporary, false, false, false, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, groupFirstKey, lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, autoDelete, autoDeleteDelay, autoDeleteMessageCount, autoCreateAddress);
+   }
+
+   @Override
    @Deprecated
    public Queue createQueue(SimpleString address,
                             RoutingType routingType,
@@ -1801,18 +1827,24 @@ public class ActiveMQServerImpl implements ActiveMQServer {
    @Override
    public Queue createQueue(AddressInfo addressInfo, SimpleString queueName, SimpleString filter, SimpleString user, boolean durable, boolean temporary, boolean autoCreated, Integer maxConsumers, Boolean purgeOnNoConsumers, boolean autoCreateAddress) throws Exception {
       AddressSettings as = getAddressSettingsRepository().getMatch(addressInfo == null ? queueName.toString() : addressInfo.getName().toString());
-      return createQueue(addressInfo, queueName, filter, user, durable, temporary, false, false, autoCreated, maxConsumers, purgeOnNoConsumers, as.isDefaultExclusiveQueue(),  as.isDefaultGroupRebalance(), as.getDefaultGroupBuckets(), as.isDefaultLastValueQueue(), as.getDefaultLastValueKey(), as.isDefaultNonDestructive(), as.getDefaultConsumersBeforeDispatch(), as.getDefaultDelayBeforeDispatch(), isAutoDelete(autoCreated, as), as.getAutoDeleteQueuesDelay(), as.getAutoDeleteQueuesMessageCo [...]
+      return createQueue(addressInfo, queueName, filter, user, durable, temporary, false, false, autoCreated, maxConsumers, purgeOnNoConsumers, as.isDefaultExclusiveQueue(),  as.isDefaultGroupRebalance(), as.getDefaultGroupBuckets(), as.getDefaultGroupFirstKey(), as.isDefaultLastValueQueue(), as.getDefaultLastValueKey(), as.isDefaultNonDestructive(), as.getDefaultConsumersBeforeDispatch(), as.getDefaultDelayBeforeDispatch(), isAutoDelete(autoCreated, as), as.getAutoDeleteQueuesDelay(), a [...]
    }
 
    @Override
    public Queue createQueue(AddressInfo addressInfo, SimpleString queueName, SimpleString filter, SimpleString user, boolean durable, boolean temporary, boolean autoCreated, Integer maxConsumers, Boolean purgeOnNoConsumers, Boolean exclusive, Boolean lastValue, boolean autoCreateAddress) throws Exception {
       AddressSettings as = getAddressSettingsRepository().getMatch(addressInfo == null ? queueName.toString() : addressInfo.getName().toString());
-      return createQueue(addressInfo, queueName, filter, user, durable, temporary, false, false, autoCreated, maxConsumers, purgeOnNoConsumers, exclusive,  as.isDefaultGroupRebalance(), as.getDefaultGroupBuckets(), lastValue, as.getDefaultLastValueKey(), as.isDefaultNonDestructive(), as.getDefaultConsumersBeforeDispatch(), as.getDefaultDelayBeforeDispatch(), isAutoDelete(autoCreated, as), as.getAutoDeleteQueuesDelay(), as.getAutoDeleteQueuesMessageCount(), autoCreateAddress, false);
+      return createQueue(addressInfo, queueName, filter, user, durable, temporary, false, false, autoCreated, maxConsumers, purgeOnNoConsumers, exclusive,  as.isDefaultGroupRebalance(), as.getDefaultGroupBuckets(), as.getDefaultGroupFirstKey(), lastValue, as.getDefaultLastValueKey(), as.isDefaultNonDestructive(), as.getDefaultConsumersBeforeDispatch(), as.getDefaultDelayBeforeDispatch(), isAutoDelete(autoCreated, as), as.getAutoDeleteQueuesDelay(), as.getAutoDeleteQueuesMessageCount(), a [...]
    }
 
    @Override
    public Queue createQueue(AddressInfo addressInfo, SimpleString queueName, SimpleString filter, SimpleString user, boolean durable, boolean temporary, boolean autoCreated, Integer maxConsumers, Boolean purgeOnNoConsumers, Boolean exclusive, Boolean groupRebalance, Integer groupBuckets, Boolean lastValue, SimpleString lastValueKey, Boolean nonDestructive, Integer consumersBeforeDispatch, Long delayBeforeDispatch, Boolean autoDelete, Long autoDeleteDelay, Long autoDeleteMessageCount, boo [...]
-      return createQueue(addressInfo, queueName, filter, user, durable, temporary, false, false, autoCreated, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, autoDelete, autoDeleteDelay, autoDeleteMessageCount, autoCreateAddress, false);
+      AddressSettings as = getAddressSettingsRepository().getMatch(addressInfo == null ? queueName.toString() : addressInfo.getName().toString());
+      return createQueue(addressInfo, queueName, filter, user, durable, temporary, false, false, autoCreated, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, as.getDefaultGroupFirstKey(), lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, autoDelete, autoDeleteDelay, autoDeleteMessageCount, autoCreateAddress, false);
+   }
+
+   @Override
+   public Queue createQueue(AddressInfo addressInfo, SimpleString queueName, SimpleString filter, SimpleString user, boolean durable, boolean temporary, boolean autoCreated, Integer maxConsumers, Boolean purgeOnNoConsumers, Boolean exclusive, Boolean groupRebalance, Integer groupBuckets, SimpleString groupFirstKey, Boolean lastValue, SimpleString lastValueKey, Boolean nonDestructive, Integer consumersBeforeDispatch, Long delayBeforeDispatch, Boolean autoDelete, Long autoDeleteDelay, Long [...]
+      return createQueue(addressInfo, queueName, filter, user, durable, temporary, false, false, autoCreated, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, groupFirstKey, lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, autoDelete, autoDeleteDelay, autoDeleteMessageCount, autoCreateAddress, false);
    }
 
    static boolean isAutoDelete(boolean autoCreated, AddressSettings addressSettings) {
@@ -1892,6 +1924,31 @@ public class ActiveMQServerImpl implements ActiveMQServer {
                                  boolean autoDelete,
                                  long autoDeleteDelay,
                                  long autoDeleteMessageCount) throws Exception {
+      AddressSettings as = getAddressSettingsRepository().getMatch(address == null ? name.toString() : address.toString());
+      createSharedQueue(address, routingType, name, filterString, user, durable, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, as.getDefaultGroupFirstKey(), lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, autoDelete, autoDeleteDelay, autoDeleteMessageCount);
+   }
+
+   @Override
+   public void createSharedQueue(final SimpleString address,
+                                 RoutingType routingType,
+                                 final SimpleString name,
+                                 final SimpleString filterString,
+                                 final SimpleString user,
+                                 boolean durable,
+                                 int maxConsumers,
+                                 boolean purgeOnNoConsumers,
+                                 boolean exclusive,
+                                 boolean groupRebalance,
+                                 int groupBuckets,
+                                 SimpleString groupFirstKey,
+                                 boolean lastValue,
+                                 SimpleString lastValueKey,
+                                 boolean nonDestructive,
+                                 int consumersBeforeDispatch,
+                                 long delayBeforeDispatch,
+                                 boolean autoDelete,
+                                 long autoDeleteDelay,
+                                 long autoDeleteMessageCount) throws Exception {
       //force the old contract about address
       if (address == null) {
          throw new NullPointerException("address can't be null!");
@@ -1905,7 +1962,7 @@ public class ActiveMQServerImpl implements ActiveMQServer {
          }
       }
 
-      final Queue queue = createQueue(address, routingType, name, filterString, user, durable, !durable, true, !durable, false, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, autoDelete, autoDeleteDelay, autoDeleteMessageCount, true);
+      final Queue queue = createQueue(address, routingType, name, filterString, user, durable, !durable, true, !durable, false, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, groupFirstKey, lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, autoDelete, autoDeleteDelay, autoDeleteMessageCount, true);
 
       if (!queue.getAddress().equals(address)) {
          throw ActiveMQMessageBundle.BUNDLE.queueSubscriptionBelongsToDifferentAddress(name);
@@ -2940,6 +2997,7 @@ public class ActiveMQServerImpl implements ActiveMQServer {
             boolean isExclusive = config.isExclusive() == null ? as.isDefaultExclusiveQueue() : config.isExclusive();
             boolean groupRebalance = config.isGroupRebalance() == null ? as.isDefaultGroupRebalance() : config.isGroupRebalance();
             int groupBuckets = config.getGroupBuckets() == null ? as.getDefaultGroupBuckets() : config.getGroupBuckets();
+            SimpleString groupFirstKey = config.getGroupFirstKey() == null ? as.getDefaultGroupFirstKey() : SimpleString.toSimpleString(config.getGroupFirstKey());
             boolean isLastValue = config.isLastValue() == null ? as.isDefaultLastValueQueue() : config.isLastValue();
             SimpleString lastValueKey = config.getLastValueKey() == null ? as.getDefaultLastValueKey() : SimpleString.toSimpleString(config.getLastValueKey());
             boolean isNonDestructive = config.isNonDestructive() == null ? as.isDefaultNonDestructive() : config.isNonDestructive();
@@ -2947,11 +3005,11 @@ public class ActiveMQServerImpl implements ActiveMQServer {
             long delayBeforeDispatch = config.getDelayBeforeDispatch() == null ? as.getDefaultDelayBeforeDispatch() : config.getDelayBeforeDispatch();
 
             if (locateQueue(queueName) != null && locateQueue(queueName).getAddress().toString().equals(config.getAddress())) {
-               updateQueue(config.getName(), config.getRoutingType(), config.getFilterString(), maxConsumers, config.getPurgeOnNoConsumers(), isExclusive, groupRebalance, groupBuckets, isNonDestructive, consumersBeforeDispatch, delayBeforeDispatch, config.getUser(), true);
+               updateQueue(config.getName(), config.getRoutingType(), config.getFilterString(), maxConsumers, config.getPurgeOnNoConsumers(), isExclusive, groupRebalance, groupBuckets, groupFirstKey.toString(), isNonDestructive, consumersBeforeDispatch, delayBeforeDispatch, config.getUser(), true);
             } else {
                // if the address::queue doesn't exist then create it
                try {
-                  createQueue(new AddressInfo(SimpleString.toSimpleString(config.getAddress())).addRoutingType(config.getRoutingType()), queueName, SimpleString.toSimpleString(config.getFilterString()), SimpleString.toSimpleString(config.getUser()), config.isDurable(), false, false, false, false, maxConsumers, config.getPurgeOnNoConsumers(), isExclusive, groupRebalance, groupBuckets, isLastValue, lastValueKey, isNonDestructive, consumersBeforeDispatch, delayBeforeDispatch, isAutoDelete(f [...]
+                  createQueue(new AddressInfo(SimpleString.toSimpleString(config.getAddress())).addRoutingType(config.getRoutingType()), queueName, SimpleString.toSimpleString(config.getFilterString()), SimpleString.toSimpleString(config.getUser()), config.isDurable(), false, false, false, false, maxConsumers, config.getPurgeOnNoConsumers(), isExclusive, groupRebalance, groupBuckets, groupFirstKey, isLastValue, lastValueKey, isNonDestructive, consumersBeforeDispatch, delayBeforeDispatch, [...]
                } catch (ActiveMQQueueExistsException e) {
                   // the queue may exist on a *different* address
                   ActiveMQServerLogger.LOGGER.warn(e.getMessage());
@@ -3144,6 +3202,7 @@ public class ActiveMQServerImpl implements ActiveMQServer {
                             final boolean exclusive,
                             final boolean groupRebalance,
                             final int groupBuckets,
+                            final SimpleString groupFirstKey,
                             final boolean lastValue,
                             final SimpleString lastValueKey,
                             final boolean nonDestructive,
@@ -3214,6 +3273,7 @@ public class ActiveMQServerImpl implements ActiveMQServer {
               .exclusive(exclusive)
               .groupRebalance(groupRebalance)
               .groupBuckets(groupBuckets)
+              .groupFirstKey(groupFirstKey)
               .lastValue(lastValue)
               .lastValueKey(lastValueKey)
               .nonDestructive(nonDestructive)
@@ -3305,7 +3365,37 @@ public class ActiveMQServerImpl implements ActiveMQServer {
                             final long autoDeleteDelay,
                             final long autoDeleteMessageCount,
                             final boolean autoCreateAddress) throws Exception {
-      return createQueue(new AddressInfo(address).addRoutingType(routingType), queueName, filterString, user, durable, temporary, ignoreIfExists, transientQueue, autoCreated, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, autoDelete, autoDeleteDelay, autoDeleteMessageCount, autoCreateAddress, false);
+      AddressSettings as = getAddressSettingsRepository().getMatch(address == null ? queueName.toString() : address.toString());
+      return createQueue(new AddressInfo(address).addRoutingType(routingType), queueName, filterString, user, durable, temporary, ignoreIfExists, transientQueue, autoCreated, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, as.getDefaultGroupFirstKey(), lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, autoDelete, autoDeleteDelay, autoDeleteMessageCount, autoCreateAddress, false);
+   }
+
+   @Override
+   public Queue createQueue(final SimpleString address,
+                            final RoutingType routingType,
+                            final SimpleString queueName,
+                            final SimpleString filterString,
+                            final SimpleString user,
+                            final boolean durable,
+                            final boolean temporary,
+                            final boolean ignoreIfExists,
+                            final boolean transientQueue,
+                            final boolean autoCreated,
+                            final int maxConsumers,
+                            final boolean purgeOnNoConsumers,
+                            final boolean exclusive,
+                            final boolean groupRebalance,
+                            final int groupBuckets,
+                            final SimpleString groupFirstKey,
+                            final boolean lastValue,
+                            final SimpleString lastValueKey,
+                            final boolean nonDestructive,
+                            final int consumersBeforeDispatch,
+                            final long delayBeforeDispatch,
+                            final boolean autoDelete,
+                            final long autoDeleteDelay,
+                            final long autoDeleteMessageCount,
+                            final boolean autoCreateAddress) throws Exception {
+      return createQueue(new AddressInfo(address).addRoutingType(routingType), queueName, filterString, user, durable, temporary, ignoreIfExists, transientQueue, autoCreated, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, groupFirstKey, lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, autoDelete, autoDeleteDelay, autoDeleteMessageCount, autoCreateAddress, false);
    }
 
    @Deprecated
@@ -3351,7 +3441,24 @@ public class ActiveMQServerImpl implements ActiveMQServer {
                             Integer consumersBeforeDispatch,
                             Long delayBeforeDispatch,
                             String user) throws Exception {
-      return updateQueue(name, routingType, filterString, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, user, null);
+      return updateQueue(name, routingType, filterString, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, null, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, user, null);
+   }
+
+   @Override
+   public Queue updateQueue(String name,
+                            RoutingType routingType,
+                            String filterString,
+                            Integer maxConsumers,
+                            Boolean purgeOnNoConsumers,
+                            Boolean exclusive,
+                            Boolean groupRebalance,
+                            Integer groupBuckets,
+                            String groupFirstKey,
+                            Boolean nonDestructive,
+                            Integer consumersBeforeDispatch,
+                            Long delayBeforeDispatch,
+                            String user) throws Exception {
+      return updateQueue(name, routingType, filterString, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, groupFirstKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, user, null);
    }
 
    private Queue updateQueue(String name,
@@ -3362,13 +3469,14 @@ public class ActiveMQServerImpl implements ActiveMQServer {
                             Boolean exclusive,
                             Boolean groupRebalance,
                             Integer groupBuckets,
+                            String groupFirstKey,
                             Boolean nonDestructive,
                             Integer consumersBeforeDispatch,
                             Long delayBeforeDispatch,
                             String user,
                             Boolean configurationManaged) throws Exception {
       final Filter filter = FilterImpl.createFilter(filterString);
-      final QueueBinding queueBinding = this.postOffice.updateQueue(new SimpleString(name), routingType, filter, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, SimpleString.toSimpleString(user), configurationManaged);
+      final QueueBinding queueBinding = this.postOffice.updateQueue(new SimpleString(name), routingType, filter, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, SimpleString.toSimpleString(groupFirstKey), nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, SimpleString.toSimpleString(user), configurationManaged);
       if (queueBinding != null) {
          final Queue queue = queueBinding.getQueue();
          return queue;
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 e418ee3..23df9a5 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
@@ -70,6 +70,7 @@ public class LastValueQueue extends QueueImpl {
                          final Boolean exclusive,
                          final Boolean groupRebalance,
                          final Integer groupBuckets,
+                         final SimpleString groupFirstKey,
                          final Integer consumersBeforeDispatch,
                          final Long delayBeforeDispatch,
                          final Boolean purgeOnNoConsumers,
@@ -86,7 +87,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, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, purgeOnNoConsumers, autoDelete, autoDeleteDelay, autoDeleteMessageCount, configurationManaged, scheduledExecutor, postOffice, storageManager, addressSettingsRepository, executor, server, 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);
       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 defb23b..67b1007 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
@@ -154,6 +154,7 @@ public class PostOfficeJournalLoader implements JournalLoader {
             .exclusive(queueBindingInfo.isExclusive())
             .groupRebalance(queueBindingInfo.isGroupRebalance())
             .groupBuckets(queueBindingInfo.getGroupBuckets())
+            .groupFirstKey(queueBindingInfo.getGroupFirstKey())
             .lastValue(queueBindingInfo.isLastValue())
             .lastValueKey(queueBindingInfo.getLastValueKey())
             .nonDestructive(queueBindingInfo.isNonDestructive())
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 7065766..af37216 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.consumersBeforeDispatch(), config.delayBeforeDispatch(), config.isPurgeOnNoConsumers(), lastValueKey(config), config.isNonDestructive(), config.isAutoDelete( [...]
+         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 [...]
       } 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.isNonDestructive(), config.consumersBeforeDispatch(), config.delayBeforeDispatch(), config.isPurgeOnNoConsumers(), config.isAutoDelete(), config.getAutoDeleteDela [...]
+         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( [...]
       }
       server.getCriticalAnalyzer().add(queue);
       return queue;
@@ -102,7 +102,7 @@ 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.getDefaultConsumersBeforeDispatch(), ActiveMQDefaultCo [...]
+         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 [...]
       } else {
          queue = new QueueImpl(persistenceID, address, name, filter, pageSubscription, user, durable, temporary, autoCreated, scheduledExecutor, postOffice, storageManager, addressSettingsRepository, executorFactory.getExecutor(), server, this);
       }
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 b5205d8..5c24143 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
@@ -430,6 +430,38 @@ public class QueueImpl extends CriticalComponentImpl implements Queue {
    }
 
    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 RoutingType routingType,
+                    final Integer maxConsumers,
+                    final Boolean exclusive,
+                    final Boolean groupRebalance,
+                    final Integer groupBuckets,
+                    final Boolean nonDestructive,
+                    final Integer consumersBeforeDispatch,
+                    final Long delayBeforeDispatch,
+                    final Boolean purgeOnNoConsumers,
+                    final Boolean autoDelete,
+                    final Long autoDeleteDelay,
+                    final Long autoDeleteMessageCount,
+                    final boolean configurationManaged,
+                    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, 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,
@@ -443,6 +475,7 @@ public class QueueImpl extends CriticalComponentImpl implements Queue {
                      final Boolean exclusive,
                      final Boolean groupRebalance,
                      final Integer groupBuckets,
+                     final SimpleString groupFirstKey,
                      final Boolean nonDestructive,
                      final Integer consumersBeforeDispatch,
                      final Long delayBeforeDispatch,
@@ -498,7 +531,7 @@ public class QueueImpl extends CriticalComponentImpl implements Queue {
 
       this.groups = groupMap(this.groupBuckets);
 
-      this.groupFirstKey = ActiveMQDefaultConfiguration.getDefaultGroupFirstKey();
+      this.groupFirstKey = groupFirstKey == null ? ActiveMQDefaultConfiguration.getDefaultGroupFirstKey() : groupFirstKey;
 
       this.autoDelete = autoDelete == null ? ActiveMQDefaultConfiguration.getDefaultQueueAutoDelete(autoCreated) : autoDelete;
 
@@ -751,6 +784,17 @@ public class QueueImpl extends CriticalComponentImpl implements Queue {
    }
 
    @Override
+   public SimpleString getGroupFirstKey() {
+      return groupFirstKey;
+   }
+
+   @Override
+   public synchronized void setGroupFirstKey(SimpleString groupFirstKey) {
+      this.groupFirstKey = groupFirstKey;
+   }
+
+
+   @Override
    public boolean isConfigurationManaged() {
       return configurationManaged;
    }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ServerSessionImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ServerSessionImpl.java
index ec1c79d..a97ab66 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ServerSessionImpl.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ServerSessionImpl.java
@@ -614,7 +614,7 @@ public class ServerSessionImpl implements ServerSession, FailureListener {
    @Override
    public Queue createQueue(AddressInfo addressInfo, SimpleString name, SimpleString filterString, boolean temporary, boolean durable) throws Exception {
       AddressSettings as = server.getAddressSettingsRepository().getMatch(addressInfo.getName().toString());
-      return createQueue(addressInfo, name, filterString, temporary, durable, as.getDefaultMaxConsumers(), as.isDefaultPurgeOnNoConsumers(), as.isDefaultExclusiveQueue(), as.isDefaultGroupRebalance(), as.getDefaultGroupBuckets(), as.isDefaultLastValueQueue(), as.getDefaultLastValueKey(), as.isDefaultNonDestructive(), as.getDefaultConsumersBeforeDispatch(), as.getDefaultDelayBeforeDispatch(), ActiveMQServerImpl.isAutoDelete(false, as), as.getAutoDeleteQueuesDelay(), as.getAutoDeleteQueues [...]
+      return createQueue(addressInfo, name, filterString, temporary, durable, as.getDefaultMaxConsumers(), as.isDefaultPurgeOnNoConsumers(), as.isDefaultExclusiveQueue(), as.isDefaultGroupRebalance(), as.getDefaultGroupBuckets(), as.getDefaultGroupFirstKey(), as.isDefaultLastValueQueue(), as.getDefaultLastValueKey(), as.isDefaultNonDestructive(), as.getDefaultConsumersBeforeDispatch(), as.getDefaultDelayBeforeDispatch(), ActiveMQServerImpl.isAutoDelete(false, as), as.getAutoDeleteQueuesD [...]
    }
 
    public Queue createQueue(final AddressInfo addressInfo,
@@ -627,6 +627,7 @@ public class ServerSessionImpl implements ServerSession, FailureListener {
                             final boolean exclusive,
                             final boolean groupRebalance,
                             final int groupBuckets,
+                            final SimpleString groupFirstKey,
                             final boolean lastValue,
                             SimpleString lastValueKey,
                             final boolean nonDestructive,
@@ -638,7 +639,7 @@ public class ServerSessionImpl implements ServerSession, FailureListener {
                             final boolean autoCreated) throws Exception {
       if (AuditLogger.isEnabled()) {
          AuditLogger.createQueue(this, getUsername(), addressInfo, name, filterString, temporary, durable, maxConsumers, purgeOnNoConsumers,
-                  exclusive, groupRebalance, groupBuckets, lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch,
+                  exclusive, groupRebalance, groupBuckets, groupFirstKey, lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch,
                   delayBeforeDispatch, autoDelete, autoDeleteDelay, autoDeleteMessageCount, autoCreated);
       }
       final SimpleString unPrefixedName = removePrefix(name);
@@ -660,7 +661,7 @@ public class ServerSessionImpl implements ServerSession, FailureListener {
 
       server.checkQueueCreationLimit(getUsername());
 
-      Queue queue = server.createQueue(art, unPrefixedName, filterString, SimpleString.toSimpleString(getUsername()), durable, temporary, autoCreated, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, autoDelete, autoDeleteDelay, autoDeleteMessageCount, as.isAutoCreateAddresses());
+      Queue queue = server.createQueue(art, unPrefixedName, filterString, SimpleString.toSimpleString(getUsername()), durable, temporary, autoCreated, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, groupFirstKey, lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, autoDelete, autoDeleteDelay, autoDeleteMessageCount, as.isAutoCreateAddresses());
 
       if (temporary) {
          // Temporary queue in core simply means the queue will be deleted if
@@ -700,7 +701,7 @@ public class ServerSessionImpl implements ServerSession, FailureListener {
                             final boolean purgeOnNoConsumers,
                             final boolean autoCreated) throws Exception {
       AddressSettings as = server.getAddressSettingsRepository().getMatch(address.toString());
-      return createQueue(new AddressInfo(address, routingType), name, filterString, temporary, durable, maxConsumers, purgeOnNoConsumers, as.isDefaultExclusiveQueue(), as.isDefaultGroupRebalance(), as.getDefaultGroupBuckets(), as.isDefaultLastValueQueue(), as.getDefaultLastValueKey(), as.isDefaultNonDestructive(), as.getDefaultConsumersBeforeDispatch(), as.getDefaultDelayBeforeDispatch(), ActiveMQServerImpl.isAutoDelete(autoCreated, as), as.getAutoDeleteQueuesDelay(), as.getAutoDeleteQue [...]
+      return createQueue(new AddressInfo(address, routingType), name, filterString, temporary, durable, maxConsumers, purgeOnNoConsumers, as.isDefaultExclusiveQueue(), as.isDefaultGroupRebalance(), as.getDefaultGroupBuckets(), as.getDefaultGroupFirstKey(), as.isDefaultLastValueQueue(), as.getDefaultLastValueKey(), as.isDefaultNonDestructive(), as.getDefaultConsumersBeforeDispatch(), as.getDefaultDelayBeforeDispatch(), ActiveMQServerImpl.isAutoDelete(autoCreated, as), as.getAutoDeleteQueu [...]
    }
 
    @Override
@@ -739,12 +740,38 @@ public class ServerSessionImpl implements ServerSession, FailureListener {
                             final Long autoDeleteDelay,
                             final Long autoDeleteMessageCount,
                             final boolean autoCreated) throws Exception {
-      if (exclusive == null || groupRebalance == null || groupBuckets == null || lastValue == null || lastValueKey == null || nonDestructive == null || consumersBeforeDispatch == null || delayBeforeDispatch == null || autoDelete == null || autoDeleteDelay == null || autoDeleteMessageCount == null) {
+      return createQueue(address, name, routingType, filterString, temporary, durable, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, null, lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, autoDelete, autoDeleteDelay, autoDeleteMessageCount, autoCreated);
+   }
+
+   @Override
+   public Queue createQueue(final SimpleString address,
+                            final SimpleString name,
+                            final RoutingType routingType,
+                            final SimpleString filterString,
+                            final boolean temporary,
+                            final boolean durable,
+                            final int maxConsumers,
+                            final boolean purgeOnNoConsumers,
+                            final Boolean exclusive,
+                            final Boolean groupRebalance,
+                            final Integer groupBuckets,
+                            final SimpleString groupFirstKey,
+                            final Boolean lastValue,
+                            final SimpleString lastValueKey,
+                            final Boolean nonDestructive,
+                            final Integer consumersBeforeDispatch,
+                            final Long delayBeforeDispatch,
+                            final Boolean autoDelete,
+                            final Long autoDeleteDelay,
+                            final Long autoDeleteMessageCount,
+                            final boolean autoCreated) throws Exception {
+      if (exclusive == null || groupRebalance == null || groupBuckets == null || groupFirstKey == null || lastValue == null || lastValueKey == null || nonDestructive == null || consumersBeforeDispatch == null || delayBeforeDispatch == null || autoDelete == null || autoDeleteDelay == null || autoDeleteMessageCount == null) {
          AddressSettings as = server.getAddressSettingsRepository().getMatch(address.toString());
          return createQueue(new AddressInfo(address, routingType), name, filterString, temporary, durable, maxConsumers, purgeOnNoConsumers,
                  exclusive == null ? as.isDefaultExclusiveQueue() : exclusive,
                  groupRebalance == null ? as.isDefaultGroupRebalance() : groupRebalance,
                  groupBuckets == null ? as.getDefaultGroupBuckets() : groupBuckets,
+                 groupFirstKey == null ? as.getDefaultGroupFirstKey() : groupFirstKey,
                  lastValue == null ? as.isDefaultLastValueQueue() : lastValue,
                  lastValueKey == null ? as.getDefaultLastValueKey() : lastValueKey,
                  nonDestructive == null ? as.isDefaultNonDestructive() : nonDestructive,
@@ -756,7 +783,7 @@ public class ServerSessionImpl implements ServerSession, FailureListener {
                  autoCreated);
       } else {
          return createQueue(new AddressInfo(address, routingType), name, filterString, temporary, durable, maxConsumers, purgeOnNoConsumers,
-                 exclusive, groupRebalance, groupBuckets, lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, autoDelete, autoDeleteDelay, autoDeleteMessageCount, autoCreated);
+                 exclusive, groupRebalance, groupBuckets, groupFirstKey, lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, autoDelete, autoDeleteDelay, autoDeleteMessageCount, autoCreated);
       }
    }
 
@@ -775,14 +802,14 @@ public class ServerSessionImpl implements ServerSession, FailureListener {
    @Override
    public Queue createQueue(AddressInfo addressInfo, SimpleString name, SimpleString filterString, boolean temporary, boolean durable, boolean autoCreated) throws Exception {
       AddressSettings as = server.getAddressSettingsRepository().getMatch(addressInfo.getName().toString());
-      return createQueue(addressInfo, name, filterString, temporary, durable, as.getDefaultMaxConsumers(), as.isDefaultPurgeOnNoConsumers(), as.isDefaultExclusiveQueue(), as.isDefaultGroupRebalance(), as.getDefaultGroupBuckets(), as.isDefaultLastValueQueue(), as.getDefaultLastValueKey(), as.isDefaultNonDestructive(), as.getDefaultConsumersBeforeDispatch(), as.getDefaultDelayBeforeDispatch(), ActiveMQServerImpl.isAutoDelete(autoCreated, as), as.getAutoDeleteQueuesDelay(), as.getAutoDelete [...]
+      return createQueue(addressInfo, name, filterString, temporary, durable, as.getDefaultMaxConsumers(), as.isDefaultPurgeOnNoConsumers(), as.isDefaultExclusiveQueue(), as.isDefaultGroupRebalance(), as.getDefaultGroupBuckets(), as.getDefaultGroupFirstKey(), as.isDefaultLastValueQueue(), as.getDefaultLastValueKey(), as.isDefaultNonDestructive(), as.getDefaultConsumersBeforeDispatch(), as.getDefaultDelayBeforeDispatch(), ActiveMQServerImpl.isAutoDelete(autoCreated, as), as.getAutoDeleteQ [...]
    }
 
    @Override
    public Queue createQueue(AddressInfo addressInfo, SimpleString name, SimpleString filterString, boolean temporary, boolean durable, Boolean exclusive, Boolean lastValue, boolean autoCreated) throws Exception {
       AddressSettings as = server.getAddressSettingsRepository().getMatch(addressInfo.getName().toString());
       return createQueue(addressInfo, name, filterString, temporary, durable, as.getDefaultMaxConsumers(), as.isDefaultPurgeOnNoConsumers(),
-                         exclusive == null ? as.isDefaultExclusiveQueue() : exclusive, as.isDefaultGroupRebalance(), as.getDefaultGroupBuckets(), lastValue == null ? as.isDefaultLastValueQueue() : lastValue, as.getDefaultLastValueKey(), as.isDefaultNonDestructive(), as.getDefaultConsumersBeforeDispatch(), as.getDefaultDelayBeforeDispatch(), ActiveMQServerImpl.isAutoDelete(autoCreated, as), as.getAutoDeleteQueuesDelay(), as.getAutoDeleteQueuesMessageCount(), autoCreated);
+                         exclusive == null ? as.isDefaultExclusiveQueue() : exclusive, as.isDefaultGroupRebalance(), as.getDefaultGroupBuckets(), as.getDefaultGroupFirstKey(), lastValue == null ? as.isDefaultLastValueQueue() : lastValue, as.getDefaultLastValueKey(), as.isDefaultNonDestructive(), as.getDefaultConsumersBeforeDispatch(), as.getDefaultDelayBeforeDispatch(), ActiveMQServerImpl.isAutoDelete(autoCreated, as), as.getAutoDeleteQueuesDelay(), as.getAutoDeleteQueuesMessageCount(),  [...]
    }
 
    @Override
@@ -851,6 +878,29 @@ public class ServerSessionImpl implements ServerSession, FailureListener {
                                  Boolean autoDelete,
                                  Long autoDeleteDelay,
                                  Long autoDeleteMessageCount) throws Exception {
+      createSharedQueue(address, name, routingType, filterString, durable, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, null, lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, autoDelete, autoDeleteDelay, autoDeleteMessageCount);
+   }
+
+   @Override
+   public void createSharedQueue(SimpleString address,
+                                 SimpleString name,
+                                 RoutingType routingType,
+                                 SimpleString filterString,
+                                 boolean durable,
+                                 Integer maxConsumers,
+                                 Boolean purgeOnNoConsumers,
+                                 Boolean exclusive,
+                                 Boolean groupRebalance,
+                                 Integer groupBuckets,
+                                 SimpleString groupFirstKey,
+                                 Boolean lastValue,
+                                 SimpleString lastValueKey,
+                                 Boolean nonDestructive,
+                                 Integer consumersBeforeDispatch,
+                                 Long delayBeforeDispatch,
+                                 Boolean autoDelete,
+                                 Long autoDeleteDelay,
+                                 Long autoDeleteMessageCount) throws Exception {
       if (AuditLogger.isEnabled()) {
          AuditLogger.createSharedQueue(this, getUsername(), address, name, routingType, filterString, durable, maxConsumers, purgeOnNoConsumers,
                   exclusive, groupRebalance, groupBuckets, lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch,
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/settings/impl/AddressSettings.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/settings/impl/AddressSettings.java
index 1904616..924ccc9 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/settings/impl/AddressSettings.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/settings/impl/AddressSettings.java
@@ -145,6 +145,8 @@ public class AddressSettings implements Mergeable<AddressSettings>, Serializable
 
    private Integer defaultGroupBuckets = null;
 
+   private SimpleString defaultGroupFirstKey = null;
+
    private Long redistributionDelay = null;
 
    private Boolean sendToDLAOnNoRoute = null;
@@ -257,6 +259,7 @@ public class AddressSettings implements Mergeable<AddressSettings>, Serializable
       this.defaultConsumerWindowSize = other.defaultConsumerWindowSize;
       this.defaultGroupRebalance = other.defaultGroupRebalance;
       this.defaultGroupBuckets = other.defaultGroupBuckets;
+      this.defaultGroupFirstKey = other.defaultGroupFirstKey;
    }
 
    public AddressSettings() {
@@ -708,6 +711,21 @@ public class AddressSettings implements Mergeable<AddressSettings>, Serializable
    }
 
    /**
+    * @return the defaultGroupFirstKey
+    */
+   public SimpleString getDefaultGroupFirstKey() {
+      return defaultGroupFirstKey != null ? defaultGroupFirstKey : ActiveMQDefaultConfiguration.getDefaultGroupFirstKey();
+   }
+
+   /**
+    * @param defaultGroupFirstKey the defaultGroupFirstKey to set
+    */
+   public AddressSettings setDefaultGroupFirstKey(SimpleString defaultGroupFirstKey) {
+      this.defaultGroupFirstKey = defaultGroupFirstKey;
+      return this;
+   }
+
+   /**
     * @param defaultGroupBuckets the defaultGroupBuckets to set
     */
    public AddressSettings setDefaultGroupBuckets(int defaultGroupBuckets) {
diff --git a/artemis-server/src/main/resources/schema/artemis-configuration.xsd b/artemis-server/src/main/resources/schema/artemis-configuration.xsd
index 365d182..d1484d4 100644
--- a/artemis-server/src/main/resources/schema/artemis-configuration.xsd
+++ b/artemis-server/src/main/resources/schema/artemis-configuration.xsd
@@ -519,6 +519,7 @@
                         <xsd:attribute name="exclusive" type="xsd:boolean" use="optional"/>
                         <xsd:attribute name="group-rebalance" type="xsd:boolean" use="optional"/>
                         <xsd:attribute name="group-buckets" type="xsd:int" use="optional"/>
+                        <xsd:attribute name="group-first-key" type="xsd:string" use="optional"/>
                         <xsd:attribute name="last-value" type="xsd:boolean" use="optional"/>
                         <xsd:attribute name="last-value-key" type="xsd:string" use="optional"/>
                         <xsd:attribute name="non-destructive" type="xsd:boolean" use="optional"/>
@@ -3055,6 +3056,14 @@
                </xsd:annotation>
             </xsd:element>
 
+            <xsd:element name="default-group-first-key" type="xsd:string" default="JMSXFirstInGroupID" maxOccurs="1" minOccurs="0">
+               <xsd:annotation>
+                  <xsd:documentation>
+                     key used to mark a message is first in a group for a consumer
+                  </xsd:documentation>
+               </xsd:annotation>
+            </xsd:element>
+
             <xsd:element name="default-consumers-before-dispatch" type="xsd:int" default="0" maxOccurs="1" minOccurs="0">
                <xsd:annotation>
                   <xsd:documentation>
@@ -3430,6 +3439,7 @@
       <xsd:attribute name="exclusive" type="xsd:boolean" use="optional"/>
       <xsd:attribute name="group-rebalance" type="xsd:boolean" use="optional"/>
       <xsd:attribute name="group-buckets" type="xsd:int" use="optional"/>
+      <xsd:attribute name="group-first-key" type="xsd:string" use="optional"/>
       <xsd:attribute name="last-value" type="xsd:boolean" use="optional"/>
       <xsd:attribute name="last-value-key" type="xsd:string" use="optional"/>
       <xsd:attribute name="non-destructive" type="xsd:boolean" use="optional"/>
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 d241786..6a03c02 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
@@ -864,6 +864,16 @@ public class ScheduledDeliveryHandlerTest extends Assert {
       }
 
       @Override
+      public SimpleString getGroupFirstKey() {
+         return null;
+      }
+
+      @Override
+      public void setGroupFirstKey(SimpleString groupFirstKey) {
+
+      }
+
+      @Override
       public boolean isConfigurationManaged() {
          return false;
       }
diff --git a/artemis-tools/src/test/resources/artemis-configuration.xsd b/artemis-tools/src/test/resources/artemis-configuration.xsd
index ebafee2..db5141d 100644
--- a/artemis-tools/src/test/resources/artemis-configuration.xsd
+++ b/artemis-tools/src/test/resources/artemis-configuration.xsd
@@ -519,6 +519,7 @@
                         <xsd:attribute name="exclusive" type="xsd:boolean" use="optional"/>
                         <xsd:attribute name="group-rebalance" type="xsd:boolean" use="optional"/>
                         <xsd:attribute name="group-buckets" type="xsd:int" use="optional"/>
+                        <xsd:attribute name="group-first-key" type="xsd:string" use="optional"/>
                         <xsd:attribute name="last-value" type="xsd:boolean" use="optional"/>
                         <xsd:attribute name="last-value-key" type="xsd:string" use="optional"/>
                         <xsd:attribute name="non-destructive" type="xsd:boolean" use="optional"/>
@@ -3055,6 +3056,14 @@
                </xsd:annotation>
             </xsd:element>
 
+            <xsd:element name="default-group-first-key" type="xsd:string" default="JMSXFirstInGroupID" maxOccurs="1" minOccurs="0">
+               <xsd:annotation>
+                  <xsd:documentation>
+                     key used to mark a message is first in a group for a consumer
+                  </xsd:documentation>
+               </xsd:annotation>
+            </xsd:element>
+
             <xsd:element name="default-consumers-before-dispatch" type="xsd:int" default="0" maxOccurs="1" minOccurs="0">
                <xsd:annotation>
                   <xsd:documentation>
@@ -3430,6 +3439,7 @@
       <xsd:attribute name="exclusive" type="xsd:boolean" use="optional"/>
       <xsd:attribute name="group-rebalance" type="xsd:boolean" use="optional"/>
       <xsd:attribute name="group-buckets" type="xsd:int" use="optional"/>
+      <xsd:attribute name="group-first-key" type="xsd:string" use="optional"/>
       <xsd:attribute name="last-value" type="xsd:boolean" use="optional"/>
       <xsd:attribute name="last-value-key" type="xsd:string" use="optional"/>
       <xsd:attribute name="non-destructive" type="xsd:boolean" use="optional"/>
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/client/GroupingTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/client/GroupingTest.java
index cfd7b13..2dc3bcc 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/client/GroupingTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/client/GroupingTest.java
@@ -30,6 +30,7 @@ import javax.jms.Session;
 import javax.jms.TextMessage;
 import java.util.UUID;
 
+import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
 import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException;
 import org.apache.activemq.artemis.api.core.RoutingType;
 import org.apache.activemq.artemis.api.core.SimpleString;
@@ -521,6 +522,112 @@ public class GroupingTest extends JMSTestBase {
    }
 
    @Test
+   public void testDefaultGroupFirstKey() throws Exception {
+      testGroupFirstKey(null);
+   }
+
+   @Test
+   public void testCustomGroupFirstKey() throws Exception {
+      testGroupFirstKey("my-custom-key");
+   }
+
+   private void testGroupFirstKey(String customFirstGroupKey) throws Exception {
+      ConnectionFactory fact = getCF();
+      Assume.assumeFalse("only makes sense withOUT auto-group", ((ActiveMQConnectionFactory) fact).isAutoGroup());
+      Assume.assumeTrue("only makes sense withOUT explicit group-id", ((ActiveMQConnectionFactory) fact).getGroupID() == null);
+      String testQueueName = getName() + "_group_first_key";
+
+      server.createQueue(SimpleString.toSimpleString(testQueueName), RoutingType.ANYCAST, SimpleString.toSimpleString(testQueueName), null, null, true, false, false, false, false, -1, false, false, true, -1, SimpleString.toSimpleString(customFirstGroupKey), false, null, false, 0, 0, false, 0, 0, true);
+
+      JMSContext ctx = addContext(getCF().createContext(JMSContext.SESSION_TRANSACTED));
+
+      Queue testQueue = ctx.createQueue(testQueueName);
+
+
+      final String groupID1 = "groupA";
+      final String groupID2 = "groupB";
+      final String groupID3 = "groupC";
+
+
+      JMSProducer producer1 = ctx.createProducer().setProperty("JMSXGroupID", groupID1);
+      JMSProducer producer2 = ctx.createProducer().setProperty("JMSXGroupID", groupID2);
+      JMSProducer producer3 = ctx.createProducer().setProperty("JMSXGroupID", groupID3);
+
+      JMSConsumer consumer1 = ctx.createConsumer(testQueue);
+      JMSConsumer consumer2 = ctx.createConsumer(testQueue);
+
+      ctx.start();
+
+      for (int j = 0; j < 10; j++) {
+         send(ctx, testQueue, groupID1, producer1, j);
+      }
+      for (int j = 10; j < 20; j++) {
+         send(ctx, testQueue, groupID2, producer2, j);
+      }
+      for (int j = 20; j < 30; j++) {
+         send(ctx, testQueue, groupID3, producer3, j);
+      }
+
+      ctx.commit();
+
+      String firstGroupKey = customFirstGroupKey == null ? ActiveMQDefaultConfiguration.getDefaultGroupFirstKey().toString() : customFirstGroupKey;
+      //First set of msgs should go to the first consumer only
+      for (int j = 0; j < 10; j++) {
+         TextMessage tm = (TextMessage) consumer1.receive(10000);
+         assertNotNull(tm);
+         tm.acknowledge();
+         assertEquals("Message" + j, tm.getText());
+         assertEquals(tm.getStringProperty("JMSXGroupID"), groupID1);
+         if (j == 0) {
+            assertTrue(tm.getBooleanProperty(firstGroupKey));
+         } else {
+            assertFalse(tm.getBooleanProperty(firstGroupKey));
+         }
+      }
+
+      //Second set of msgs should go to the second consumers only
+      for (int j = 10; j < 20; j++) {
+         TextMessage tm = (TextMessage) consumer2.receive(10000);
+
+         assertNotNull(tm);
+
+         tm.acknowledge();
+
+         assertEquals("Message" + j, tm.getText());
+
+         assertEquals(tm.getStringProperty("JMSXGroupID"), groupID2);
+
+         if (j == 10) {
+            assertTrue(tm.getBooleanProperty(firstGroupKey));
+         } else {
+            assertFalse(tm.getBooleanProperty(firstGroupKey));
+         }
+      }
+
+      //Third set of msgs should go to the first consumer only
+      for (int j = 20; j < 30; j++) {
+         TextMessage tm = (TextMessage) consumer1.receive(10000);
+
+         assertNotNull(tm);
+
+         tm.acknowledge();
+
+         assertEquals("Message" + j, tm.getText());
+
+         assertEquals(tm.getStringProperty("JMSXGroupID"), groupID3);
+
+         if (j == 20) {
+            assertTrue(tm.getBooleanProperty(firstGroupKey));
+         } else {
+            assertFalse(tm.getBooleanProperty(firstGroupKey));
+         }
+      }
+      ctx.commit();
+
+      ctx.close();
+   }
+
+   @Test
    public void testGroupDisable() throws Exception {
       ConnectionFactory fact = getCF();
       Assume.assumeFalse("only makes sense withOUT auto-group", ((ActiveMQConnectionFactory) fact).isAutoGroup());
@@ -656,4 +763,33 @@ public class GroupingTest extends JMSTestBase {
       }
    }
 
+
+   @Test
+   public void testGroupFirstKeyUsingAddressQueueParameters() throws Exception {
+      ConnectionFactory fact = getCF();
+      Connection connection = fact.createConnection();
+
+      try {
+
+         Session session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+
+         String testQueueName = getName() + "group_first_key_param";
+
+         Queue queue = session.createQueue(testQueueName + "?group-first-key=my-custom-key");
+         assertEquals(testQueueName, queue.getQueueName());
+
+
+         ActiveMQDestination a = (ActiveMQDestination) queue;
+         assertEquals("my-custom-key", a.getQueueAttributes().getGroupFirstKey().toString());
+
+         MessageProducer producer = session.createProducer(queue);
+
+
+         QueueBinding queueBinding = (QueueBinding) server.getPostOffice().getBinding(SimpleString.toSimpleString(testQueueName));
+         assertEquals("my-custom-key", queueBinding.getQueue().getGroupFirstKey().toString());
+      } finally {
+         connection.close();
+      }
+   }
+
 }
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlUsingCoreTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlUsingCoreTest.java
index 4e4cfb7..61a6e02 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlUsingCoreTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlUsingCoreTest.java
@@ -172,6 +172,11 @@ public class ActiveMQServerControlUsingCoreTest extends ActiveMQServerControlTes
          }
 
          @Override
+         public String updateQueue(String name, String routingType, String filter, Integer maxConsumers, Boolean purgeOnNoConsumers, Boolean exclusive, Boolean groupRebalance, Integer groupBuckets, String groupFirstKey, Boolean nonDestructive, Integer consumersBeforeDispatch, Long delayBeforeDispatch, String user) throws Exception {
+            return (String) proxy.invokeOperation("updateQueue", name, routingType, filter, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, groupFirstKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, user);
+         }
+
+         @Override
          public void deleteAddress(@Parameter(name = "name", desc = "The name of the address") String name) throws Exception {
             proxy.invokeOperation("deleteAddress", name);
          }
@@ -211,7 +216,12 @@ public class ActiveMQServerControlUsingCoreTest extends ActiveMQServerControlTes
 
          @Override
          public String createQueue(String address, String routingType, String name, String filter, boolean durable, int maxConsumers, boolean purgeOnNoConsumers, boolean exclusive, boolean groupRebalance, int groupBuckets, boolean lastValue, String lastValueKey, boolean nonDestructive, int consumersBeforeDispatch, long delayBeforeDispatch, boolean autoDelete, long autoDeleteDelay, long autoDeleteMessageCount, boolean autoCreateAddress) throws Exception {
-            return (String) proxy.invokeOperation("createQueue", address, routingType, name, filter, durable, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, autoCreateAddress);
+            return (String) proxy.invokeOperation("createQueue", address, routingType, name, filter, durable, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, autoDelete, autoDeleteDelay, autoDeleteMessageCount, autoCreateAddress);
+         }
+
+         @Override
+         public String createQueue(String address, String routingType, String name, String filter, boolean durable, int maxConsumers, boolean purgeOnNoConsumers, boolean exclusive, boolean groupRebalance, int groupBuckets, String groupFirstKey, boolean lastValue, String lastValueKey, boolean nonDestructive, int consumersBeforeDispatch, long delayBeforeDispatch, boolean autoDelete, long autoDeleteDelay, long autoDeleteMessageCount, boolean autoCreateAddress) throws Exception {
+            return (String) proxy.invokeOperation("createQueue", address, routingType, name, filter, durable, maxConsumers, purgeOnNoConsumers, exclusive, groupRebalance, groupBuckets, groupFirstKey, lastValue, lastValueKey, nonDestructive, consumersBeforeDispatch, delayBeforeDispatch, autoDelete, autoDeleteDelay, autoDeleteMessageCount, autoCreateAddress);
          }
 
          @Override
diff --git a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/postoffice/impl/FakeQueue.java b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/postoffice/impl/FakeQueue.java
index 9d43b26..2944fd4 100644
--- a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/postoffice/impl/FakeQueue.java
+++ b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/postoffice/impl/FakeQueue.java
@@ -146,6 +146,16 @@ public class FakeQueue extends CriticalComponentImpl implements Queue {
    }
 
    @Override
+   public SimpleString getGroupFirstKey() {
+      return null;
+   }
+
+   @Override
+   public void setGroupFirstKey(SimpleString groupFirstKey) {
+
+   }
+
+   @Override
    public boolean isConfigurationManaged() {
       return false;
    }
diff --git a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/server/impl/fakes/FakePostOffice.java b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/server/impl/fakes/FakePostOffice.java
index 1f775e6..6c84ffc 100644
--- a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/server/impl/fakes/FakePostOffice.java
+++ b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/server/impl/fakes/FakePostOffice.java
@@ -53,6 +53,7 @@ public class FakePostOffice implements PostOffice {
                                    Boolean exclusive,
                                    Boolean groupRebalance,
                                    Integer groupBuckets,
+                                   SimpleString groupFirstKey,
                                    Boolean lastValue,
                                    Integer consumersBeforeDispatch,
                                    Long delayBeforeDispatch,


Mime
View raw message