You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@artemis.apache.org by cl...@apache.org on 2026/09/10 17:09:13 UTC

(artemis) branch main updated (354b07024b -> eb3c2b757f)

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

clebertsuconic pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/artemis.git


    from 354b07024b ARTEMIS-6250 Bump org.apache.maven.plugin-tools:maven-plugin-annotations from 3.15.2 to 3.16.0
     new 8542c574f0 Improve Core connection handshake
     new ff256d7c82 Reattachment should be disabled
     new ac6041c391 Disable discovery by default
     new 5a5c1385ff Disallow JsonUtil from performing deserialization
     new 905ae7b855 Remove unused packet handling
     new d94fc56b81 Enforce auth checks for RemoveSubscriptionInfo and other openwire commands that arrive before the ConnectionInfo is received.
     new ffb79bb987 Fix compatibility with older clients during pre-authentication topology exchange.
     new 26d189a086 Limiting Wildcards (%) on selectors
     new acdec7a902 Applying SystemPropertyHelper to previous commits
     new e5d278eac7 fixing flaky test from 'Improve Core connection handshake'
     new eb3c2b757f Further harden RemoveSubscriptionInfo handling

The 11 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../activemq/artemis/cli/commands/Create.java      |   14 +
 .../activemq/artemis/cli/commands/Upgrade.java     |    2 +-
 .../cli/commands/tools/xml/XmlDataImporter.java    |    2 +-
 .../config/ServerLocatorConfigTestAccessor.java    |    8 +-
 .../cli/commands/CreateDiscoveryPropertyTest.java  |   91 ++
 .../activemq/artemis/util/JVMArgumentTest.java     |   13 +-
 .../org/apache/activemq/cli/test/ArtemisTest.java  |    2 +-
 .../org/apache/activemq/cli/test/CliTestBase.java  |    9 +
 ...{BooleanUtil.java => SystemPropertyHelper.java} |   17 +-
 .../artemis/api/config/ServerLocatorConfig.java    |   30 +
 .../apache/activemq/artemis/api/core/JsonUtil.java |   71 +-
 .../artemis/api/core/client/ServerLocator.java     |   16 +
 .../core/management/ActiveMQManagementProxy.java   |    2 +-
 .../api/core/management/ManagementHelper.java      |    5 +-
 .../api/core/management/SimpleManagement.java      |    2 +-
 .../artemis/core/client/ActiveMQClientLogger.java  |    3 +
 .../core/client/ActiveMQClientMessageBundle.java   |    8 +
 .../core/client/impl/ClientSessionFactoryImpl.java |  101 +-
 .../core/client/impl/ClientSessionImpl.java        |  106 +-
 .../core/client/impl/ServerLocatorImpl.java        |   36 +-
 .../artemis/core/client/impl/Topology.java         |    3 +
 .../artemis/core/protocol/core/Channel.java        |   14 -
 .../core/impl/ActiveMQClientProtocolManager.java   |   18 +-
 .../protocol/core/impl/ActiveMQSessionContext.java |   31 +-
 .../core/impl/BackwardsCompatibilityUtils.java     |    8 +
 .../core/protocol/core/impl/ChannelImpl.java       |   26 +-
 .../core/protocol/core/impl/PacketDecoder.java     |   15 +-
 .../core/protocol/core/impl/PacketImpl.java        |   24 +-
 .../core/impl/wireformat/CheckFailoverMessage.java |   55 -
 .../impl/wireformat/CheckFailoverReplyMessage.java |   60 -
 .../core/impl/wireformat/ConnectMessage.java       |  301 +++++
 .../impl/wireformat/ConnectResponseMessage.java    |  136 ++
 .../core/remoting/AuthenticationListener.java      |   11 +-
 .../impl/netty/CoreFrameWithSizeLimitDecoder.java  |   68 +
 .../core/protocol/AbstractRemotingConnection.java  |   37 +
 .../spi/core/protocol/RemotingConnection.java      |   28 +
 .../spi/core/remoting/ClientProtocolManager.java   |    2 +-
 .../artemis/spi/core/remoting/SessionContext.java  |    7 -
 .../ServerLocatorConfigDiscoveryPropertyTest.java  |  126 ++
 .../core/impl/wireformat/ConnectMessageTest.java   |  214 ++++
 .../wireformat/ConnectResponseMessageTest.java     |   74 ++
 .../protocol/AbstractRemotingConnectionTest.java   |  190 +++
 .../jms/client/ActiveMQConnectionFactory.java      |    7 +-
 .../config/ServerLocatorConfigTestAccessor.java    |    8 +-
 .../artemis/uri/ConnectionFactoryURITest.java      |   18 +-
 .../junit/AbstractActiveMQClientDelegate.java      |    2 +-
 .../activemq/artemis/maven/ArtemisCLIPlugin.java   |    2 +-
 .../amqp/client/ProtonClientProtocolManager.java   |    2 +-
 .../amqp/connect/AMQPBrokerConnectionManager.java  |    2 +-
 .../client/HornetQClientProtocolManager.java       |    4 +-
 .../core/protocol/openwire/OpenWireConnection.java |   42 +-
 .../openwire/amq/OpenWireConnectionTest.java       |    2 -
 .../artemis/ra/ActiveMQResourceAdapter.java        |    9 +
 .../artemis/ra/inflow/ActiveMQActivation.java      |    2 +-
 .../selector/filter/ComparisonExpression.java      |   37 +
 .../activemq/artemis/selector/SelectorTest.java    |   69 +-
 .../artemis/core/config/ConfigurationUtils.java    |   12 +
 .../impl/ManagementRemotingConnection.java         |   19 +
 .../protocol/core/ServerSessionPacketHandler.java  |   59 -
 .../protocol/core/impl/ActiveMQPacketHandler.java  |  124 +-
 .../protocol/core/impl/CoreProtocolManager.java    |  152 ++-
 .../core/security/impl/SecurityStoreImpl.java      |    4 +
 .../artemis/core/server/ActiveMQMessageBundle.java |    8 +
 .../artemis/core/server/ActiveMQServerLogger.java  |   17 +-
 .../activemq/artemis/core/server/NodeLocator.java  |    8 -
 .../artemis/core/server/cluster/BackupManager.java |    1 +
 .../core/server/cluster/ClusterController.java     |   20 +-
 .../core/server/cluster/ClusterManager.java        |    1 +
 .../core/server/cluster/impl/BridgeImpl.java       |    2 +-
 .../server/cluster/impl/ClusterConnectionImpl.java |    4 +
 .../server/federation/FederationConnection.java    |    2 +-
 .../core/server/impl/ActiveMQServerImpl.java       |    2 +
 .../server/impl/BackupRecoveryJournalLoader.java   |    2 +-
 .../core/server/impl/PostOfficeJournalLoader.java  |   11 +-
 .../core/server/impl/PrimaryOnlyActivation.java    |    3 +-
 .../server/impl/ReplicationBackupActivation.java   |    9 +-
 .../server/impl/SharedNothingBackupActivation.java |   62 +-
 .../impl/SharedNothingPrimaryActivation.java       |    3 +-
 .../management/impl/ManagementServiceImpl.java     |   10 +-
 .../server/routing/targets/ActiveMQTarget.java     |    2 +-
 .../config/ServerLocatorConfigTestAccessor.java    |    8 +-
 ...adcastDiscoveryConfigurationValidationTest.java |  126 ++
 .../config/impl/HAPolicyConfigurationTest.java     |    6 +
 .../core/impl/ActiveMQPacketHandlerTest.java       |  266 ++++
 .../core/security/impl/SecurityStoreImplTest.java  |   37 +
 .../artemis/tests/util/ServerTestBase.java         |    9 +
 .../xa/recovery/ActiveMQXAResourceWrapper.java     |    2 +-
 docs/user-manual/clusters.adoc                     |   26 +
 docs/user-manual/core.adoc                         |   68 +
 docs/user-manual/threat-model.adoc                 |    5 +
 docs/user-manual/versions.adoc                     |   16 +
 .../config/ServerLocatorConfigTestAccessor.java    |    8 +-
 .../MultiThreadRandomReattachTestBase.java         | 1252 ------------------
 .../MultiThreadReattachSupportTestBase.java        |  268 ----
 .../artemis/tests/util/ActiveMQTestBase.java       |    9 +
 .../legacyclient/artemisTopologyTest.groovy        |   89 +-
 .../resources/legacyclient/hqTopologyTest.groovy   |   78 +-
 .../multiVersionReplica/mainServer.groovy          |    3 +-
 .../reattachCompatibility/testValidReattach.groovy |  189 +++
 .../resources/selectorWildcard/sendMessages.groovy |   77 ++
 .../filter/ComparisonExpressionTestAccessor.java   |   13 +-
 .../compatibility/LegacyClientTopologyTest.java    |   46 +-
 .../compatibility/MultiVersionReplicaTest.java     |    4 +-
 .../SelectorWildcardCompatibilityTest.java         |  126 ++
 .../ValidateReattachCompatibilityTest.java         |  121 ++
 tests/db-tests/pom.xml                             |    1 +
 .../client/impl/ClientSessionImplAccessor.java     |  133 ++
 .../client/CoreConnectHandshakeTest.java           |  124 ++
 .../integration/client/SessionFactoryTest.java     |    1 +
 .../integration/client/TemporaryQueueTest.java     |   13 +-
 .../bridge/BridgeWithDiscoveryGroupStartTest.java  |    1 +
 .../MessageRedistributionWithDiscoveryTest.java    |    1 +
 .../SymmetricClusterWithDiscoveryTest.java         |    8 +
 .../TwoWayTwoNodeClusterWithDiscoveryTest.java     |   11 +-
 .../cluster/failover/BackupAuthenticationTest.java |   34 +-
 .../DiscoveryClusterWithBackupFailoverTest.java    |    8 +
 .../cluster/failover/SecurityFailoverTest.java     |    9 +
 .../LockManagerBackupAuthenticationTest.java       |   36 +-
 .../reattach/MultiThreadRandomReattachTest.java    |   25 -
 .../NettyMultiThreadRandomReattachTest.java        |   40 -
 .../cluster/reattach/OrderReattachTest.java        |  258 ----
 .../reattach/RandomReattachIntegrationTest.java    | 1354 --------------------
 .../cluster/reattach/ReattachDisabledTest.java     |  155 +++
 .../integration/cluster/reattach/ReattachTest.java | 1030 ---------------
 .../HAClientTopologyWithDiscoveryTest.java         |   10 +-
 .../integration/cluster/util/BackupSyncDelay.java  |   10 -
 .../jms/ActiveMQConnectionFactoryTest.java         |    1 +
 .../integration/jms/SimpleJNDIClientTest.java      |    5 +-
 .../tests/integration/jms/client/RollbackTest.java |   29 +-
 .../integration/jms/cluster/JMSReconnectTest.java  |   14 -
 .../ConnectionFactorySerializationTest.java        |   10 +-
 ...nectionFactoryWithJGroupsSerializationTest.java |    1 +
 ...ListenerForConnectionTimedOutExceptionTest.java |    4 +-
 .../jms/server/JMSServerDeployerTest.java          |    1 +
 .../tests/integration/json/JsonSerialTest.java     |  225 ++++
 .../management/BroadcastGroupControlTest.java      |    1 +
 .../management/ClusterConnectionControl2Test.java  |    8 +-
 .../management/ClusterConnectionControlTest.java   |    8 +-
 .../management/ConnectionRouterControlTest.java    |    9 +-
 .../JGroupsChannelBroadcastGroupControlTest.java   |    8 +-
 .../JGroupsFileBroadcastGroupControlTest.java      |    1 +
 .../management/ManagementServiceImplTest.java      |   68 +
 .../OpenWireRemoveSubscriptionInfoFrameTest.java   |  632 +++++++++
 .../tests/integration/ra/ResourceAdapterTest.java  |    7 +-
 .../tests/integration/remoting/ReconnectTest.java  |   50 -
 .../compat/HQClientProtocolManagerTest.java        |    2 +-
 .../integration/routing/AmqpRedirectTest.java      |    8 +
 .../tests/integration/routing/KeyTypeTest.java     |    7 +
 .../integration/routing/MQTTRedirectTest.java      |    8 +
 .../tests/integration/routing/RedirectTest.java    |    8 +
 .../netty/CoreFrameWithSizeLimitDecoderTest.java   |   77 ++
 .../activemq/artemis/common/AbstractAdmin.java     |    1 +
 .../artemis/tests/unit/ra/ResourceAdapterTest.java |   47 +-
 153 files changed, 5048 insertions(+), 5035 deletions(-)
 copy artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/coreWrapper/MessageEOFException.java => artemis-cli/src/test/java/org/apache/activemq/artemis/api/config/ServerLocatorConfigTestAccessor.java (79%)
 create mode 100644 artemis-cli/src/test/java/org/apache/activemq/artemis/cli/commands/CreateDiscoveryPropertyTest.java
 copy artemis-commons/src/main/java/org/apache/activemq/artemis/utils/{BooleanUtil.java => SystemPropertyHelper.java} (70%)
 delete mode 100644 artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CheckFailoverMessage.java
 delete mode 100644 artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CheckFailoverReplyMessage.java
 create mode 100644 artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ConnectMessage.java
 create mode 100644 artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ConnectResponseMessage.java
 copy artemis-cli/src/main/java/org/apache/activemq/artemis/cli/ConfigurationException.java => artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/AuthenticationListener.java (80%)
 create mode 100644 artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/CoreFrameWithSizeLimitDecoder.java
 create mode 100644 artemis-core-client/src/test/java/org/apache/activemq/artemis/api/config/ServerLocatorConfigDiscoveryPropertyTest.java
 create mode 100644 artemis-core-client/src/test/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ConnectMessageTest.java
 create mode 100644 artemis-core-client/src/test/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ConnectResponseMessageTest.java
 create mode 100644 artemis-core-client/src/test/java/org/apache/activemq/artemis/spi/core/protocol/AbstractRemotingConnectionTest.java
 copy artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/coreWrapper/MessageEOFException.java => artemis-jms-client/src/test/java/org/apache/activemq/artemis/api/config/ServerLocatorConfigTestAccessor.java (79%)
 copy artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/coreWrapper/MessageEOFException.java => artemis-server/src/test/java/org/apache/activemq/artemis/api/config/ServerLocatorConfigTestAccessor.java (79%)
 create mode 100644 artemis-server/src/test/java/org/apache/activemq/artemis/core/config/BroadcastDiscoveryConfigurationValidationTest.java
 create mode 100644 artemis-server/src/test/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQPacketHandlerTest.java
 copy artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/coreWrapper/MessageEOFException.java => tests/artemis-test-support/src/main/java/org/apache/activemq/artemis/api/config/ServerLocatorConfigTestAccessor.java (79%)
 delete mode 100644 tests/artemis-test-support/src/main/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/MultiThreadRandomReattachTestBase.java
 delete mode 100644 tests/artemis-test-support/src/main/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/MultiThreadReattachSupportTestBase.java
 create mode 100644 tests/compatibility-tests/src/main/resources/reattachCompatibility/testValidReattach.groovy
 create mode 100644 tests/compatibility-tests/src/main/resources/selectorWildcard/sendMessages.groovy
 copy artemis-selector/src/main/java/org/apache/activemq/artemis/selector/filter/Expression.java => tests/compatibility-tests/src/test/java/org/apache/activemq/artemis/selector/filter/ComparisonExpressionTestAccessor.java (81%)
 mode change 100755 => 100644
 create mode 100644 tests/compatibility-tests/src/test/java/org/apache/activemq/artemis/tests/compatibility/SelectorWildcardCompatibilityTest.java
 create mode 100644 tests/compatibility-tests/src/test/java/org/apache/activemq/artemis/tests/compatibility/ValidateReattachCompatibilityTest.java
 create mode 100644 tests/integration-tests/src/test/java/org/apache/activemq/artemis/core/client/impl/ClientSessionImplAccessor.java
 create mode 100644 tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/client/CoreConnectHandshakeTest.java
 delete mode 100644 tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/MultiThreadRandomReattachTest.java
 delete mode 100644 tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/NettyMultiThreadRandomReattachTest.java
 delete mode 100644 tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/OrderReattachTest.java
 delete mode 100644 tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/RandomReattachIntegrationTest.java
 create mode 100644 tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/ReattachDisabledTest.java
 delete mode 100644 tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/ReattachTest.java
 create mode 100644 tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/json/JsonSerialTest.java
 create mode 100644 tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/openwire/OpenWireRemoveSubscriptionInfoFrameTest.java
 create mode 100644 tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/transports/netty/CoreFrameWithSizeLimitDecoderTest.java


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


(artemis) 10/11: fixing flaky test from 'Improve Core connection handshake'

Posted by cl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit e5d278eac79118eb46074f1c2b7ee974af2d7838
Author: Clebert Suconic <cl...@gmail.com>
AuthorDate: Tue Aug 18 12:35:12 2026 -0400

    fixing flaky test from 'Improve Core connection handshake'
    
    (cherry picked from commit 8109e0adb4ec9aaf081bb617c518bebc07ce4fc0)
---
 .../core/impl/ActiveMQPacketHandlerTest.java         | 20 ++++++++++++--------
 1 file changed, 12 insertions(+), 8 deletions(-)

diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQPacketHandlerTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQPacketHandlerTest.java
index 762898ebc0..9a0d56d70e 100644
--- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQPacketHandlerTest.java
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQPacketHandlerTest.java
@@ -17,6 +17,7 @@
 package org.apache.activemq.artemis.core.protocol.core.impl;
 
 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
@@ -54,6 +55,7 @@ public class ActiveMQPacketHandlerTest {
 
    private ActiveMQServer server;
    private Channel channel1;
+   private CountDownLatch closedLatch;
    private CoreRemotingConnection connection;
    private CoreProtocolManager protocolManager;
    private SecurityStore securityStore;
@@ -75,6 +77,13 @@ public class ActiveMQPacketHandlerTest {
       when(server.getVersion()).thenReturn(version);
       when(server.getNodeID()).thenReturn(SimpleString.of("test-node"));
       when(server.getHAPolicy()).thenReturn(mock(HAPolicy.class));
+
+      closedLatch = new CountDownLatch(1);
+
+      doAnswer((Answer<Void>) inv -> {
+         closedLatch.countDown();
+         return null;
+      }).when(connection).close();
    }
 
    /**
@@ -180,6 +189,7 @@ public class ActiveMQPacketHandlerTest {
       verify(channel1).sendAndFlush(captor.capture());
       assertInstanceOf(ActiveMQSecurityException.class, captor.getValue().getException(),
          "exception message must carry an ActiveMQSecurityException");
+      assertTrue(closedLatch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS));
       verify(connection).close();
       verify(channel1, never()).send(any(ConnectResponseMessage.class));
    }
@@ -206,6 +216,7 @@ public class ActiveMQPacketHandlerTest {
       verify(channel1).sendAndFlush(captor.capture());
       assertInstanceOf(ActiveMQSecurityException.class, captor.getValue().getException(),
          "exception message must carry an ActiveMQSecurityException");
+      assertTrue(closedLatch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS));
       verify(connection).close();
       verify(channel1, never()).send(any(ConnectResponseMessage.class));
    }
@@ -220,20 +231,13 @@ public class ActiveMQPacketHandlerTest {
 
       // Two packets: first latch counts down on the response, second triggers connection.close()
       CountDownLatch responseLatch = countdownOnChannelSend();
-      CountDownLatch closeLatch = new CountDownLatch(1);
-      doAnswer((Answer<Void>) inv -> {
-         closeLatch.countDown();
-         return null;
-      }).when(connection).close();
-
       ConnectMessage msg = new ConnectMessage("test-node", 1);
       ActiveMQPacketHandler handler = newHandler();
       handler.handlePacket(msg);
       responseLatch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
 
       handler.handlePacket(msg);
-      closeLatch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-
+      assertTrue(closedLatch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS));
       verify(channel1).send(any(ConnectResponseMessage.class));
       verify(connection).close();
    }


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


(artemis) 01/11: Improve Core connection handshake

Posted by cl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 8542c574f020cbd31ea2c0d9cb81cae160ea88d0
Author: Domenico Francesco Bruscino <br...@apache.org>
AuthorDate: Mon Jul 13 08:35:56 2026 +0200

    Improve Core connection handshake
    
    Replace the legacy failover-check packet with ConnectMessage and
    ConnectResponseMessage, adding optional connection authentication and
    client/server version fields. Propagate credentials through cluster
    links, JMS, and management clients via createSessionFactory and
    setConnectionCredentials. Add configurable Core frame size limits
    and connection handshake settings on acceptors, with docs and tests.
    
    Assisted-by: Cursor
    
    (cherry picked from commit 51b067484e3581d261fb755fb80483584ed1aed6)
    
    ARTEMIS-6244
---
 .../cli/commands/tools/xml/XmlDataImporter.java    |   2 +-
 .../artemis/api/config/ServerLocatorConfig.java    |   6 +
 .../artemis/api/core/client/ServerLocator.java     |  16 ++
 .../core/management/ActiveMQManagementProxy.java   |   2 +-
 .../api/core/management/ManagementHelper.java      |   2 +-
 .../api/core/management/SimpleManagement.java      |   2 +-
 .../artemis/core/client/ActiveMQClientLogger.java  |   3 +
 .../core/client/impl/ClientSessionFactoryImpl.java | 101 ++++---
 .../core/client/impl/ServerLocatorImpl.java        |  22 +-
 .../artemis/core/client/impl/Topology.java         |   3 +
 .../core/impl/ActiveMQClientProtocolManager.java   |  18 +-
 .../core/protocol/core/impl/ChannelImpl.java       |   8 +-
 .../core/protocol/core/impl/PacketDecoder.java     |  15 +-
 .../core/protocol/core/impl/PacketImpl.java        |  24 +-
 .../core/impl/wireformat/CheckFailoverMessage.java |  55 ----
 .../impl/wireformat/CheckFailoverReplyMessage.java |  60 ----
 .../core/impl/wireformat/ConnectMessage.java       | 301 +++++++++++++++++++++
 .../impl/wireformat/ConnectResponseMessage.java    | 136 ++++++++++
 .../core/remoting/AuthenticationListener.java      |  25 ++
 .../impl/netty/CoreFrameWithSizeLimitDecoder.java  |  68 +++++
 .../core/protocol/AbstractRemotingConnection.java  |  37 +++
 .../spi/core/protocol/RemotingConnection.java      |  28 ++
 .../spi/core/remoting/ClientProtocolManager.java   |   2 +-
 .../core/impl/wireformat/ConnectMessageTest.java   | 214 +++++++++++++++
 .../wireformat/ConnectResponseMessageTest.java     |  74 +++++
 .../protocol/AbstractRemotingConnectionTest.java   | 190 +++++++++++++
 .../jms/client/ActiveMQConnectionFactory.java      |   7 +-
 .../junit/AbstractActiveMQClientDelegate.java      |   2 +-
 .../activemq/artemis/maven/ArtemisCLIPlugin.java   |   2 +-
 .../amqp/client/ProtonClientProtocolManager.java   |   2 +-
 .../amqp/connect/AMQPBrokerConnectionManager.java  |   2 +-
 .../client/HornetQClientProtocolManager.java       |   4 +-
 .../artemis/ra/ActiveMQResourceAdapter.java        |   9 +
 .../artemis/ra/inflow/ActiveMQActivation.java      |   2 +-
 .../impl/ManagementRemotingConnection.java         |  19 ++
 .../protocol/core/impl/ActiveMQPacketHandler.java  |  50 +++-
 .../protocol/core/impl/CoreProtocolManager.java    | 136 +++++++++-
 .../core/security/impl/SecurityStoreImpl.java      |   4 +
 .../artemis/core/server/ActiveMQMessageBundle.java |   2 +
 .../artemis/core/server/ActiveMQServerLogger.java  |   6 +
 .../activemq/artemis/core/server/NodeLocator.java  |   8 -
 .../artemis/core/server/cluster/BackupManager.java |   1 +
 .../core/server/cluster/ClusterController.java     |  20 +-
 .../core/server/cluster/ClusterManager.java        |   1 +
 .../core/server/cluster/impl/BridgeImpl.java       |   2 +-
 .../server/cluster/impl/ClusterConnectionImpl.java |   4 +
 .../server/federation/FederationConnection.java    |   2 +-
 .../server/impl/BackupRecoveryJournalLoader.java   |   2 +-
 .../core/server/impl/PrimaryOnlyActivation.java    |   3 +-
 .../server/impl/ReplicationBackupActivation.java   |   9 +-
 .../server/impl/SharedNothingBackupActivation.java |  62 +++--
 .../impl/SharedNothingPrimaryActivation.java       |   3 +-
 .../server/routing/targets/ActiveMQTarget.java     |   2 +-
 .../core/impl/ActiveMQPacketHandlerTest.java       | 262 ++++++++++++++++++
 .../core/security/impl/SecurityStoreImplTest.java  |  37 +++
 .../xa/recovery/ActiveMQXAResourceWrapper.java     |   2 +-
 docs/user-manual/core.adoc                         |  68 +++++
 docs/user-manual/versions.adoc                     |  12 +
 .../multiVersionReplica/mainServer.groovy          |   3 +-
 .../compatibility/MultiVersionReplicaTest.java     |   4 +-
 .../client/CoreConnectHandshakeTest.java           | 124 +++++++++
 .../cluster/failover/BackupAuthenticationTest.java |  34 +--
 .../cluster/failover/SecurityFailoverTest.java     |   9 +
 .../LockManagerBackupAuthenticationTest.java       |  36 +--
 ...ListenerForConnectionTimedOutExceptionTest.java |   4 +-
 .../compat/HQClientProtocolManagerTest.java        |   2 +-
 .../netty/CoreFrameWithSizeLimitDecoderTest.java   |  77 ++++++
 .../activemq/artemis/common/AbstractAdmin.java     |   1 +
 .../artemis/tests/unit/ra/ResourceAdapterTest.java |  45 ++-
 69 files changed, 2209 insertions(+), 291 deletions(-)

diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/tools/xml/XmlDataImporter.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/tools/xml/XmlDataImporter.java
index 4c3780fdae..63865b4a83 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/tools/xml/XmlDataImporter.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/tools/xml/XmlDataImporter.java
@@ -182,7 +182,7 @@ public final class XmlDataImporter extends ConnectionConfigurationAbtract {
       } else {
          serverLocator = ActiveMQClient.createServerLocator(brokerURL);
       }
-      ClientSessionFactory sf = serverLocator.createSessionFactory();
+      ClientSessionFactory sf = serverLocator.createSessionFactory(user, password);
 
       ClientSession session = null;
       ClientSession managementSession = null;
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ServerLocatorConfig.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ServerLocatorConfig.java
index cd57f98449..96a146c696 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ServerLocatorConfig.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ServerLocatorConfig.java
@@ -53,6 +53,10 @@ public class ServerLocatorConfig {
    public boolean compressLargeMessage = ActiveMQClient.DEFAULT_COMPRESS_LARGE_MESSAGES;
    public boolean useTopologyForLoadBalancing = ActiveMQClient.DEFAULT_USE_TOPOLOGY_FOR_LOADBALANCING;
 
+   public String connectionUser;
+
+   public String connectionPassword;
+
    public ServerLocatorConfig() {
    }
 
@@ -89,5 +93,7 @@ public class ServerLocatorConfig {
       initialMessagePacketSize = locator.initialMessagePacketSize;
       useTopologyForLoadBalancing = locator.useTopologyForLoadBalancing;
       compressionLevel = locator.compressionLevel;
+      connectionUser = locator.connectionUser;
+      connectionPassword = locator.connectionPassword;
    }
 }
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/client/ServerLocator.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/client/ServerLocator.java
index 0f6848f8bf..9bee04b649 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/client/ServerLocator.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/client/ServerLocator.java
@@ -58,6 +58,15 @@ public interface ServerLocator extends AutoCloseable {
     */
    ClientSessionFactory createSessionFactory() throws Exception;
 
+   /**
+    * Creates a ClientSessionFactory using whatever load balancing policy is in force
+    *
+    * @param connectionUser connection user
+    * @param connectionPassword connection password
+    * @return The ClientSessionFactory
+    */
+   ClientSessionFactory createSessionFactory(String connectionUser, String connectionPassword) throws Exception;
+
    /**
     * Creates a {@link ClientSessionFactory} to a specific server. The server must already be known about by this
     * ServerLocator. This method allows the user to make a connection to a specific server bypassing any load balancing
@@ -893,4 +902,11 @@ public interface ServerLocator extends AutoCloseable {
    ServerLocator setPasswordCodec(String passwordCodec);
 
    String getPasswordCodec();
+
+   /**
+    * Sets connection-level credentials sent in {@code CONNECT} after connecting.
+    *
+    * @return this
+    */
+   ServerLocator setConnectionCredentials(String connectionUser, String connectionPassword);
 }
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQManagementProxy.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQManagementProxy.java
index 6df336a7c3..3d010aa072 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQManagementProxy.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQManagementProxy.java
@@ -41,7 +41,7 @@ public class ActiveMQManagementProxy implements AutoCloseable {
 
    public ActiveMQManagementProxy(final ServerLocator locator, final String username, final String password) throws Exception {
       serverLocator = locator;
-      sessionFactory = locator.createSessionFactory();
+      sessionFactory = locator.createSessionFactory(username, password);
       clientSession = sessionFactory.createSession(username, password, false, true, true, false, ActiveMQClient.DEFAULT_ACK_BATCH_SIZE).start();
    }
 
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ManagementHelper.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ManagementHelper.java
index f63a6ea90c..76af6a7504 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ManagementHelper.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ManagementHelper.java
@@ -106,7 +106,7 @@ public final class ManagementHelper {
    }
 
    public static void doManagement(ServerLocator locator, String user, String password, MessageAcceptor setup, MessageAcceptor ok, MessageAcceptor failed) throws Exception {
-      try (ClientSessionFactory sessionFactory = locator.createSessionFactory();
+      try (ClientSessionFactory sessionFactory = locator.createSessionFactory(user, password);
            ClientSession session = sessionFactory.createSession(user, password, false, true, true, false, ActiveMQClient.DEFAULT_ACK_BATCH_SIZE)) {
          doManagement(session, setup, ok, failed);
       }
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/SimpleManagement.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/SimpleManagement.java
index dbf1f61112..f904dd50c6 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/SimpleManagement.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/SimpleManagement.java
@@ -62,7 +62,7 @@ public class SimpleManagement implements AutoCloseable {
    public SimpleManagement open() throws Exception {
       if (session == null) {
          locator = ServerLocatorImpl.newLocator(uri);
-         sessionFactory = locator.createSessionFactory();
+         sessionFactory = locator.createSessionFactory(user, password);
          session = sessionFactory.createSession(user, password, false, true, true, false, ActiveMQClient.DEFAULT_ACK_BATCH_SIZE);
       }
       return this;
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/ActiveMQClientLogger.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/ActiveMQClientLogger.java
index ce5007d0a0..314bd8b4b9 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/ActiveMQClientLogger.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/ActiveMQClientLogger.java
@@ -352,4 +352,7 @@ public interface ActiveMQClientLogger {
 
    @LogMessage(id = 214036, value = "Connection closure to {} has been detected: {} [code={}]", level = LogMessage.Level.INFO)
    void connectionClosureDetected(String remoteAddress, String message, ActiveMQExceptionType type);
+
+   @LogMessage(id = 214037, value = "Failed to execute authentication listener", level = LogMessage.Level.ERROR)
+   void errorCallingAuthenticationListener(Throwable e);
 }
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientSessionFactoryImpl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientSessionFactoryImpl.java
index 267d38f03b..999fe90456 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientSessionFactoryImpl.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientSessionFactoryImpl.java
@@ -166,6 +166,10 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
 
    private final TransportConfiguration[] connectorConfigs;
 
+   private final String connectionUser;
+
+   private final String connectionPassword;
+
    public ClientSessionFactoryImpl(final ServerLocatorInternal serverLocator,
                                    final TransportConfiguration connectorConfig,
                                    final ServerLocatorConfig locatorConfig,
@@ -175,12 +179,11 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
                                    final Executor flowControlThreadPool,
                                    final List<Interceptor> incomingInterceptors,
                                    final List<Interceptor> outgoingInterceptors) {
-      this(serverLocator, new Pair<>(connectorConfig, null),
-               locatorConfig, reconnectAttempts, threadPool,
-               scheduledThreadPool, flowControlThreadPool, incomingInterceptors, outgoingInterceptors);
+      this(serverLocator, new Pair<>(connectorConfig, null), locatorConfig, reconnectAttempts, threadPool,
+         scheduledThreadPool, flowControlThreadPool, incomingInterceptors, outgoingInterceptors, null,
+         locatorConfig.connectionUser, locatorConfig.connectionPassword);
    }
 
-
    ClientSessionFactoryImpl(final ServerLocatorInternal serverLocator,
                             final Pair<TransportConfiguration, TransportConfiguration> connectorConfig,
                             final ServerLocatorConfig locatorConfig,
@@ -189,22 +192,10 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
                             final ScheduledExecutorService scheduledThreadPool,
                             final Executor flowControlThreadPool,
                             final List<Interceptor> incomingInterceptors,
-                            final List<Interceptor> outgoingInterceptors) {
-      this(serverLocator, connectorConfig,
-           locatorConfig, reconnectAttempts, threadPool,
-           scheduledThreadPool, flowControlThreadPool, incomingInterceptors, outgoingInterceptors, null);
-   }
-
-   ClientSessionFactoryImpl(final ServerLocatorInternal serverLocator,
-                          final Pair<TransportConfiguration, TransportConfiguration> connectorConfig,
-                          final ServerLocatorConfig locatorConfig,
-                          final int reconnectAttempts,
-                          final Executor threadPool,
-                          final ScheduledExecutorService scheduledThreadPool,
-                          final Executor flowControlThreadPool,
-                          final List<Interceptor> incomingInterceptors,
-                          final List<Interceptor> outgoingInterceptors,
-                          final TransportConfiguration[] connectorConfigs) {
+                            final List<Interceptor> outgoingInterceptors,
+                            final TransportConfiguration[] connectorConfigs,
+                            final String connectionUser,
+                            final String connectionPassword) {
       this.serverLocator = serverLocator;
 
       this.clientProtocolManager = serverLocator.newProtocolManager();
@@ -268,6 +259,10 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
       }
 
       this.connectorConfigs = connectorConfigs;
+
+      this.connectionUser = connectionUser;
+
+      this.connectionPassword = connectionPassword;
    }
 
    @Override
@@ -279,7 +274,7 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
    @Override
    public void connect(final int initialConnectAttempts) throws ActiveMQException {
       // Get the connection
-      getConnectionWithRetry(initialConnectAttempts, null);
+      internalConnectWithRetry(initialConnectAttempts, null);
 
       if (connection == null) {
          StringBuilder msg = new StringBuilder("Unable to connect to server using configuration ").append(currentConnectorConfig);
@@ -689,7 +684,11 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
                while (reconnectRetryPredicate.test(sessionsReconnected, reconnectRetries)) {
 
                   int remainingReconnectRetries = reconnectAttempts == -1 ? -1 : reconnectAttempts - reconnectRetries;
-                  reconnectRetries += getConnectionWithRetry(remainingReconnectRetries, oldConnection);
+                  try {
+                     reconnectRetries += internalConnectWithRetry(remainingReconnectRetries, oldConnection);
+                  } catch (ActiveMQException ignore) {
+                     reconnectRetries++;
+                  }
 
                   if (connection != null) {
                      sessionsReconnected = reconnectSessions(sessionsToFailover, oldConnection, me);
@@ -934,7 +933,7 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
       return !sessionFailoverError;
    }
 
-   private int getConnectionWithRetry(final int reconnectAttempts, RemotingConnection oldConnection) {
+   private int internalConnectWithRetry(final int reconnectAttempts, RemotingConnection oldConnection) throws ActiveMQException {
       if (!clientProtocolManager.isAlive()) {
          return 0;
       }
@@ -952,8 +951,8 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
             logger.debug("Trying reconnection attempt {}/{}", count, reconnectAttempts);
          }
 
-         if (getConnection() != null) {
-            if (oldConnection != null && oldConnection instanceof CoreRemotingConnection oldRemotingConnection) {
+         if (internalConnect() != null) {
+            if (oldConnection instanceof CoreRemotingConnection oldRemotingConnection) {
                // transferring old connection version into the new connection
                ((CoreRemotingConnection)connection).setChannelVersion(oldRemotingConnection.getChannelVersion());
             }
@@ -1042,12 +1041,20 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
       }
    }
 
+   @Override
+   public RemotingConnection getConnection() {
+      try {
+         return internalConnect();
+      } catch (ActiveMQException ignore) {
+         return null;
+      }
+   }
+
    //The order of connector configs to try to get a connection:
    //currentConnectorConfig, backupConfig and then lastConnectorConfig.
    //On each successful connect, the current and last will be
    //updated properly.
-   @Override
-   public RemotingConnection getConnection() {
+   public RemotingConnection internalConnect() throws ActiveMQException {
       if (closed) {
          throw new IllegalStateException("ClientSessionFactory is closed!");
       }
@@ -1063,12 +1070,33 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
 
             this.connection = connection;
 
+            if (connection != null) {
+               if (logger.isTraceEnabled()) {
+                  logger.trace("Initialize connection with user {} from {} to {} ", connectionUser,
+                     connection.getTransportLocalAddress(), connection.getRemoteAddress(), new Exception("trace"));
+               }
+            }
+
             //we check if we can actually connect.
             // we do it here as to receive the reply connection has to be not null
             //make sure to reset this.connection == null
-            if (connection != null && primaryNodeID != null) {
+            if (connection != null && (primaryNodeID != null || connectionUser != null)) {
                try {
-                  if (!clientProtocolManager.checkForFailover(primaryNodeID)) {
+                  String connectionUser = this.connectionUser;
+                  String connectionPassword = this.connectionPassword;
+
+                  if (connectionUser != null) {
+                     try {
+                        connectionUser = PasswordMaskingUtil.resolveMask(connectionUser, serverLocator.getPasswordCodec());
+                        connectionPassword = PasswordMaskingUtil.resolveMask(connectionPassword, serverLocator.getPasswordCodec());
+                     } catch (Exception e) {
+                        connection.destroy();
+                        this.connection = null;
+                        throw new ActiveMQException(e.getMessage(), e, ActiveMQExceptionType.GENERIC_EXCEPTION);
+                     }
+                  }
+
+                  if (!clientProtocolManager.sendConnect(primaryNodeID, connectionUser, connectionPassword)) {
                      connection.destroy();
                      this.connection = null;
                      return null;
@@ -1076,7 +1104,7 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
                } catch (ActiveMQException e) {
                   connection.destroy();
                   this.connection = null;
-                  return null;
+                  throw e;
                }
             }
 
@@ -1526,12 +1554,15 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
                                boolean isLast) {
 
          try {
-            // if it is our connector then set the primary id used for failover
-            if (connectorPair.getA() != null && TransportConfigurationUtil.isSameHost(connectorPair.getA(), currentConnectorConfig)) {
-               primaryNodeID = nodeID;
-            }
+            // Skip notifications for PRE_AUTH_NODE_ID
+            if (!Topology.PRE_AUTH_NODE_ID.equals(nodeID)) {
+               // if it is our connector then set the primary id used for failover
+               if (connectorPair.getA() != null && TransportConfigurationUtil.isSameHost(connectorPair.getA(), currentConnectorConfig)) {
+                  primaryNodeID = nodeID;
+               }
 
-            serverLocator.notifyNodeUp(uniqueEventID, nodeID, backupGroupName, scaleDownGroupName, connectorPair, isLast);
+               serverLocator.notifyNodeUp(uniqueEventID, nodeID, backupGroupName, scaleDownGroupName, connectorPair, isLast);
+            }
          } finally {
             if (isLast) {
                topologyReady = true;
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ServerLocatorImpl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ServerLocatorImpl.java
index b8d05736b4..6a53e704f4 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ServerLocatorImpl.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ServerLocatorImpl.java
@@ -45,6 +45,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQExceptionType;
 import org.apache.activemq.artemis.api.core.ActiveMQIllegalStateException;
 import org.apache.activemq.artemis.api.core.ActiveMQInternalErrorException;
 import org.apache.activemq.artemis.api.core.ActiveMQInterruptedException;
+import org.apache.activemq.artemis.api.core.ActiveMQSecurityException;
 import org.apache.activemq.artemis.api.core.DiscoveryGroupConfiguration;
 import org.apache.activemq.artemis.api.core.Interceptor;
 import org.apache.activemq.artemis.api.core.Pair;
@@ -326,6 +327,14 @@ public final class ServerLocatorImpl implements ServerLocatorInternal, Discovery
       return this.passwordCodec;
    }
 
+   @Override
+   public ServerLocatorImpl setConnectionCredentials(final String connectionUser, final String connectionPassword) {
+      checkWrite();
+      config.connectionUser = connectionUser;
+      config.connectionPassword = connectionPassword;
+      return this;
+   }
+
    private static DiscoveryGroup createDiscoveryGroup(String nodeID,
                                                       DiscoveryGroupConfiguration config) throws Exception {
       return new DiscoveryGroup(nodeID, config.getName(), config.getRefreshTimeout(), config.getStoppingTimeout(), config.getBroadcastEndpointFactory(), null);
@@ -671,6 +680,11 @@ public final class ServerLocatorImpl implements ServerLocatorInternal, Discovery
 
    @Override
    public ClientSessionFactory createSessionFactory() throws ActiveMQException {
+      return createSessionFactory(config.connectionUser, config.connectionPassword);
+   }
+
+   @Override
+   public ClientSessionFactory createSessionFactory(String connectionUser, String connectionPassword) throws ActiveMQException {
       assertOpen();
 
       initialize();
@@ -707,7 +721,9 @@ public final class ServerLocatorImpl implements ServerLocatorInternal, Discovery
             // try each factory in the list until we find one which works
 
             try {
-               factory = new ClientSessionFactoryImpl(this, tc, config, config.reconnectAttempts, threadPool, scheduledThreadPool, flowControlThreadPool, incomingInterceptors, outgoingInterceptors, initialConnectors);
+               factory = new ClientSessionFactoryImpl(this, tc, config, config.reconnectAttempts, threadPool,
+                  scheduledThreadPool, flowControlThreadPool, incomingInterceptors, outgoingInterceptors, initialConnectors,
+                  connectionUser, connectionPassword);
                try {
                   addToConnecting(factory);
                   // We always try to connect here with only one attempt,
@@ -1824,6 +1840,8 @@ public final class ServerLocatorImpl implements ServerLocatorInternal, Discovery
             }
             logger.trace("Rejected execution", e);
             throw e;
+         } catch (ActiveMQSecurityException e) {
+            throw e;
          } catch (Exception e) {
             if (isClosed() || skipWarnings) {
                return null;
@@ -1894,6 +1912,8 @@ public final class ServerLocatorImpl implements ServerLocatorInternal, Discovery
                   }
                }
                return factoryToUse;
+            } catch (ActiveMQSecurityException e) {
+               throw e;
             } catch (ActiveMQException e) {
                logger.trace("{}::Exception on establish connector initial connection", this, e);
                return null;
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/Topology.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/Topology.java
index 99ff680447..c24c1d1e29 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/Topology.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/Topology.java
@@ -43,6 +43,9 @@ public final class Topology {
 
    private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
+   public static final String PRE_AUTH_NODE_ID = "00000000-0000-0000-0000-000000000000";
+   public static final String PRE_AUTH_CONNECTOR_NAME = "PRE_AUTH_CONNECTOR";
+
    private final Set<ClusterTopologyListener> topologyListeners;
 
    private Executor executor;
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQClientProtocolManager.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQClientProtocolManager.java
index 97ff1fbd43..5e3f3d5858 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQClientProtocolManager.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQClientProtocolManager.java
@@ -42,8 +42,8 @@ import org.apache.activemq.artemis.core.protocol.core.Channel;
 import org.apache.activemq.artemis.core.protocol.core.ChannelHandler;
 import org.apache.activemq.artemis.core.protocol.core.CoreRemotingConnection;
 import org.apache.activemq.artemis.core.protocol.core.Packet;
-import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CheckFailoverMessage;
-import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CheckFailoverReplyMessage;
+import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ConnectMessage;
+import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ConnectResponseMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage_V2;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage_V3;
@@ -424,9 +424,13 @@ public class ActiveMQClientProtocolManager implements ClientProtocolManager {
    }
 
    @Override
-   public boolean checkForFailover(String nodeID) throws ActiveMQException {
-      CheckFailoverMessage packet = new CheckFailoverMessage(nodeID);
-      CheckFailoverReplyMessage message = (CheckFailoverReplyMessage) getChannel1().sendBlocking(packet, PacketImpl.CHECK_FOR_FAILOVER_REPLY);
+   public boolean sendConnect(String nodeID, String connectionUser, String connectionPassword) throws ActiveMQException {
+      int clientVersion = VersionLoader.getVersion().getIncrementingVersion();
+      ConnectMessage packet = (connectionUser != null)
+         ? ConnectMessage.withPlainCredentials(nodeID, clientVersion, connectionUser, connectionPassword)
+         : new ConnectMessage(nodeID, clientVersion);
+      ConnectResponseMessage message = (ConnectResponseMessage) getChannel1().sendBlocking(packet, PacketImpl.CONNECT_RESPONSE);
+      connection.setChannelVersion(message.getServerVersion());
       return message.isOkToFailover();
    }
 
@@ -497,8 +501,8 @@ public class ActiveMQClientProtocolManager implements ClientProtocolManager {
             ClusterTopologyChangeMessage_V4 topMessage = (ClusterTopologyChangeMessage_V4) packet;
             notifyTopologyChange(updateTransportConfiguration(topMessage));
             connection.setChannelVersion(topMessage.getServerVersion());
-         } else if (type == PacketImpl.CHECK_FOR_FAILOVER_REPLY) {
-            System.out.println("Channel0Handler.handlePacket");
+         } else if (type == PacketImpl.CONNECT_RESPONSE) {
+            logger.debug("Handle connect response: {}", packet);
          }
       }
 
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ChannelImpl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ChannelImpl.java
index bc33403ed6..f2d5c9cfec 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ChannelImpl.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ChannelImpl.java
@@ -241,7 +241,13 @@ public final class ChannelImpl implements Channel {
          if (responseAsyncCache != null) {
             responseAsyncCache.errorAll(activeMQException);
          }
-         response = new ActiveMQExceptionMessage(activeMQException);
+
+         // Preserve an already-delivered EXCEPTION (e.g. security failure that raced with connection
+         // close) over AMQ219016. Other packets could not unblock sendBlocking unless type and
+         // correlation match, so replacing them here is fine.
+         if (response == null || response.getType() != EXCEPTION) {
+            response = new ActiveMQExceptionMessage(activeMQException);
+         }
 
          sendCondition.signal();
       } finally {
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/PacketDecoder.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/PacketDecoder.java
index e2d2f22d46..d33f1a797f 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/PacketDecoder.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/PacketDecoder.java
@@ -24,8 +24,8 @@ import org.apache.activemq.artemis.core.protocol.core.CoreRemotingConnection;
 import org.apache.activemq.artemis.core.protocol.core.Packet;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ActiveMQExceptionMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ActiveMQExceptionMessage_V2;
-import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CheckFailoverMessage;
-import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CheckFailoverReplyMessage;
+import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ConnectMessage;
+import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ConnectResponseMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage_V2;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage_V3;
@@ -100,7 +100,7 @@ import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.SessionXAS
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.SubscribeClusterTopologyUpdatesMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.SubscribeClusterTopologyUpdatesMessageV2;
 
-import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CHECK_FOR_FAILOVER;
+import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CONNECT;
 import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CLUSTER_TOPOLOGY;
 import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CLUSTER_TOPOLOGY_V2;
 import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CLUSTER_TOPOLOGY_V3;
@@ -122,6 +122,7 @@ import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.EXC
 import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.FEDERATION_DOWNSTREAM_CONNECT;
 import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.NULL_RESPONSE;
 import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.PACKETS_CONFIRMED;
+import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CONNECT_RESPONSE;
 import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.PING;
 import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.REATTACH_SESSION;
 import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.REATTACH_SESSION_RESP;
@@ -223,8 +224,8 @@ public abstract class PacketDecoder implements Serializable {
             packet = new CreateSessionMessage();
             break;
          }
-         case CHECK_FOR_FAILOVER: {
-            packet = new CheckFailoverMessage();
+         case CONNECT: {
+            packet = new ConnectMessage();
             break;
          }
          case CREATESESSION_RESP: {
@@ -485,8 +486,8 @@ public abstract class PacketDecoder implements Serializable {
             packet = new SessionUniqueAddMetaDataMessage();
             break;
          }
-         case PacketImpl.CHECK_FOR_FAILOVER_REPLY: {
-            packet = new CheckFailoverReplyMessage();
+         case CONNECT_RESPONSE: {
+            packet = new ConnectResponseMessage();
             break;
          }
          case PacketImpl.DISCONNECT_CONSUMER_KILL: {
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/PacketImpl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/PacketImpl.java
index e6406ef727..6d9ef60390 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/PacketImpl.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/PacketImpl.java
@@ -272,9 +272,29 @@ public class PacketImpl implements Packet {
 
    public static final byte QUORUM_VOTE_REPLY = -3;
 
-   public static final byte CHECK_FOR_FAILOVER = -4;
+   /**
+    * Core connect handshake sent after the transport is established.
+    * Reuses wire packet ID {@code -4}, formerly {@link #CHECK_FOR_FAILOVER}.
+    */
+   public static final byte CONNECT = -4;
+
+   /**
+    * @deprecated Renamed to {@link #CONNECT}; retained for the same wire packet ID ({@code -4}).
+    */
+   @Deprecated
+   public static final byte CHECK_FOR_FAILOVER = CONNECT;
 
-   public static final byte CHECK_FOR_FAILOVER_REPLY = -5;
+   /**
+    * Response to a {@link #CONNECT} packet.
+    * Reuses wire packet ID {@code -5}, formerly {@link #CHECK_FOR_FAILOVER_REPLY}.
+    */
+   public static final byte CONNECT_RESPONSE = -5;
+
+   /**
+    * @deprecated Renamed to {@link #CONNECT_RESPONSE}; retained for the same wire packet ID ({@code -5}).
+    */
+   @Deprecated
+   public static final byte CHECK_FOR_FAILOVER_REPLY = CONNECT_RESPONSE;
 
    public static final byte SCALEDOWN_ANNOUNCEMENT = -6;
 
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CheckFailoverMessage.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CheckFailoverMessage.java
deleted file mode 100644
index 807fe1f812..0000000000
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CheckFailoverMessage.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.activemq.artemis.core.protocol.core.impl.wireformat;
-
-import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
-import org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl;
-
-public class CheckFailoverMessage extends PacketImpl {
-
-   private String nodeID;
-
-   public CheckFailoverMessage(final String nodeID) {
-      super(CHECK_FOR_FAILOVER);
-      this.nodeID = nodeID;
-   }
-
-   public CheckFailoverMessage() {
-      super(CHECK_FOR_FAILOVER);
-   }
-
-   @Override
-   public void encodeRest(ActiveMQBuffer buffer) {
-      buffer.writeNullableString(nodeID);
-   }
-
-   @Override
-   public void decodeRest(ActiveMQBuffer buffer) {
-      nodeID = buffer.readNullableString();
-   }
-
-   @Override
-   protected String getPacketString() {
-      StringBuilder sb = new StringBuilder(super.getPacketString());
-      sb.append(", nodeID=" + nodeID);
-      return sb.toString();
-   }
-
-   public String getNodeID() {
-      return nodeID;
-   }
-}
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CheckFailoverReplyMessage.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CheckFailoverReplyMessage.java
deleted file mode 100644
index af3e48f278..0000000000
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CheckFailoverReplyMessage.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.activemq.artemis.core.protocol.core.impl.wireformat;
-
-import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
-import org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl;
-
-public class CheckFailoverReplyMessage extends PacketImpl {
-
-   private boolean okToFailover;
-
-   public CheckFailoverReplyMessage(boolean okToFailover) {
-      super(CHECK_FOR_FAILOVER_REPLY);
-      this.okToFailover = okToFailover;
-   }
-
-   public CheckFailoverReplyMessage() {
-      super(CHECK_FOR_FAILOVER_REPLY);
-   }
-
-   @Override
-   public boolean isResponse() {
-      return true;
-   }
-
-   @Override
-   public void encodeRest(ActiveMQBuffer buffer) {
-      buffer.writeBoolean(okToFailover);
-   }
-
-   @Override
-   public void decodeRest(ActiveMQBuffer buffer) {
-      okToFailover = buffer.readBoolean();
-   }
-
-   @Override
-   protected String getPacketString() {
-      StringBuilder sb = new StringBuilder(super.getPacketString());
-      sb.append(", okToFailover=" + okToFailover);
-      return sb.toString();
-   }
-
-   public boolean isOkToFailover() {
-      return okToFailover;
-   }
-}
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ConnectMessage.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ConnectMessage.java
new file mode 100644
index 0000000000..28f1e3d99c
--- /dev/null
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ConnectMessage.java
@@ -0,0 +1,301 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.core.protocol.core.impl.wireformat;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Objects;
+
+import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
+import org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl;
+import org.apache.activemq.artemis.utils.AbstractMapPersister;
+
+/**
+ * Core connect handshake sent after the transport is established.
+ * <p>
+ * Replaces the former {@code CheckFailoverMessage} on the same wire packet ID
+ * ({@link PacketImpl#CONNECT}, {@code -4}). The node id field is unchanged;
+ * optional client version and auth fields may follow as extension fields for
+ * backward-compatible decoding.
+ * <p>
+ * Supported auth mechanisms:
+ * <ul>
+ *   <li>{@link #MECHANISM_PLAIN} – SASL PLAIN as defined in
+ *       <a href="https://www.rfc-editor.org/rfc/rfc4616">RFC 4616</a>;
+ *       {@code FIELD_AUTH_DATA} encodes
+ *       {@code authzid NUL authcid NUL passwd} as UTF-8 bytes,
+ *       where {@code authzid} (authorization identity) is always empty.</li>
+ * </ul>
+ */
+public class ConnectMessage extends PacketImpl {
+
+   /**
+    * SASL PLAIN mechanism name as defined in
+    * <a href="https://www.rfc-editor.org/rfc/rfc4616">RFC 4616</a>.
+    */
+   public static final String MECHANISM_PLAIN = "PLAIN";
+
+   static final short FIELD_CLIENT_VERSION = 1;
+   /**
+    * Auth mechanism name, e.g. {@code "PLAIN"}.
+    */
+   static final short FIELD_AUTH_MECHANISM = 2;
+   /**
+    * Auth data bytes. For PLAIN (RFC 4616):
+    * {@code authzid NUL authcid NUL passwd} encoded as UTF-8,
+    * where {@code authzid} is always empty.
+    */
+   static final short FIELD_AUTH_DATA = 3;
+
+   // ConnectMapSerializer is stateless and safe to be used from multiple threads.
+   // The state needed is on ConnectMessage and ActiveMQBuffer only
+   private static final ConnectMapSerializer mapSerializer = new ConnectMapSerializer();
+
+   private static class ConnectMapSerializer extends AbstractMapPersister<ConnectMessage> {
+      public void encode(ActiveMQBuffer buffer, ConnectMessage message) {
+         int headerPosition = buffer.writerIndex();
+         writeHeader(buffer, 0, 0); // to reserve the header, will be rewritten at the end
+
+         int fields = 0;
+
+         if (message.clientVersion > 0) {
+            writeInteger(buffer, FIELD_CLIENT_VERSION, message.clientVersion);
+            fields++;
+         }
+
+         if (message.authMechanism != null) {
+            writeString(buffer, FIELD_AUTH_MECHANISM, message.authMechanism);
+            fields++;
+         }
+
+         if (message.authData != null) {
+            byte[] data = message.authData;
+            writeByteArray(buffer, FIELD_AUTH_DATA, data.length, buf -> buf.writeBytes(data));
+            fields++;
+         }
+
+         int endPosition = buffer.writerIndex();
+         buffer.writerIndex(headerPosition);
+         writeHeader(buffer, endPosition - headerPosition, fields);
+         buffer.writerIndex(endPosition);
+      }
+
+      @Override
+      protected int getMaxAllowedElements() {
+         return 10;
+      }
+
+      @Override
+      protected void onMapReadInteger(short key, int value, ConnectMessage decodingObject) {
+         switch (key) {
+            case FIELD_CLIENT_VERSION -> {
+               decodingObject.clientVersion = value;
+            }
+         }
+      }
+
+      @Override
+      protected void onMapReadByte(short key, byte value, ConnectMessage decodingObject) {
+      }
+
+      @Override
+      protected void onMapReadBoolean(short key, boolean value, ConnectMessage decodingObject) {
+      }
+
+      @Override
+      protected void onMapReadLong(short key, long value, ConnectMessage decodingObject) {
+      }
+
+      @Override
+      protected void onMapReadString(short key, String value, ConnectMessage decodingObject) {
+         switch (key) {
+            case FIELD_AUTH_MECHANISM -> {
+               decodingObject.authMechanism = value;
+            }
+         }
+      }
+
+      @Override
+      protected void onMapReadByteArray(short key, ActiveMQBuffer slice, ConnectMessage decodingObject) {
+         switch (key) {
+            case FIELD_AUTH_DATA -> {
+               int len = slice.readableBytes();
+               byte[] bytes = new byte[len];
+               slice.readBytes(bytes);
+               decodingObject.authData = bytes;
+            }
+         }
+      }
+   }
+
+   private String nodeID;
+
+   private int clientVersion;
+
+   /**
+    * Auth mechanism name; {@code null} means legacy client — skip connection auth.
+    */
+   private String authMechanism;
+
+   /**
+    * Raw auth data; {@code null} for legacy clients.
+    */
+   private byte[] authData;
+
+   /**
+    * Creates a ConnectMessage with PLAIN credentials.
+    * <p>
+    * The auth data is encoded per
+    * <a href="https://www.rfc-editor.org/rfc/rfc4616">RFC 4616</a>:
+    * {@code authzid NUL authcid NUL passwd} as UTF-8 bytes.
+    * The authorization identity ({@code authzid}) is always empty;
+    * {@code authcid} and {@code passwd} are set from the supplied arguments.
+    */
+   public static ConnectMessage withPlainCredentials(String nodeID, int clientVersion, String username, String password) {
+      ConnectMessage msg = new ConnectMessage(nodeID, clientVersion);
+      msg.authMechanism = MECHANISM_PLAIN;
+      String authcid = username != null ? username : "";
+      String passwd = password != null ? password : "";
+      // RFC 4616: authzid NUL authcid NUL passwd
+      msg.authData = ("\0" + authcid + "\0" + passwd).getBytes(StandardCharsets.UTF_8);
+      return msg;
+   }
+
+   public ConnectMessage() {
+      super(CONNECT);
+   }
+
+   public ConnectMessage(String nodeID, int clientVersion) {
+      super(CONNECT);
+      this.nodeID = nodeID;
+      this.clientVersion = clientVersion;
+   }
+
+   @Override
+   public void encodeRest(ActiveMQBuffer buffer) {
+      buffer.writeNullableString(nodeID);
+
+      mapSerializer.encode(buffer, this);
+   }
+
+   @Override
+   public void decodeRest(ActiveMQBuffer buffer) {
+      nodeID = buffer.readNullableString();
+
+      if (buffer.readableBytes() > 0) {
+         mapSerializer.decode(buffer, this);
+      }
+   }
+
+   @Override
+   protected String getPacketString() {
+      StringBuilder sb = new StringBuilder(super.getPacketString());
+      sb.append(", nodeID=").append(nodeID);
+      sb.append(", clientVersion=").append(clientVersion);
+      if (authMechanism != null) {
+         sb.append(", authMechanism=").append(authMechanism);
+      }
+      return sb.toString();
+   }
+
+   public String getNodeID() {
+      return nodeID;
+   }
+
+   public int getClientVersion() {
+      return clientVersion;
+   }
+
+   /**
+    * Returns the auth mechanism name (e.g. {@code "PLAIN"}),
+    * or {@code null} if no mechanism was sent (legacy client — skip connection auth).
+    */
+   public String getAuthMechanism() {
+      return authMechanism;
+   }
+
+   /**
+    * Sets the auth mechanism.
+    */
+   public void setAuthMechanism(String authMechanism) {
+      this.authMechanism = authMechanism;
+   }
+
+   /**
+    * Returns the raw auth data bytes, or {@code null} if not present
+    * (e.g. legacy client).
+    */
+   public byte[] getAuthData() {
+      return authData;
+   }
+
+   /**
+    * Sets the raw auth data bytes.
+    */
+   void setAuthData(byte[] authData) {
+      this.authData = authData;
+   }
+
+   /**
+    * Decodes PLAIN auth data per
+    * <a href="https://www.rfc-editor.org/rfc/rfc4616">RFC 4616</a>
+    * ({@code authzid NUL authcid NUL passwd}) and returns a two-element array
+    * {@code [authcid, passwd]} (i.e. {@code [username, password]}).
+    * The {@code authzid} field is parsed but not returned, as it is always empty.
+    */
+   public String[] decodePlainAuthData() {
+      byte[] data = authData;
+
+      if (data == null || data.length == 0) {
+         return new String[]{"", ""};
+      }
+
+      // RFC 4616: authzid NUL authcid NUL passwd
+      // Split on NUL to obtain the three fields. Limit 3 keeps a NUL inside
+      // passwd intact should one ever appear.
+      String payload = new String(data, StandardCharsets.UTF_8);
+      String[] fields = payload.split("\0", 3);
+
+      // fields[0] = authzid (always empty), fields[1] = authcid, fields[2] = passwd
+      String authcid = fields.length > 1 ? fields[1] : "";
+      String passwd = fields.length > 2 ? fields[2] : "";
+      return new String[]{authcid, passwd};
+   }
+
+   @Override
+   public int hashCode() {
+      return super.hashCode() + Objects.hash(nodeID, clientVersion, authMechanism) + Arrays.hashCode(authData);
+   }
+
+   @Override
+   public boolean equals(Object obj) {
+      if (this == obj) {
+         return true;
+      }
+      if (!super.equals(obj)) {
+         return false;
+      }
+      if (!(obj instanceof ConnectMessage other)) {
+         return false;
+      }
+
+      return Objects.equals(nodeID, other.nodeID)
+         && Objects.equals(clientVersion, other.clientVersion)
+         && Objects.equals(authMechanism, other.authMechanism)
+         && Arrays.equals(authData, other.authData);
+   }
+}
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ConnectResponseMessage.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ConnectResponseMessage.java
new file mode 100644
index 0000000000..236f66247f
--- /dev/null
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ConnectResponseMessage.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.core.protocol.core.impl.wireformat;
+
+import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
+import org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl;
+import org.apache.activemq.artemis.utils.AbstractMapPersister;
+
+/**
+ * Response to a {@link ConnectMessage}.
+ * <p>
+ * Replaces the former {@code CheckFailoverReplyMessage} on the same wire packet ID
+ * ({@link PacketImpl#CONNECT_RESPONSE}, {@code -5}). The {@code okToFailover}
+ * field is unchanged; an optional server version may follow as an extension
+ * field for backward-compatible decoding.
+ */
+public class ConnectResponseMessage extends PacketImpl {
+
+   static final short FIELD_SERVER_VERSION = 1;
+
+   private static final ConnectResponseMapSerializer mapSerializer = new ConnectResponseMapSerializer();
+
+   private static class ConnectResponseMapSerializer extends AbstractMapPersister<ConnectResponseMessage> {
+      public void encode(ActiveMQBuffer buffer, ConnectResponseMessage message) {
+         int recordSize = headerSize() + (message.serverVersion > 0 ? payloadSizeInteger() : 0);
+         int fields = message.serverVersion > 0 ? 1 : 0;
+         // since we don't use strings, we can calculate the size ahead, no need to rewrite the header like how it's done in ConnectMessage
+         writeHeader(buffer, recordSize, fields);
+
+         if (message.serverVersion > 0) {
+            writeInteger(buffer, FIELD_SERVER_VERSION, message.serverVersion);
+         }
+
+      }
+
+      @Override
+      protected int getMaxAllowedElements() {
+         return 10;
+      }
+
+      @Override
+      protected void onMapReadInteger(short key, int value, ConnectResponseMessage decodingObject) {
+         switch (key) {
+            case FIELD_SERVER_VERSION -> {
+               decodingObject.serverVersion = value;
+            }
+         }
+      }
+
+      @Override
+      protected void onMapReadByte(short key, byte value, ConnectResponseMessage decodingObject) {
+      }
+
+      @Override
+      protected void onMapReadBoolean(short key, boolean value, ConnectResponseMessage decodingObject) {
+      }
+
+      @Override
+      protected void onMapReadLong(short key, long value, ConnectResponseMessage decodingObject) {
+      }
+
+      @Override
+      protected void onMapReadString(short key, String value, ConnectResponseMessage decodingObject) {
+      }
+
+      @Override
+      protected void onMapReadByteArray(short key, ActiveMQBuffer slice, ConnectResponseMessage decodingObject) {
+      }
+   }
+
+   private boolean okToFailover;
+
+   private int serverVersion;
+
+   public ConnectResponseMessage(boolean okToFailover, int serverVersion) {
+      super(CONNECT_RESPONSE);
+      this.okToFailover = okToFailover;
+      this.serverVersion = serverVersion;
+   }
+
+   public ConnectResponseMessage() {
+      super(CONNECT_RESPONSE);
+   }
+
+   @Override
+   public boolean isResponse() {
+      return true;
+   }
+
+   @Override
+   public void encodeRest(ActiveMQBuffer buffer) {
+      buffer.writeBoolean(okToFailover);
+
+      mapSerializer.encode(buffer, this);
+   }
+
+   @Override
+   public void decodeRest(ActiveMQBuffer buffer) {
+      okToFailover = buffer.readBoolean();
+
+      if (buffer.readableBytes() > 0) {
+         mapSerializer.decode(buffer, this);
+      }
+   }
+
+   @Override
+   protected String getPacketString() {
+      StringBuilder sb = new StringBuilder(super.getPacketString());
+      sb.append(", okToFailover=" + okToFailover);
+      sb.append(", serverVersion=" + serverVersion);
+      return sb.toString();
+   }
+
+   public boolean isOkToFailover() {
+      return okToFailover;
+   }
+
+   public int getServerVersion() {
+      return serverVersion;
+   }
+
+}
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/AuthenticationListener.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/AuthenticationListener.java
new file mode 100644
index 0000000000..6e9cd2cee1
--- /dev/null
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/AuthenticationListener.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.core.remoting;
+
+public interface AuthenticationListener {
+
+   /**
+    * called when the connection is authenticated
+    */
+   void connectionAuthenticated();
+}
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/CoreFrameWithSizeLimitDecoder.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/CoreFrameWithSizeLimitDecoder.java
new file mode 100644
index 0000000000..46f3b3779d
--- /dev/null
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/CoreFrameWithSizeLimitDecoder.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.core.remoting.impl.netty;
+
+import java.nio.ByteOrder;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.TooLongFrameException;
+import io.netty.util.AttributeKey;
+
+/**
+ * Decoder that enforces a variable maximum frame size limit
+ */
+public class CoreFrameWithSizeLimitDecoder extends ActiveMQFrameDecoder2 {
+
+   public static final AttributeKey<Integer> FRAME_SIZE_LIMIT =
+      AttributeKey.valueOf("frameSizeLimit");
+
+   private final int initialFrameSizeLimit;
+
+   private Channel channel;
+
+   public CoreFrameWithSizeLimitDecoder(final int initialFrameSizeLimit) {
+      this.initialFrameSizeLimit = initialFrameSizeLimit;
+   }
+
+   public static void setFrameSizeLimit(final Channel channel, final int frameSizeLimit) {
+      channel.attr(FRAME_SIZE_LIMIT).set(frameSizeLimit);
+   }
+
+   private static int getFrameSizeLimit(final Channel channel) {
+      final Integer limit = channel.attr(FRAME_SIZE_LIMIT).get();
+      return limit == null ? 0 : limit;
+   }
+
+   @Override
+   public void handlerAdded(final ChannelHandlerContext ctx) throws Exception {
+      channel = ctx.channel();
+      setFrameSizeLimit(channel, initialFrameSizeLimit);
+      super.handlerAdded(ctx);
+   }
+
+   @Override
+   protected long getUnadjustedFrameLength(final ByteBuf buf, final int offset, final int length, final ByteOrder order) {
+      final long unadjustedFrameLength = super.getUnadjustedFrameLength(buf, offset, length, order);
+      final int frameSizeLimit = getFrameSizeLimit(channel);
+      if (frameSizeLimit > 0 && unadjustedFrameLength > frameSizeLimit) {
+         throw new TooLongFrameException("Frame size exceeded: " + unadjustedFrameLength);
+      }
+      return unadjustedFrameLength;
+   }
+}
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/protocol/AbstractRemotingConnection.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/protocol/AbstractRemotingConnection.java
index 8814d60af1..9313878b66 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/protocol/AbstractRemotingConnection.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/protocol/AbstractRemotingConnection.java
@@ -18,6 +18,7 @@ package org.apache.activemq.artemis.spi.core.protocol;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Future;
@@ -29,6 +30,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQInterruptedException;
 import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.core.client.ActiveMQClientLogger;
 import org.apache.activemq.artemis.core.client.ActiveMQClientMessageBundle;
+import org.apache.activemq.artemis.core.remoting.AuthenticationListener;
 import org.apache.activemq.artemis.core.remoting.CloseListener;
 import org.apache.activemq.artemis.core.remoting.FailureListener;
 import org.apache.activemq.artemis.spi.core.remoting.Connection;
@@ -45,6 +47,7 @@ public abstract class AbstractRemotingConnection implements RemotingConnection {
 
    protected final List<FailureListener> failureListeners = new CopyOnWriteArrayList<>();
    protected final List<CloseListener> closeListeners = new CopyOnWriteArrayList<>();
+   protected final List<AuthenticationListener> authenticationListeners = new CopyOnWriteArrayList<>();
    protected final Connection transportConnection;
    protected final Executor executor;
    protected final long creationTime;
@@ -52,6 +55,7 @@ public abstract class AbstractRemotingConnection implements RemotingConnection {
    protected volatile boolean dataReceived;
    private String clientId;
    private Subject subject;
+   private volatile boolean authenticated;
 
    public AbstractRemotingConnection(final Connection transportConnection, final Executor executor) {
       this.transportConnection = transportConnection;
@@ -282,6 +286,39 @@ public abstract class AbstractRemotingConnection implements RemotingConnection {
       return subject;
    }
 
+   @Override
+   public void setAuthenticated() {
+      if (!this.authenticated) {
+         this.authenticated = true;
+
+         final List<AuthenticationListener> listeners = new ArrayList<>(authenticationListeners);
+         for (final AuthenticationListener listener : listeners) {
+            try {
+               listener.connectionAuthenticated();
+            } catch (final Throwable t) {
+               ActiveMQClientLogger.LOGGER.errorCallingAuthenticationListener(t);
+            }
+         }
+      }
+   }
+
+   @Override
+   public boolean isAuthenticated() {
+      return authenticated;
+   }
+
+   @Override
+   public void addAuthenticationListener(final AuthenticationListener listener) {
+      Objects.requireNonNull(listener);
+      authenticationListeners.add(listener);
+   }
+
+   @Override
+   public boolean removeAuthenticationListener(final AuthenticationListener listener) {
+      Objects.requireNonNull(listener);
+      return authenticationListeners.remove(listener);
+   }
+
    @Override
    public void setClientID(String clientId) {
       this.clientId = clientId;
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/protocol/RemotingConnection.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/protocol/RemotingConnection.java
index e24875e287..9d1e9cdb1d 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/protocol/RemotingConnection.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/protocol/RemotingConnection.java
@@ -25,6 +25,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQException;
 import org.apache.activemq.artemis.api.core.DisconnectReason;
 import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.api.core.TransportConfiguration;
+import org.apache.activemq.artemis.core.remoting.AuthenticationListener;
 import org.apache.activemq.artemis.core.remoting.CloseListener;
 import org.apache.activemq.artemis.core.remoting.FailureListener;
 import org.apache.activemq.artemis.spi.core.remoting.BufferHandler;
@@ -219,6 +220,33 @@ public interface RemotingConnection extends BufferHandler {
     */
    Subject getSubject();
 
+   /**
+    * marks this connection as authenticated
+    */
+   void setAuthenticated();
+
+   /**
+    * {@return true if the {@link RemotingConnection} is authenticated, otherwise false}
+    */
+   boolean isAuthenticated();
+
+   /**
+    * add a AuthenticationListener.
+    * <p>
+    * This will be called when the connection is authenticated.
+    *
+    * @param listener the listener to add
+    */
+   void addAuthenticationListener(AuthenticationListener listener);
+
+   /**
+    * remove a AuthenticationListener
+    *
+    * @param listener the listener to remove
+    * @return true if removed
+    */
+   boolean removeAuthenticationListener(AuthenticationListener listener);
+
    /**
     * {@return the name of the protocol for this Remoting Connection}
     */
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/ClientProtocolManager.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/ClientProtocolManager.java
index 28b6c62bc0..6fecb97bff 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/ClientProtocolManager.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/ClientProtocolManager.java
@@ -74,7 +74,7 @@ public interface ClientProtocolManager {
 
    boolean cleanupBeforeFailover(ActiveMQException cause);
 
-   boolean checkForFailover(String nodeID) throws ActiveMQException;
+   boolean sendConnect(String nodeID, String connectionUser, String connectionPassword) throws ActiveMQException;
 
    void setSessionFactory(ClientSessionFactory factory);
 
diff --git a/artemis-core-client/src/test/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ConnectMessageTest.java b/artemis-core-client/src/test/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ConnectMessageTest.java
new file mode 100644
index 0000000000..d966b9ae8b
--- /dev/null
+++ b/artemis-core-client/src/test/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ConnectMessageTest.java
@@ -0,0 +1,214 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.core.protocol.core.impl.wireformat;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.nio.charset.StandardCharsets;
+
+import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
+import org.apache.activemq.artemis.api.core.ActiveMQBuffers;
+import org.junit.jupiter.api.Test;
+
+public class ConnectMessageTest {
+
+   static final int TEST_VERSION = 123;
+
+   @Test
+   public void testRoundTrip() {
+      ConnectMessage encoded = new ConnectMessage("node-1", TEST_VERSION);
+
+      ConnectMessage decoded = roundTrip(encoded);
+
+      assertEquals("node-1", decoded.getNodeID());
+      assertEquals(TEST_VERSION, decoded.getClientVersion());
+      assertNull(decoded.getAuthMechanism());
+      assertNull(decoded.getAuthData());
+   }
+
+   @Test
+   public void testRoundTripPlainWithCredentials() {
+      ConnectMessage encoded = ConnectMessage.withPlainCredentials("node-1", TEST_VERSION, "cluster-user", "cluster-pass");
+
+      ConnectMessage decoded = roundTrip(encoded);
+
+      assertEquals("node-1", decoded.getNodeID());
+      assertEquals(TEST_VERSION, decoded.getClientVersion());
+      assertEquals(ConnectMessage.MECHANISM_PLAIN, decoded.getAuthMechanism());
+      assertPlainAuthData(decoded.getAuthData(), "cluster-user", "cluster-pass");
+   }
+
+   @Test
+   public void testRoundTripPlainWithNullOrEmptyPassword() {
+      // Both null and "" password must encode identically as an empty passwd field.
+      for (String password : new String[]{null, ""}) {
+         ConnectMessage encoded = ConnectMessage.withPlainCredentials("node-1", TEST_VERSION, "cluster-user", password);
+         ConnectMessage decoded = roundTrip(encoded);
+         assertEquals(ConnectMessage.MECHANISM_PLAIN, decoded.getAuthMechanism());
+         assertPlainAuthData(decoded.getAuthData(), "cluster-user", "");
+      }
+   }
+
+   @Test
+   public void testLegacyMessageNoMechanism() {
+      // A message with no auth mechanism simulates a legacy client — authMechanism must be null.
+      ConnectMessage decoded = new ConnectMessage();
+      ActiveMQBuffer buffer = ActiveMQBuffers.dynamicBuffer(256);
+      decoded.encodeRest(buffer); // encode the empty no-op message
+      ConnectMessage redecoded = new ConnectMessage();
+      redecoded.decodeRest(buffer);
+
+      assertNull(redecoded.getAuthMechanism(), "legacy client must yield null authMechanism");
+      assertNull(redecoded.getAuthData());
+   }
+
+   @Test
+   public void testDecodeIgnoresUnknownFutureSections() {
+      ConnectMessage encoded = ConnectMessage.withPlainCredentials("node-1", TEST_VERSION, "cluster-user", "cluster-pass");
+      ActiveMQBuffer buffer = ActiveMQBuffers.dynamicBuffer(256);
+      encoded.encodeRest(buffer);
+      buffer.writeByte((byte) 99);
+      buffer.writeInt(42);
+
+      ConnectMessage decoded = new ConnectMessage();
+      decoded.decodeRest(buffer);
+
+      assertEquals("node-1", decoded.getNodeID());
+      assertEquals(TEST_VERSION, decoded.getClientVersion());
+      assertEquals(ConnectMessage.MECHANISM_PLAIN, decoded.getAuthMechanism());
+      assertPlainAuthData(decoded.getAuthData(), "cluster-user", "cluster-pass");
+   }
+
+   @Test
+   public void testDecodePlainAuthDataEmptyUsername() {
+      ConnectMessage msg = ConnectMessage.withPlainCredentials("node-1", TEST_VERSION, "", "mypass");
+      String[] decoded = msg.decodePlainAuthData();
+      assertEquals("", decoded[0]);
+      assertEquals("mypass", decoded[1]);
+   }
+
+   @Test
+   public void testDecodePlainAuthDataBothEmpty() {
+      ConnectMessage msg = ConnectMessage.withPlainCredentials("node-1", TEST_VERSION, "", "");
+      String[] decoded = msg.decodePlainAuthData();
+      assertEquals("", decoded[0]);
+      assertEquals("", decoded[1]);
+   }
+
+   @Test
+   public void testDecodePlainAuthDataNullAuthData() {
+      // decodePlainAuthData on a message with no auth data returns empty strings
+      ConnectMessage msg = new ConnectMessage("node-1", TEST_VERSION);
+      String[] decoded = msg.decodePlainAuthData();
+      assertEquals("", decoded[0], "username must be empty");
+      assertEquals("", decoded[1], "password must be empty");
+   }
+
+   @Test
+   public void testDecodePlainAuthDataAfterRoundTrip() {
+      ConnectMessage msg = ConnectMessage.withPlainCredentials("node-1", TEST_VERSION, "cluster-user", "cluster-pass");
+      ConnectMessage decoded = roundTrip(msg);
+      String[] credentials = decoded.decodePlainAuthData();
+      assertEquals("cluster-user", credentials[0]);
+      assertEquals("cluster-pass", credentials[1]);
+   }
+
+   @Test
+   public void testRfc4616WireStructure() {
+      // Verify the raw bytes follow RFC 4616: authzid NUL authcid NUL passwd
+      // With empty authzid the bytes must be: NUL "alice" NUL "secret"
+      ConnectMessage msg = ConnectMessage.withPlainCredentials("node-1", TEST_VERSION, "alice", "secret");
+      byte[] data = msg.getAuthData();
+      byte[] expected = new byte[]{0, 'a', 'l', 'i', 'c', 'e', 0, 's', 'e', 'c', 'r', 'e', 't'};
+      assertArrayEquals(expected, data, "authData must be RFC 4616 authzid NUL authcid NUL passwd");
+   }
+
+   @Test
+   public void testDecodePlainAuthDataFromRfc4616Bytes() {
+      // Manually construct RFC 4616 bytes: authzid="" NUL authcid="bob" NUL passwd="pw"
+      // and verify decodePlainAuthData extracts authcid and passwd correctly.
+      ConnectMessage msg = new ConnectMessage("node-1", TEST_VERSION);
+      msg.setAuthData(new byte[]{0, 'b', 'o', 'b', 0, 'p', 'w'});
+      String[] credentials = msg.decodePlainAuthData();
+      assertEquals("bob", credentials[0], "authcid must be extracted from RFC 4616 bytes");
+      assertEquals("pw", credentials[1], "passwd must be extracted from RFC 4616 bytes");
+   }
+
+   @Test
+   public void testDecodePlainAuthDataFromRfc4616BytesWithNonEmptyAuthzid() {
+      // RFC 4616 allows a non-empty authzid: authzid="proxy" NUL authcid="bob" NUL passwd="pw"
+      // decodePlainAuthData must ignore authzid and still return [authcid, passwd].
+      ConnectMessage msg = new ConnectMessage("node-1", TEST_VERSION);
+      msg.setAuthData(("proxy" + "\0" + "bob" + "\0" + "pw").getBytes(StandardCharsets.UTF_8));
+      String[] credentials = msg.decodePlainAuthData();
+      assertEquals("bob", credentials[0], "authcid must be extracted even when authzid is non-empty");
+      assertEquals("pw", credentials[1], "passwd must be extracted even when authzid is non-empty");
+   }
+
+   @Test
+   public void testDecodePlainAuthDataMalformedNoNul() {
+      // No NUL at all — not a valid RFC 4616 payload.
+      // Decoder treats the entire bytes as authzid (fields[0] only), so authcid and passwd default to empty.
+      ConnectMessage msg = new ConnectMessage("node-1", TEST_VERSION);
+      msg.setAuthData("justtext".getBytes(StandardCharsets.UTF_8));
+      String[] credentials = msg.decodePlainAuthData();
+      assertEquals("", credentials[0], "malformed: no NUL — authcid must be empty");
+      assertEquals("", credentials[1], "malformed: no NUL — passwd must be empty");
+   }
+
+   @Test
+   public void testDecodePlainAuthDataMalformedOnlyOneNul() {
+      // Only one NUL: authzid NUL authcid — passwd field is missing.
+      // Decoder returns authcid from fields[1] and empty string for the missing passwd.
+      ConnectMessage msg = new ConnectMessage("node-1", TEST_VERSION);
+      msg.setAuthData(("authzid" + "\0" + "authcid").getBytes(StandardCharsets.UTF_8));
+      String[] credentials = msg.decodePlainAuthData();
+      assertEquals("authcid", credentials[0], "malformed: one NUL — authcid must be fields[1]");
+      assertEquals("", credentials[1], "malformed: one NUL — missing passwd must be empty");
+   }
+
+   @Test
+   public void testDecodePlainAuthDataMalformedLeadingNulOnly() {
+      // Leading NUL but no second NUL: authzid="" NUL authcid — passwd field is missing.
+      ConnectMessage msg = new ConnectMessage("node-1", TEST_VERSION);
+      msg.setAuthData(("\0" + "authcid").getBytes(StandardCharsets.UTF_8));
+      String[] credentials = msg.decodePlainAuthData();
+      assertEquals("authcid", credentials[0], "malformed: leading NUL only — authcid must be fields[1]");
+      assertEquals("", credentials[1], "malformed: leading NUL only — missing passwd must be empty");
+   }
+
+   /**
+    * Asserts that {@code authData} matches the RFC 4616 SASL PLAIN wire format:
+    * {@code authzid NUL authcid NUL passwd} where {@code authzid} is always empty.
+    */
+   private static void assertPlainAuthData(byte[] authData, String expectedUser, String expectedPass) {
+      // RFC 4616: authzid NUL authcid NUL passwd  (authzid is empty)
+      byte[] expected = ("" + "\0" + expectedUser + "\0" + expectedPass).getBytes(StandardCharsets.UTF_8);
+      assertArrayEquals(expected, authData);
+   }
+
+   private static ConnectMessage roundTrip(ConnectMessage encoded) {
+      ActiveMQBuffer buffer = ActiveMQBuffers.dynamicBuffer(256);
+      encoded.encodeRest(buffer);
+
+      ConnectMessage decoded = new ConnectMessage();
+      decoded.decodeRest(buffer);
+      return decoded;
+   }
+}
diff --git a/artemis-core-client/src/test/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ConnectResponseMessageTest.java b/artemis-core-client/src/test/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ConnectResponseMessageTest.java
new file mode 100644
index 0000000000..aa6f8b92f9
--- /dev/null
+++ b/artemis-core-client/src/test/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ConnectResponseMessageTest.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.core.protocol.core.impl.wireformat;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
+import org.apache.activemq.artemis.api.core.ActiveMQBuffers;
+import org.junit.jupiter.api.Test;
+
+public class ConnectResponseMessageTest {
+
+   static final int TEST_VERSION = 456;
+
+   @Test
+   public void testRoundTripWithServerVersion() {
+      ConnectResponseMessage encoded = new ConnectResponseMessage(true, TEST_VERSION);
+
+      ConnectResponseMessage decoded = roundTrip(encoded);
+
+      assertTrue(decoded.isOkToFailover());
+      assertEquals(TEST_VERSION, decoded.getServerVersion());
+   }
+
+   @Test
+   public void testRoundTripWithoutServerVersion() {
+      ConnectResponseMessage encoded = new ConnectResponseMessage(false, 0);
+
+      ConnectResponseMessage decoded = roundTrip(encoded);
+
+      assertFalse(decoded.isOkToFailover());
+      assertEquals(0, decoded.getServerVersion());
+   }
+
+   @Test
+   public void testDecodeIgnoresUnknownFutureSections() {
+      ConnectResponseMessage encoded = new ConnectResponseMessage(true, TEST_VERSION);
+      ActiveMQBuffer buffer = ActiveMQBuffers.dynamicBuffer(256);
+      encoded.encodeRest(buffer);
+      buffer.writeByte((byte) 99);
+      buffer.writeInt(42);
+
+      ConnectResponseMessage decoded = new ConnectResponseMessage();
+      decoded.decodeRest(buffer);
+
+      assertTrue(decoded.isOkToFailover());
+      assertEquals(TEST_VERSION, decoded.getServerVersion());
+   }
+
+   private static ConnectResponseMessage roundTrip(ConnectResponseMessage encoded) {
+      ActiveMQBuffer buffer = ActiveMQBuffers.dynamicBuffer(256);
+      encoded.encodeRest(buffer);
+
+      ConnectResponseMessage decoded = new ConnectResponseMessage();
+      decoded.decodeRest(buffer);
+      return decoded;
+   }
+}
diff --git a/artemis-core-client/src/test/java/org/apache/activemq/artemis/spi/core/protocol/AbstractRemotingConnectionTest.java b/artemis-core-client/src/test/java/org/apache/activemq/artemis/spi/core/protocol/AbstractRemotingConnectionTest.java
new file mode 100644
index 0000000000..84139e8485
--- /dev/null
+++ b/artemis-core-client/src/test/java/org/apache/activemq/artemis/spi/core/protocol/AbstractRemotingConnectionTest.java
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.spi.core.protocol;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.activemq.artemis.core.remoting.AuthenticationListener;
+import org.apache.activemq.artemis.spi.core.remoting.Connection;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class AbstractRemotingConnectionTest {
+
+   /** Minimal concrete subclass — only what the constructor requires. */
+   private static class TestRemotingConnection extends AbstractRemotingConnection {
+      TestRemotingConnection(Connection transport) {
+         super(transport, Runnable::run);
+      }
+
+      @Override
+      public void bufferReceived(Object connectionID, org.apache.activemq.artemis.api.core.ActiveMQBuffer buffer) {
+      }
+
+      @Override
+      public void fail(org.apache.activemq.artemis.api.core.ActiveMQException me) {
+      }
+
+      @Override
+      public void fail(org.apache.activemq.artemis.api.core.ActiveMQException me, String scaleDownTargetNodeID) {
+      }
+
+      @Override
+      public void destroy() {
+      }
+
+      @Override
+      public void disconnect(boolean criticalError) {
+      }
+
+      @Override
+      public void disconnect(String scaleDownNodeID, boolean criticalError) {
+      }
+
+      @Override
+      public String getProtocolName() {
+         return "TEST";
+      }
+   }
+
+   private TestRemotingConnection connection;
+
+   @BeforeEach
+   public void setUp() {
+      connection = new TestRemotingConnection(Mockito.mock(Connection.class));
+   }
+
+   @Test
+   public void testIsAuthenticatedFalseByDefault() {
+      assertFalse(connection.isAuthenticated());
+   }
+
+   @Test
+   public void testSetAuthenticatedMarksConnectionAsAuthenticated() {
+      connection.setAuthenticated();
+      assertTrue(connection.isAuthenticated());
+   }
+
+   @Test
+   public void testSetAuthenticatedIsIdempotent() {
+      connection.setAuthenticated();
+      connection.setAuthenticated(); // second call must be a no-op
+      assertTrue(connection.isAuthenticated());
+   }
+
+   @Test
+   public void testListenerIsCalledOnSetAuthenticated() {
+      AtomicInteger callCount = new AtomicInteger(0);
+      connection.addAuthenticationListener(callCount::incrementAndGet);
+
+      connection.setAuthenticated();
+
+      assertEquals(1, callCount.get());
+   }
+
+   @Test
+   public void testListenerIsNotCalledWhenAlreadyAuthenticated() {
+      connection.setAuthenticated(); // first call marks authenticated
+
+      AtomicInteger callCount = new AtomicInteger(0);
+      connection.addAuthenticationListener(callCount::incrementAndGet);
+
+      connection.setAuthenticated(); // second call must be a no-op
+      assertEquals(0, callCount.get());
+   }
+
+   @Test
+   public void testMultipleListenersAreAllCalled() {
+      AtomicInteger countA = new AtomicInteger(0);
+      AtomicInteger countB = new AtomicInteger(0);
+      connection.addAuthenticationListener(countA::incrementAndGet);
+      connection.addAuthenticationListener(countB::incrementAndGet);
+
+      connection.setAuthenticated();
+
+      assertEquals(1, countA.get());
+      assertEquals(1, countB.get());
+   }
+
+   @Test
+   public void testRemovedListenerIsNotCalled() {
+      AtomicInteger callCount = new AtomicInteger(0);
+      AuthenticationListener listener = callCount::incrementAndGet;
+      connection.addAuthenticationListener(listener);
+
+      boolean removed = connection.removeAuthenticationListener(listener);
+      assertTrue(removed);
+
+      connection.setAuthenticated();
+      assertEquals(0, callCount.get());
+   }
+
+   @Test
+   public void testRemoveReturnsFalseForUnknownListener() {
+      AuthenticationListener listener = () -> { };
+      assertFalse(connection.removeAuthenticationListener(listener));
+   }
+
+   @Test
+   public void testListenerThatThrowsDoesNotPreventOtherListeners() {
+      AuthenticationListener throwing = () -> {
+         throw new RuntimeException("simulated error");
+      };
+      AtomicInteger callCount = new AtomicInteger(0);
+      connection.addAuthenticationListener(throwing);
+      connection.addAuthenticationListener(callCount::incrementAndGet);
+
+      connection.setAuthenticated(); // must not propagate the exception
+
+      assertTrue(connection.isAuthenticated());
+      assertEquals(1, callCount.get());
+   }
+
+   @Test
+   public void testAddListenerRejectsNull() {
+      assertThrows(NullPointerException.class, () -> connection.addAuthenticationListener(null));
+   }
+
+   @Test
+   public void testRemoveListenerRejectsNull() {
+      assertThrows(NullPointerException.class, () -> connection.removeAuthenticationListener(null));
+   }
+
+   @Test
+   public void testListenerCanRemoveItselfDuringFiring() {
+      AtomicInteger callCount = new AtomicInteger(0);
+      AuthenticationListener[] holder = new AuthenticationListener[1];
+      holder[0] = () -> {
+         callCount.incrementAndGet();
+         connection.removeAuthenticationListener(holder[0]);
+      };
+      connection.addAuthenticationListener(holder[0]);
+
+      connection.setAuthenticated();
+
+      assertEquals(1, callCount.get());
+      // Listener removed itself — a second setAuthenticated on a fresh connection
+      // would not call it again; verify removal succeeded
+      assertFalse(connection.removeAuthenticationListener(holder[0]));
+   }
+}
diff --git a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQConnectionFactory.java b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQConnectionFactory.java
index f28eaf994d..d7b39c25ce 100644
--- a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQConnectionFactory.java
+++ b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQConnectionFactory.java
@@ -42,6 +42,7 @@ import java.security.PrivilegedAction;
 import java.util.Map;
 import java.util.Properties;
 
+import org.apache.activemq.artemis.api.core.ActiveMQException;
 import org.apache.activemq.artemis.api.core.DiscoveryGroupConfiguration;
 import org.apache.activemq.artemis.api.core.Interceptor;
 import org.apache.activemq.artemis.api.core.TransportConfiguration;
@@ -837,6 +838,7 @@ public class ActiveMQConnectionFactory extends JNDIStorable implements Connectio
    public ActiveMQConnectionFactory setUser(String user) {
       checkWrite();
       this.user = user;
+      serverLocator.setConnectionCredentials(user, password);
       return this;
    }
 
@@ -851,6 +853,7 @@ public class ActiveMQConnectionFactory extends JNDIStorable implements Connectio
    public ActiveMQConnectionFactory setPassword(String password) {
       checkWrite();
       this.password = password;
+      serverLocator.setConnectionCredentials(user, password);
       return this;
    }
 
@@ -913,7 +916,9 @@ public class ActiveMQConnectionFactory extends JNDIStorable implements Connectio
       ClientSessionFactory factory;
 
       try {
-         factory = serverLocator.createSessionFactory();
+         factory = serverLocator.createSessionFactory(username, password);
+      } catch (ActiveMQException e) {
+         throw JMSExceptionHelper.convertFromActiveMQException(e);
       } catch (Exception e) {
          JMSException jmse = new JMSException("Failed to create session factory");
 
diff --git a/artemis-junit/artemis-junit-commons/src/main/java/org/apache/activemq/artemis/junit/AbstractActiveMQClientDelegate.java b/artemis-junit/artemis-junit-commons/src/main/java/org/apache/activemq/artemis/junit/AbstractActiveMQClientDelegate.java
index 19a3668ad5..e1f990e480 100644
--- a/artemis-junit/artemis-junit-commons/src/main/java/org/apache/activemq/artemis/junit/AbstractActiveMQClientDelegate.java
+++ b/artemis-junit/artemis-junit-commons/src/main/java/org/apache/activemq/artemis/junit/AbstractActiveMQClientDelegate.java
@@ -89,7 +89,7 @@ public abstract class AbstractActiveMQClientDelegate {
    void start() {
       log.info("Starting {}", this.getClass().getSimpleName());
       try {
-         sessionFactory = serverLocator.createSessionFactory();
+         sessionFactory = serverLocator.createSessionFactory(username, password);
          session = sessionFactory.createSession(username, password, false, true, true, serverLocator.isPreAcknowledge(),
                                                 serverLocator.getAckBatchSize());
       } catch (RuntimeException runtimeEx) {
diff --git a/artemis-maven-plugin/src/main/java/org/apache/activemq/artemis/maven/ArtemisCLIPlugin.java b/artemis-maven-plugin/src/main/java/org/apache/activemq/artemis/maven/ArtemisCLIPlugin.java
index 3a25ab1c24..88ef4903c7 100644
--- a/artemis-maven-plugin/src/main/java/org/apache/activemq/artemis/maven/ArtemisCLIPlugin.java
+++ b/artemis-maven-plugin/src/main/java/org/apache/activemq/artemis/maven/ArtemisCLIPlugin.java
@@ -98,7 +98,7 @@ public class ArtemisCLIPlugin extends ArtemisAbstractPlugin {
                while (System.currentTimeMillis() <= timeout) {
                   try (ServerLocator locator = ActiveMQClient.createServerLocator(testURI)) {
                      if (testUser != null || testPassword != null || testClientID != null) {
-                        locator.createSessionFactory().createSession(testUser, testPassword, false, false, false, false, 0, testClientID).close();
+                        locator.createSessionFactory(testUser, testPassword).createSession(testUser, testPassword, false, false, false, false, 0, testClientID).close();
                      } else {
                         locator.createSessionFactory().createSession().close();
                      }
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/client/ProtonClientProtocolManager.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/client/ProtonClientProtocolManager.java
index 82744e4c5a..3fcc568a98 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/client/ProtonClientProtocolManager.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/client/ProtonClientProtocolManager.java
@@ -106,7 +106,7 @@ public class ProtonClientProtocolManager extends ProtonProtocolManager implement
    }
 
    @Override
-   public boolean checkForFailover(String nodeID) throws ActiveMQException {
+   public boolean sendConnect(String nodeID, String connectionUser, String connectionPassword) throws ActiveMQException {
       throw new UnsupportedOperationException();
    }
 
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/connect/AMQPBrokerConnectionManager.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/connect/AMQPBrokerConnectionManager.java
index f4d4ad5704..05fe29019a 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/connect/AMQPBrokerConnectionManager.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/connect/AMQPBrokerConnectionManager.java
@@ -348,7 +348,7 @@ public class AMQPBrokerConnectionManager implements ActiveMQComponent, ClientCon
       }
 
       @Override
-      public boolean checkForFailover(String nodeID) throws ActiveMQException {
+      public boolean sendConnect(String nodeID, String connectionUser, String connectionPassword) throws ActiveMQException {
          return false;
       }
 
diff --git a/artemis-protocols/artemis-hqclient-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/hornetq/client/HornetQClientProtocolManager.java b/artemis-protocols/artemis-hqclient-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/hornetq/client/HornetQClientProtocolManager.java
index 089a0d77e4..86d096bf01 100644
--- a/artemis-protocols/artemis-hqclient-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/hornetq/client/HornetQClientProtocolManager.java
+++ b/artemis-protocols/artemis-hqclient-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/hornetq/client/HornetQClientProtocolManager.java
@@ -67,8 +67,8 @@ public class HornetQClientProtocolManager extends ActiveMQClientProtocolManager
    }
 
    @Override
-   public boolean checkForFailover(String nodeID) throws ActiveMQException {
-      //HornetQ doesn't support CheckFailoverMessage packet
+   public boolean sendConnect(String nodeID, String connectionUser, String connectionPassword) throws ActiveMQException {
+      //HornetQ doesn't support connect handshake
       return true;
    }
 
diff --git a/artemis-ra/src/main/java/org/apache/activemq/artemis/ra/ActiveMQResourceAdapter.java b/artemis-ra/src/main/java/org/apache/activemq/artemis/ra/ActiveMQResourceAdapter.java
index 831fa3f7d9..6477a2b0ed 100644
--- a/artemis-ra/src/main/java/org/apache/activemq/artemis/ra/ActiveMQResourceAdapter.java
+++ b/artemis-ra/src/main/java/org/apache/activemq/artemis/ra/ActiveMQResourceAdapter.java
@@ -1198,6 +1198,10 @@ public class ActiveMQResourceAdapter implements ResourceAdapter, Serializable {
          throw new IllegalArgumentException("must provide either TransportType or DiscoveryGroupAddress and DiscoveryGroupPort for ResourceAdapter Connection Factory");
       }
 
+      cf.setUser(getUserName());
+      cf.setPassword(getPassword());
+      cf.setPasswordCodec(getPasswordCodec());
+
       cf.setUseTopologyForLoadBalancing(raProperties.isUseTopologyForLoadBalancing());
 
       cf.setEnableSharedClientID(true);
@@ -1260,6 +1264,11 @@ public class ActiveMQResourceAdapter implements ResourceAdapter, Serializable {
 
          cf = ActiveMQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.XA_CF, transportConfigurations);
       }
+
+      cf.setUser(getUserName());
+      cf.setPassword(getPassword());
+      cf.setPasswordCodec(getPasswordCodec());
+
       setParams(cf, overrideProperties);
 
       //now make sure we are HA in any way
diff --git a/artemis-ra/src/main/java/org/apache/activemq/artemis/ra/inflow/ActiveMQActivation.java b/artemis-ra/src/main/java/org/apache/activemq/artemis/ra/inflow/ActiveMQActivation.java
index 26b2250659..eb0045b4ba 100644
--- a/artemis-ra/src/main/java/org/apache/activemq/artemis/ra/inflow/ActiveMQActivation.java
+++ b/artemis-ra/src/main/java/org/apache/activemq/artemis/ra/inflow/ActiveMQActivation.java
@@ -252,7 +252,7 @@ public class ActiveMQActivation {
 
          try {
             if (cf == null) {
-               cf = factory.getServerLocator().createSessionFactory();
+               cf = factory.getServerLocator().createSessionFactory(spec.getUser(), spec.getPassword());
             }
             session = setupSession(cf);
             ActiveMQMessageHandler handler = new ActiveMQMessageHandler(factory, this, ra.getTSR(), (ClientSessionInternal) session, cf, i);
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ManagementRemotingConnection.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ManagementRemotingConnection.java
index b3487a54d0..c7850b6f4d 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ManagementRemotingConnection.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ManagementRemotingConnection.java
@@ -24,6 +24,7 @@ import java.util.concurrent.Future;
 import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
 import org.apache.activemq.artemis.api.core.ActiveMQException;
 import org.apache.activemq.artemis.api.core.SimpleString;
+import org.apache.activemq.artemis.core.remoting.AuthenticationListener;
 import org.apache.activemq.artemis.core.remoting.CloseListener;
 import org.apache.activemq.artemis.core.remoting.FailureListener;
 import org.apache.activemq.artemis.core.server.MessageReference;
@@ -180,6 +181,24 @@ public class ManagementRemotingConnection implements RemotingConnection {
       return subject;
    }
 
+   @Override
+   public void setAuthenticated() {
+   }
+
+   @Override
+   public boolean isAuthenticated() {
+      return subject != null;
+   }
+
+   @Override
+   public void addAuthenticationListener(AuthenticationListener listener) {
+   }
+
+   @Override
+   public boolean removeAuthenticationListener(AuthenticationListener listener) {
+      return false;
+   }
+
    @Override
    public String getProtocolName() {
       return null;
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQPacketHandler.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQPacketHandler.java
index 87fc1c00c1..789e14bafc 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQPacketHandler.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQPacketHandler.java
@@ -33,8 +33,8 @@ import org.apache.activemq.artemis.core.protocol.core.CoreRemotingConnection;
 import org.apache.activemq.artemis.core.protocol.core.Packet;
 import org.apache.activemq.artemis.core.protocol.core.ServerSessionPacketHandler;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ActiveMQExceptionMessage;
-import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CheckFailoverMessage;
-import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CheckFailoverReplyMessage;
+import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ConnectMessage;
+import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ConnectResponseMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateQueueMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSessionMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSessionMessage_V2;
@@ -70,6 +70,8 @@ public class ActiveMQPacketHandler implements ChannelHandler {
 
    private final Actor<Packet> packetActor;
 
+   private boolean connectReceived;
+
    public ActiveMQPacketHandler(final CoreProtocolManager protocolManager,
                                 final ActiveMQServer server,
                                 final Channel channel1,
@@ -107,10 +109,10 @@ public class ActiveMQPacketHandler implements ChannelHandler {
 
             break;
          }
-         case PacketImpl.CHECK_FOR_FAILOVER: {
-            CheckFailoverMessage request = (CheckFailoverMessage) packet;
+         case PacketImpl.CONNECT: {
+            ConnectMessage request = (ConnectMessage) packet;
 
-            handleCheckForFailover(request);
+            handleConnect(request);
 
             break;
          }
@@ -136,10 +138,42 @@ public class ActiveMQPacketHandler implements ChannelHandler {
       }
    }
 
-   private void handleCheckForFailover(CheckFailoverMessage failoverMessage) {
-      String nodeID = failoverMessage.getNodeID();
+   private void handleConnect(ConnectMessage connectMessage) {
+      if (connectReceived) {
+         ActiveMQServerLogger.LOGGER.invalidPacket(connectMessage);
+         connection.close();
+         return;
+      }
+      connectReceived = true;
+
+      connection.setChannelVersion(connectMessage.getClientVersion());
+
+      // Legacy clients omit authMechanism; skip connection auth and rely on session creation.
+      if (server.getSecurityStore().isSecurityEnabled() &&
+         protocolManager.isCoreConnectionSecurityEnabled() &&
+         connectMessage.getAuthMechanism() != null) {
+
+         try {
+            if (ConnectMessage.MECHANISM_PLAIN.equals(connectMessage.getAuthMechanism())) {
+               String[] credentials = connectMessage.decodePlainAuthData();
+               server.validateUser(credentials[0], credentials[1], connection, protocolManager.getSecurityDomain());
+            } else {
+               throw ActiveMQMessageBundle.BUNDLE.authenticationMechanismNotSupported(connectMessage.getAuthMechanism());
+            }
+         } catch (Exception e) {
+            // Flush before close so the exception is on the wire ahead of TCP FIN.
+            // ChannelImpl.returnBlocking preserves an already-delivered EXCEPTION to avoid AMQ219016.
+            ActiveMQException messageException = e instanceof ActiveMQException ? (ActiveMQException) e
+               : new ActiveMQInternalErrorException("Connection authentication failed", e);
+            channel1.sendAndFlush(new ActiveMQExceptionMessage(messageException));
+            connection.close();
+            return;
+         }
+      }
+
+      String nodeID = connectMessage.getNodeID();
       boolean okToFailover = nodeID == null || server.getNodeID().toString().equals(nodeID) || !(server.getHAPolicy().canScaleDown() && !server.hasScaledDown(SimpleString.of(nodeID)));
-      channel1.send(new CheckFailoverReplyMessage(okToFailover));
+      channel1.send(new ConnectResponseMessage(okToFailover, server.getVersion().getIncrementingVersion()));
    }
 
    private void handleCreateSession(final CreateSessionMessage request) {
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/CoreProtocolManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/CoreProtocolManager.java
index c8a17b09a5..ce8cad8d2f 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/CoreProtocolManager.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/CoreProtocolManager.java
@@ -38,6 +38,7 @@ import org.apache.activemq.artemis.api.core.TransportConfiguration;
 import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
 import org.apache.activemq.artemis.api.core.client.ClusterTopologyListener;
 import org.apache.activemq.artemis.api.core.client.TopologyMember;
+import org.apache.activemq.artemis.core.client.impl.Topology;
 import org.apache.activemq.artemis.core.config.Configuration;
 import org.apache.activemq.artemis.core.config.FederationConfiguration;
 import org.apache.activemq.artemis.core.config.federation.FederationConnectionConfiguration;
@@ -60,9 +61,12 @@ import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.Federation
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.Ping;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.SubscribeClusterTopologyUpdatesMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.SubscribeClusterTopologyUpdatesMessageV2;
+import org.apache.activemq.artemis.core.remoting.AuthenticationListener;
 import org.apache.activemq.artemis.core.remoting.FailureListener;
 import org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnection;
 import org.apache.activemq.artemis.core.remoting.impl.netty.ActiveMQFrameDecoder2;
+import org.apache.activemq.artemis.core.remoting.impl.netty.CoreFrameWithSizeLimitDecoder;
+import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnection;
 import org.apache.activemq.artemis.core.remoting.impl.netty.NettyServerConnection;
 import org.apache.activemq.artemis.core.server.ActiveMQServer;
 import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
@@ -76,10 +80,15 @@ import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal;
 import org.apache.activemq.artemis.utils.SecurityManagerUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+
 import java.lang.invoke.MethodHandles;
 
 public class CoreProtocolManager implements ProtocolManager<Interceptor, ActiveMQRoutingHandler> {
 
+   public static final int CORE_INITIAL_MAX_FRAME_SIZE = 4 * 1024;
+
+   public static final int CORE_MAX_FRAME_SIZE = 128 * 1024;
+
    private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
    private static final List<String> websocketRegistryNames = Collections.emptyList();
@@ -98,6 +107,12 @@ public class CoreProtocolManager implements ProtocolManager<Interceptor, ActiveM
 
    private final ActiveMQRoutingHandler routingHandler;
 
+   private boolean coreConnectionSecurityEnabled = true;
+
+   private int coreInitialMaxFrameSize = CORE_INITIAL_MAX_FRAME_SIZE;
+
+   private int coreMaxFrameSize = CORE_MAX_FRAME_SIZE;
+
    public CoreProtocolManager(final CoreProtocolManagerFactory factory,
                               final ActiveMQServer server,
                               final List<Interceptor> incomingInterceptors,
@@ -118,6 +133,30 @@ public class CoreProtocolManager implements ProtocolManager<Interceptor, ActiveM
       return protocolManagerFactory;
    }
 
+   public boolean isCoreConnectionSecurityEnabled() {
+      return coreConnectionSecurityEnabled;
+   }
+
+   public void setCoreConnectionSecurityEnabled(boolean coreConnectionSecurityEnabled) {
+      this.coreConnectionSecurityEnabled = coreConnectionSecurityEnabled;
+   }
+
+   public int getCoreInitialMaxFrameSize() {
+      return coreInitialMaxFrameSize;
+   }
+
+   public void setCoreInitialMaxFrameSize(int coreInitialMaxFrameSize) {
+      this.coreInitialMaxFrameSize = coreInitialMaxFrameSize;
+   }
+
+   public int getCoreMaxFrameSize() {
+      return coreMaxFrameSize;
+   }
+
+   public void setCoreMaxFrameSize(int coreMaxFrameSize) {
+      this.coreMaxFrameSize = coreMaxFrameSize;
+   }
+
    @Override
    public void updateInterceptors(List<BaseInterceptor> incoming, List<BaseInterceptor> outgoing) {
       this.incomingInterceptors.clear();
@@ -168,6 +207,16 @@ public class CoreProtocolManager implements ProtocolManager<Interceptor, ActiveM
 
       rc.getChannel(CHANNEL_ID.FEDERATION.id, -1).setHandler(new FederationChannelHandler(acceptorUsed, rc));
 
+      final Connection transportConnection = rc.getTransportConnection();
+      if (transportConnection instanceof NettyConnection nettyConnection) {
+         rc.addAuthenticationListener(new AuthenticationListener() {
+            @Override
+            public void connectionAuthenticated() {
+               rc.removeAuthenticationListener(this);
+               CoreFrameWithSizeLimitDecoder.setFrameSizeLimit(nettyConnection.getChannel(), getCoreMaxFrameSize());
+            }
+         });
+      }
       return entry;
    }
 
@@ -192,7 +241,11 @@ public class CoreProtocolManager implements ProtocolManager<Interceptor, ActiveM
 
    @Override
    public void addChannelHandlers(ChannelPipeline pipeline) {
-      pipeline.addLast("activemq-decoder", new ActiveMQFrameDecoder2());
+      if (server.getSecurityStore().isSecurityEnabled() && isCoreConnectionSecurityEnabled()) {
+         pipeline.addLast("activemq-decoder", new CoreFrameWithSizeLimitDecoder(getCoreInitialMaxFrameSize()));
+      } else {
+         pipeline.addLast("activemq-decoder", new ActiveMQFrameDecoder2());
+      }
    }
 
    @Override
@@ -270,6 +323,10 @@ public class CoreProtocolManager implements ProtocolManager<Interceptor, ActiveM
       private final Acceptor acceptorUsed;
       private final CoreRemotingConnection rc;
 
+      private boolean preAuthTopologySent = false;
+      private boolean postAuthTopologySent = false;
+      private boolean topologySubscribed = false;
+
       private LocalChannelHandler(final Configuration config,
                                   final ConnectionEntry entry,
                                   final Channel channel0,
@@ -296,11 +353,82 @@ public class CoreProtocolManager implements ProtocolManager<Interceptor, ActiveM
             channel0.send(packet);
          } else if (packet.getType() == PacketImpl.SUBSCRIBE_TOPOLOGY
             || packet.getType() == PacketImpl.SUBSCRIBE_TOPOLOGY_V2) {
-            SubscribeClusterTopologyUpdatesMessage msg = (SubscribeClusterTopologyUpdatesMessage) packet;
+            // Reject multiple subscriptions: only one topology subscription is permitted per connection.
+            // A second request indicates a misbehaving or malicious client; close the connection immediately.
+            if (topologySubscribed) {
+               ActiveMQServerLogger.LOGGER.multipleTopologySubscriptions(rc.getRemoteAddress());
+               rc.close();
+               return;
+            }
+            topologySubscribed = true;
 
             if (packet.getType() == PacketImpl.SUBSCRIBE_TOPOLOGY_V2) {
                channel0.getConnection().setChannelVersion(
-                  ((SubscribeClusterTopologyUpdatesMessageV2) msg).getClientVersion());
+                  ((SubscribeClusterTopologyUpdatesMessageV2) packet).getClientVersion());
+            }
+
+            if (server.getSecurityStore().isSecurityEnabled() && isCoreConnectionSecurityEnabled() && !rc.isAuthenticated()) {
+               if (((SubscribeClusterTopologyUpdatesMessage)packet).isClusterConnection()) {
+                  ActiveMQServerLogger.LOGGER.unauthenticatedClusterTopologySubscriptionRequest(rc.getRemoteAddress());
+               }
+
+               sendPreAuthTopology();
+            } else {
+               sendPostAuthTopology();
+            }
+         }
+      }
+
+      // Send a single dummy topology response to unblock the client's isLast=true wait (the client blocks
+      // until it receives a topology message before proceeding to CREATE_SESSION). The dummy response
+      // uses PRE_AUTH_NODE_ID and an empty connector pair so no real broker identity or connectivity
+      // information is disclosed to unauthenticated callers.
+      private void sendPreAuthTopology() {
+         preAuthTopologySent = true;
+         Pair<TransportConfiguration, TransportConfiguration> preAuthConfig = new Pair<>(null, new TransportConfiguration(
+            rc.getTransportConnection().getConnectorConfig().getFactoryClassName(), null, Topology.PRE_AUTH_CONNECTOR_NAME));
+
+         logger.debug("Sending pre-authentication topology to unauthenticated connection from {}.", rc.getRemoteAddress());
+
+         entry.connectionExecutor.execute(() -> {
+            if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V4)) {
+               channel0.send(new ClusterTopologyChangeMessage_V4(System.currentTimeMillis(), Topology.PRE_AUTH_NODE_ID,
+                  null, null, preAuthConfig, true, server.getVersion().getIncrementingVersion()));
+            } else if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V2)) {
+               channel0.send(new ClusterTopologyChangeMessage_V2(System.currentTimeMillis(),
+                  Topology.PRE_AUTH_NODE_ID, null, preAuthConfig, true));
+            } else {
+               channel0.send(new ClusterTopologyChangeMessage(Topology.PRE_AUTH_NODE_ID, preAuthConfig, true));
+            }
+         });
+
+         // Defer the real topology subscription until authentication completes. The subject
+         // listener fires once when setSubject() is called with a non-null subject (i.e. after
+         // a successful CONNECT or CREATE_SESSION), then removes itself to avoid further executions.
+         rc.addAuthenticationListener(new AuthenticationListener() {
+            @Override
+            public void connectionAuthenticated() {
+               rc.removeAuthenticationListener(this);
+               sendPostAuthTopology();
+            }
+         });
+      }
+
+      private void sendPostAuthTopology() {
+         if (!postAuthTopologySent) {
+            postAuthTopologySent = true;
+
+            if (preAuthTopologySent) {
+               // Remove the placeholder entry the client added when it received the pre-auth dummy response.
+               // Without this nodeDown the client's topology map would contain PRE_AUTH_NODE_ID alongside
+               // the real nodes delivered below.
+               entry.connectionExecutor.execute(() -> {
+                  if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V2)) {
+                     channel0.send(new ClusterTopologyChangeMessage_V2(System.currentTimeMillis(), Topology.PRE_AUTH_NODE_ID));
+                  } else {
+                     channel0.send(new ClusterTopologyChangeMessage(Topology.PRE_AUTH_NODE_ID));
+                  }
+               });
             }
 
             final ClusterTopologyListener listener = new ClusterTopologyListener() {
@@ -426,7 +554,7 @@ public class CoreProtocolManager implements ProtocolManager<Interceptor, ActiveM
       public void handlePacket(final Packet packet) {
          if (packet.getType() == PacketImpl.FEDERATION_DOWNSTREAM_CONNECT) {
             if (server.getSecurityStore().isSecurityEnabled()) {
-               if (rc.getSubject() == null) {
+               if (!rc.isAuthenticated()) {
                   ActiveMQServerLogger.LOGGER.federationDownstreamUnauthenticated(rc.getRemoteAddress());
                   rc.close();
                   return;
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImpl.java
index 1b3822a61a..3370faca3c 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImpl.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImpl.java
@@ -241,6 +241,7 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC
 
          if (connection != null) {
             connection.setSubject(subject);
+            connection.setAuthenticated();
          }
          if (AuditLogger.isResourceLoggingEnabled()) {
             if (connection != null) {
@@ -380,6 +381,9 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC
 
       if (checkResult == ClusterCredentialsCheckResult.VALID) {
          AUTHENTICATION_SUCCESS_COUNT_UPDATER.incrementAndGet(this);
+         if (connection != null) {
+            connection.setAuthenticated();
+         }
          return user;
       } else if (checkResult == ClusterCredentialsCheckResult.INVALID) {
          AUTHENTICATION_FAILURE_COUNT_UPDATER.incrementAndGet(this);
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQMessageBundle.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQMessageBundle.java
index 9ab509ae6a..3a1027dc01 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQMessageBundle.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQMessageBundle.java
@@ -545,4 +545,6 @@ public interface ActiveMQMessageBundle {
    @Message(id = 229260, value = "Wildcard addresses are not supported on producers. Only on consumers. Please send to a real address. {}")
    ActiveMQException wildcardOnProducerNotSupported(String val);
 
+   @Message(id = 229261, value = "Authentication mechanism not supported: {}")
+   ActiveMQSecurityException authenticationMechanismNotSupported(String authMechanism);
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java
index 31966da0a5..33847c5cac 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java
@@ -1551,6 +1551,12 @@ public interface ActiveMQServerLogger {
    @LogMessage(id = 224164, value = "Failed to recover stored configuration for divert named '{}': {}. To repair this record create a new divert with the same name via the management API.", level = LogMessage.Level.WARN)
    void failedToRecoverStoredDivertConfiguration(String divertName, String divert);
 
+   @LogMessage(id = 224165, value = "Unauthenticated cluster topology subscription request. Update the remote broker to authenticate the cluster connection from {}.", level = LogMessage.Level.WARN)
+   void unauthenticatedClusterTopologySubscriptionRequest(String remoteAddress);
+
+   @LogMessage(id = 224166, value = "Multiple topology subscriptions to connection from {}. Closing connection.", level = LogMessage.Level.WARN)
+   void multipleTopologySubscriptions(String remoteAddress);
+
    @LogMessage(id = 224170, value = "Server is stopping. Unable to process redelivery during rollback; ref: {}; transaction: {}; exception message: {}", level = LogMessage.Level.WARN)
    void unableToProcessRedeliveryDuringRollback(String messageRef, String transaction, String exceptionMessage);
 
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/NodeLocator.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/NodeLocator.java
index 7e49a82e82..7e706b687b 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/NodeLocator.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/NodeLocator.java
@@ -20,7 +20,6 @@ import org.apache.activemq.artemis.api.core.ActiveMQException;
 import org.apache.activemq.artemis.api.core.Pair;
 import org.apache.activemq.artemis.api.core.TransportConfiguration;
 import org.apache.activemq.artemis.api.core.client.ClusterTopologyListener;
-import org.apache.activemq.artemis.core.client.impl.ServerLocatorInternal;
 
 /**
  * A class that will locate a particular server running in a cluster. How this server is chosen is a job for the
@@ -77,11 +76,4 @@ public abstract class NodeLocator implements ClusterTopologyListener {
          backupRegistrationListener.onBackupRegistrationFailed(alreadyReplicating);
       }
    }
-
-   /**
-    * connects to the cluster
-    */
-   public void connectToCluster(ServerLocatorInternal serverLocator) throws ActiveMQException {
-      serverLocator.connect();
-   }
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/cluster/BackupManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/cluster/BackupManager.java
index 20e0596d97..e61706da70 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/cluster/BackupManager.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/cluster/BackupManager.java
@@ -250,6 +250,7 @@ public class BackupManager implements ActiveMQComponent {
             backupServerLocator.setIdentity("backupLocatorFor='" + server + "'");
             backupServerLocator.setReconnectAttempts(-1);
             backupServerLocator.setInitialConnectAttempts(-1);
+            backupServerLocator.setConnectionCredentials(configuration.getClusterUser(), configuration.getClusterPassword());
             backupServerLocator.setCallTimeout(config.getCallTimeout());
             backupServerLocator.setProtocolManagerFactory(ActiveMQServerSideProtocolManagerFactory.getInstance(backupServerLocator, server.getStorageManager()));
          }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/cluster/ClusterController.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/cluster/ClusterController.java
index fcc0d77ae2..28c9725b80 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/cluster/ClusterController.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/cluster/ClusterController.java
@@ -24,6 +24,7 @@ import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.activemq.artemis.api.core.ActiveMQException;
+import org.apache.activemq.artemis.api.core.ActiveMQSecurityException;
 import org.apache.activemq.artemis.api.core.DiscoveryGroupConfiguration;
 import org.apache.activemq.artemis.api.core.Interceptor;
 import org.apache.activemq.artemis.api.core.Pair;
@@ -50,6 +51,7 @@ import org.apache.activemq.artemis.core.server.ActiveMQComponent;
 import org.apache.activemq.artemis.core.server.ActiveMQServer;
 import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
 import org.apache.activemq.artemis.core.server.cluster.quorum.QuorumManager;
+import org.apache.activemq.artemis.core.server.cluster.ClusterManager.IncomingInterceptorLookingForExceptionMessage;
 import org.apache.activemq.artemis.core.server.impl.Activation;
 import org.apache.activemq.artemis.spi.core.remoting.Acceptor;
 import org.slf4j.Logger;
@@ -78,6 +80,7 @@ public class ClusterController implements ActiveMQComponent {
    private final Executor executor;
 
    private CountDownLatch replicationClusterConnectedLatch;
+   private Exception replicationClusterConnectingException;
 
    private boolean started;
    private SimpleString replicatedClusterName;
@@ -232,6 +235,7 @@ public class ClusterController implements ActiveMQComponent {
       //if the cluster isn't available we want to hang around until it is
       serverLocator.setReconnectAttempts(config.getReconnectAttempts());
       serverLocator.setInitialConnectAttempts(config.getInitialConnectAttempts());
+      serverLocator.setConnectionCredentials(server.getConfiguration().getClusterUser(), server.getConfiguration().getClusterPassword());
       serverLocator.setCallTimeout(config.getCallTimeout());
       serverLocator.setCallFailoverTimeout(config.getCallFailoverTimeout());
       serverLocator.setRetryInterval(config.getRetryInterval());
@@ -240,6 +244,7 @@ public class ClusterController implements ActiveMQComponent {
       //this is used for replication so need to use the server packet decoder
       serverLocator.setProtocolManagerFactory(ActiveMQServerSideProtocolManagerFactory.getInstance(serverLocator, server.getStorageManager()));
       serverLocator.setThreadPools(server.getThreadPool(), server.getScheduledPool(), server.getThreadPool());
+      serverLocator.addIncomingInterceptor(new IncomingInterceptorLookingForExceptionMessage(server.getClusterManager(), executor));
       if (connector != null) {
          serverLocator.setClusterTransportConfiguration(connector);
       }
@@ -318,8 +323,12 @@ public class ClusterController implements ActiveMQComponent {
    /**
     * wait until we have connected to the cluster.
     */
-   public void awaitConnectionToReplicationCluster() throws InterruptedException {
+   public void awaitConnectionToReplicationCluster() throws Exception {
       replicationClusterConnectedLatch.await();
+
+      if (replicationClusterConnectingException != null) {
+         throw replicationClusterConnectingException;
+      }
    }
 
    /**
@@ -409,7 +418,7 @@ public class ClusterController implements ActiveMQComponent {
 
                boolean userIsValid = false;
                try {
-                  server.validateUser(msg.getClusterUser(), msg.getClusterPassword(), null, null);
+                  server.validateUser(msg.getClusterUser(), msg.getClusterPassword(), remotingConnection, null);
                   userIsValid = true;
                } catch (Exception e) {
                   // cluster user isn't valid
@@ -489,6 +498,13 @@ public class ClusterController implements ActiveMQComponent {
                   replicationClusterConnectedLatch.countDown();
                }
             }
+         } catch (ActiveMQSecurityException e) {
+            if (serverLocator == replicationLocator) {
+               replicationClusterConnectingException = e;
+               replicationClusterConnectedLatch.countDown();
+            }
+
+            logger.warn("failed to connect with " + serverLocator);
          } catch (ActiveMQException e) {
             if (!started) {
                return;
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/cluster/ClusterManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/cluster/ClusterManager.java
index 4661afac34..ecac84215b 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/cluster/ClusterManager.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/cluster/ClusterManager.java
@@ -452,6 +452,7 @@ public class ClusterManager implements ActiveMQComponent {
       // We are going to manually retry on the bridge in case of failure
       serverLocator.setReconnectAttempts(0);
       serverLocator.setInitialConnectAttempts(0);
+      serverLocator.setConnectionCredentials(config.getUser(), config.getPassword());
       serverLocator.setRetryInterval(config.getRetryInterval());
       serverLocator.setMaxRetryInterval(config.getMaxRetryInterval());
       serverLocator.setRetryIntervalMultiplier(config.getRetryIntervalMultiplier());
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/cluster/impl/BridgeImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/cluster/impl/BridgeImpl.java
index 6cce715848..73ecc2e271 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/cluster/impl/BridgeImpl.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/cluster/impl/BridgeImpl.java
@@ -915,7 +915,7 @@ public class BridgeImpl implements Bridge, SessionFailureListener, SendAcknowled
          csf = reconnectOnOriginalNode();
       } else {
          serverLocator.resetToInitialConnectors();
-         csf = (ClientSessionFactoryInternal) serverLocator.createSessionFactory();
+         csf = (ClientSessionFactoryInternal) serverLocator.createSessionFactory(configuration.getUser(), configuration.getPassword());
       }
 
       // null here means the targetNodeIS is not available yet
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/cluster/impl/ClusterConnectionImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/cluster/impl/ClusterConnectionImpl.java
index 3cbccd655d..9c49805fa9 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/cluster/impl/ClusterConnectionImpl.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/cluster/impl/ClusterConnectionImpl.java
@@ -725,11 +725,13 @@ public final class ClusterConnectionImpl implements ClusterConnection, AfterConn
                try (ServerLocatorInternal targetLocator = new ServerLocatorImpl(topology, true, transportConfiguration)) {
                   targetLocator.setReconnectAttempts(0);
                   targetLocator.setInitialConnectAttempts(0);
+                  targetLocator.setConnectionCredentials(clusterUser, clusterPassword);
                   targetLocator.setConnectionTTL(connectionTTL);
                   targetLocator.setCallTimeout(callTimeout);
                   targetLocator.setNodeID(nodeManager.getNodeId().toString());
                   targetLocator.setClusterTransportConfiguration(connector);
                   targetLocator.setIdentity("(Cluster-topology-scanner::" + server.toString() + ")");
+                  targetLocator.addIncomingInterceptor(new IncomingInterceptorLookingForExceptionMessage(manager, executor));
 
                   try {
                      try (ClientSessionFactoryInternal targetClientSessionFactory = targetLocator.connect()) {
@@ -880,6 +882,7 @@ public final class ClusterConnectionImpl implements ClusterConnection, AfterConn
          serverLocator.setClusterConnection(true);
          serverLocator.setClusterTransportConfiguration(connector);
          serverLocator.setInitialConnectAttempts(-1);
+         serverLocator.setConnectionCredentials(clusterUser, clusterPassword);
          serverLocator.setClientFailureCheckPeriod(clientFailureCheckPeriod);
          serverLocator.setConnectionTTL(connectionTTL);
          serverLocator.setConfirmationWindowSize(confirmationWindowSize);
@@ -1065,6 +1068,7 @@ public final class ClusterConnectionImpl implements ClusterConnection, AfterConn
       targetLocator.setReconnectAttempts(0);
 
       targetLocator.setInitialConnectAttempts(0);
+      targetLocator.setConnectionCredentials(clusterUser, clusterPassword);
       targetLocator.setClientFailureCheckPeriod(clientFailureCheckPeriod);
       targetLocator.setConnectionTTL(connectionTTL);
 
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederationConnection.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederationConnection.java
index 132e0bf460..791ecbb423 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederationConnection.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/federation/FederationConnection.java
@@ -171,7 +171,7 @@ public class FederationConnection {
       if (clientSessionFactory != null && !clientSessionFactory.isClosed()) {
          return clientSessionFactory;
       } else {
-         clientSessionFactory = serverLocator.createSessionFactory();
+         clientSessionFactory = serverLocator.createSessionFactory(config.getUsername(), config.getPassword());
          this.clientSessionFactory = clientSessionFactory;
          return clientSessionFactory;
       }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/BackupRecoveryJournalLoader.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/BackupRecoveryJournalLoader.java
index 4a39855aed..4e607591b3 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/BackupRecoveryJournalLoader.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/BackupRecoveryJournalLoader.java
@@ -95,7 +95,7 @@ public class BackupRecoveryJournalLoader extends PostOfficeJournalLoader {
       ScaleDownHandler scaleDownHandler = new ScaleDownHandler(pagingManager, postOffice, nodeManager, clusterController, parentServer != null ? parentServer.getStorageManager() : storageManager, parentServer != null ? parentServer.getHAPolicy().getScaleDownCommitInterval() : -1);
       locator.setProtocolManagerFactory(ActiveMQServerSideProtocolManagerFactory.getInstance(locator, storageManager));
 
-      try (ClientSessionFactory sessionFactory = locator.createSessionFactory()) {
+      try (ClientSessionFactory sessionFactory = locator.createSessionFactory(configuration.getClusterUser(), configuration.getClusterPassword())) {
          scaleDownHandler.scaleDown(sessionFactory, resourceManager, duplicateIDMap, parentServer != null ? parentServer.getConfiguration().getManagementAddress() : configuration.getManagementAddress(), parentServer != null ? parentServer.getNodeID() : null);
       }
    }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/PrimaryOnlyActivation.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/PrimaryOnlyActivation.java
index 098d9a1c11..0efedb9032 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/PrimaryOnlyActivation.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/PrimaryOnlyActivation.java
@@ -147,11 +147,12 @@ public class PrimaryOnlyActivation extends Activation {
       try {
          scaleDownServerLocator = ScaleDownPolicy.getScaleDownConnector(scaleDownPolicy, activeMQServer);
          //use a Node Locator to connect to the cluster
+         scaleDownServerLocator.setConnectionCredentials(activeMQServer.getConfiguration().getClusterUser(), activeMQServer.getConfiguration().getClusterPassword());
          scaleDownServerLocator.setProtocolManagerFactory(ActiveMQServerSideProtocolManagerFactory.getInstance(scaleDownServerLocator, activeMQServer.getStorageManager()));
          NodeLocator nodeLocator = scaleDownPolicy.getGroupName() == null ? new AnyNodeLocatorForScaleDown(activeMQServer) : new NamedNodeLocatorForScaleDown(scaleDownPolicy.getGroupName(), activeMQServer);
          scaleDownServerLocator.addClusterTopologyListener(nodeLocator);
 
-         nodeLocator.connectToCluster(scaleDownServerLocator);
+         scaleDownServerLocator.connect();
          // a timeout is necessary here in case we use a NamedNodeLocatorForScaleDown and there's no matching node in the cluster
          // should the timeout be configurable?
          nodeLocator.locateNode(ActiveMQClient.DEFAULT_DISCOVERY_INITIAL_WAIT_TIMEOUT);
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ReplicationBackupActivation.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ReplicationBackupActivation.java
index d46d1f18be..28bebbcf3c 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ReplicationBackupActivation.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ReplicationBackupActivation.java
@@ -212,7 +212,14 @@ public final class ReplicationBackupActivation extends Activation implements Dis
          logger.info("Apache ActiveMQ Artemis Backup Server version {} [{}] started, awaiting connection to a primary to start replication", activeMQServer.getVersion().getFullVersion(),
                       activeMQServer.toString());
 
-         clusterController.awaitConnectionToReplicationCluster();
+         try {
+            clusterController.awaitConnectionToReplicationCluster();
+         } catch (Exception e) {
+            logger.error("Stopping the broker because the connection to the replication cluster failed", e);
+            asyncRestartServer(activeMQServer, false);
+            return;
+         }
+
          activeMQServer.getBackupManager().start();
          final DistributedLock primaryLock = replicateAndFailover(clusterController);
          if (primaryLock == null) {
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/SharedNothingBackupActivation.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/SharedNothingBackupActivation.java
index 206bfc1d48..bc8b4b3eb2 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/SharedNothingBackupActivation.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/SharedNothingBackupActivation.java
@@ -150,7 +150,13 @@ public final class SharedNothingBackupActivation extends Activation implements R
          clusterController.addClusterTopologyListenerForReplication(nodeLocator);
 
          logger.trace("Waiting on cluster connection");
-         clusterController.awaitConnectionToReplicationCluster();
+         try {
+            clusterController.awaitConnectionToReplicationCluster();
+         } catch (Exception e) {
+            logger.error("Stopping the broker because the connection to the replication cluster failed", e);
+            asyncStopServer(activeMQServer, false);
+            return;
+         }
 
          logger.trace("Cluster Connected");
 
@@ -239,30 +245,7 @@ public final class SharedNothingBackupActivation extends Activation implements R
                break;
             } else if (signal == SharedNothingBackupQuorum.BACKUP_ACTIVATION.FAILURE_REPLICATING || signal == SharedNothingBackupQuorum.BACKUP_ACTIVATION.FAILURE_RETRY) {
                // something has gone badly run restart from scratch
-               logger.trace("Starting a new thread to stop the server!");
-
-               final SharedNothingBackupQuorum.BACKUP_ACTIVATION signalToStop = signal;
-
-               Thread startThread = new Thread(() -> {
-                  try {
-                     logger.trace("Calling activeMQServer.stop() as initialization failed");
-
-                     if (activeMQServer.getState() != ActiveMQServer.SERVER_STATE.STOPPED &&
-                         activeMQServer.getState() != ActiveMQServer.SERVER_STATE.STOPPING) {
-
-                        if (signalToStop == SharedNothingBackupQuorum.BACKUP_ACTIVATION.FAILURE_RETRY) {
-                           activeMQServer.stop(false);
-                           logger.trace("The server was shutdown for a network isolation, we keep retrying");
-                           activeMQServer.start();
-                        } else {
-                           activeMQServer.stop();
-                        }
-                     }
-                  } catch (Exception e) {
-                     ActiveMQServerLogger.LOGGER.errorRestartingBackupServer(activeMQServer, e);
-                  }
-               });
-               startThread.start();
+               asyncStopServer(activeMQServer, signal == SharedNothingBackupQuorum.BACKUP_ACTIVATION.FAILURE_RETRY);
                return;
             }
             //ok, this primary is no good, let's reset and try again
@@ -335,6 +318,35 @@ public final class SharedNothingBackupActivation extends Activation implements R
       }
    }
 
+   private void asyncStopServer(final ActiveMQServer server, boolean restart) {
+      logger.trace("Starting a new thread to stop the server!");
+
+      Thread stopThread = new Thread(() -> {
+         try {
+            logger.trace("Calling activeMQServer.stop() as initialization failed");
+
+            if (server.getState() != ActiveMQServer.SERVER_STATE.STOPPED &&
+               server.getState() != ActiveMQServer.SERVER_STATE.STOPPING) {
+
+               if (restart) {
+                  server.stop(false);
+                  logger.trace("The server was shutdown for a network isolation, we keep retrying");
+                  server.start();
+               } else {
+                  server.stop();
+               }
+            }
+         } catch (Exception e) {
+            if (restart) {
+               ActiveMQServerLogger.LOGGER.errorRestartingBackupServer(activeMQServer, e);
+            } else {
+               ActiveMQServerLogger.LOGGER.errorStoppingServer(e);
+            }
+         }
+      });
+      stopThread.start();
+   }
+
    private static ClusterControl tryConnectToNodeInReplicatedCluster(ClusterController clusterController, TransportConfiguration tc) {
       try {
          logger.trace("Calling clusterController.connectToNodeInReplicatedCluster({})", tc);
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/SharedNothingPrimaryActivation.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/SharedNothingPrimaryActivation.java
index 97475f40b7..2d300db21b 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/SharedNothingPrimaryActivation.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/SharedNothingPrimaryActivation.java
@@ -312,6 +312,7 @@ public class SharedNothingPrimaryActivation extends PrimaryActivation {
       try (ServerLocatorInternal locator = getLocator(config)) {
          locator.addClusterTopologyListener(listener);
          locator.setReconnectAttempts(0);
+         locator.setConnectionCredentials(activeMQServer.getConfiguration().getClusterUser(), activeMQServer.getConfiguration().getClusterPassword());
          try (ClientSessionFactoryInternal factory = locator.connectNoWarnings()) {
             // Just try connecting
             listener.latch.await(5, TimeUnit.SECONDS);
@@ -417,7 +418,7 @@ public class SharedNothingPrimaryActivation extends PrimaryActivation {
          boolean result = false;
 
          try (ServerLocator serverLocator = ActiveMQClient.createServerLocator(false, transportConfiguration);
-              ClientSessionFactory clientSessionFactory = serverLocator.createSessionFactory();
+              ClientSessionFactory clientSessionFactory = serverLocator.createSessionFactory(user, password);
               ClientSession clientSession = clientSessionFactory.createSession(user, password, false, false, false, false, 0)) {
             result = true;
          } catch (Exception e) {
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/routing/targets/ActiveMQTarget.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/routing/targets/ActiveMQTarget.java
index f9e69f53d5..1312801801 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/routing/targets/ActiveMQTarget.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/routing/targets/ActiveMQTarget.java
@@ -61,7 +61,7 @@ public class ActiveMQTarget extends AbstractTarget implements FailureListener {
 
    @Override
    public void connect() throws Exception {
-      sessionFactory = serverLocator.createSessionFactory();
+      sessionFactory = serverLocator.createSessionFactory(getUsername(), getPassword());
 
       remotingConnection = sessionFactory.getConnection();
       remotingConnection.addFailureListener(this);
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQPacketHandlerTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQPacketHandlerTest.java
new file mode 100644
index 0000000000..762898ebc0
--- /dev/null
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQPacketHandlerTest.java
@@ -0,0 +1,262 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.core.protocol.core.impl;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.activemq.artemis.api.core.ActiveMQSecurityException;
+import org.apache.activemq.artemis.api.core.SimpleString;
+import org.mockito.ArgumentCaptor;
+import org.apache.activemq.artemis.core.protocol.core.Channel;
+import org.apache.activemq.artemis.core.protocol.core.CoreRemotingConnection;
+import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ActiveMQExceptionMessage;
+import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ConnectMessage;
+import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ConnectResponseMessage;
+import org.apache.activemq.artemis.core.security.SecurityStore;
+import org.apache.activemq.artemis.core.server.ActiveMQServer;
+import org.apache.activemq.artemis.core.server.cluster.ha.HAPolicy;
+import org.apache.activemq.artemis.core.version.Version;
+import org.apache.activemq.artemis.utils.ExecutorFactory;
+import org.apache.activemq.artemis.utils.actors.ArtemisExecutor;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.stubbing.Answer;
+
+public class ActiveMQPacketHandlerTest {
+
+   private static final long LATCH_TIMEOUT_MS = 5000;
+
+   private ActiveMQServer server;
+   private Channel channel1;
+   private CoreRemotingConnection connection;
+   private CoreProtocolManager protocolManager;
+   private SecurityStore securityStore;
+
+   @BeforeEach
+   public void setUp() {
+      server = mock(ActiveMQServer.class);
+      channel1 = mock(Channel.class);
+      connection = mock(CoreRemotingConnection.class);
+      protocolManager = mock(CoreProtocolManager.class);
+      securityStore = mock(SecurityStore.class);
+
+      Version version = mock(Version.class);
+      when(version.getIncrementingVersion()).thenReturn(1);
+
+      ExecutorFactory executorFactory = () -> ArtemisExecutor.delegate(Executors.newSingleThreadExecutor());
+      when(server.getExecutorFactory()).thenReturn(executorFactory);
+      when(server.getSecurityStore()).thenReturn(securityStore);
+      when(server.getVersion()).thenReturn(version);
+      when(server.getNodeID()).thenReturn(SimpleString.of("test-node"));
+      when(server.getHAPolicy()).thenReturn(mock(HAPolicy.class));
+   }
+
+   /**
+    * When security is enabled, coreConnectionSecurity is enabled, and PLAIN credentials
+    * are supplied, validateUser must be called with the decoded username and password.
+    */
+   @Test
+   public void testHandleConnectPlainCredentialsCallsValidateUser() throws Exception {
+      when(securityStore.isSecurityEnabled()).thenReturn(true);
+      when(protocolManager.isCoreConnectionSecurityEnabled()).thenReturn(true);
+      when(server.validateUser(any(), any(), any(), any())).thenReturn("cluster-user");
+
+      CountDownLatch latch = countdownOnChannelSend();
+
+      ConnectMessage msg = ConnectMessage.withPlainCredentials("test-node", 1, "cluster-user", "cluster-pass");
+      newHandler().handlePacket(msg);
+
+      latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+      verify(server).validateUser(eq("cluster-user"), eq("cluster-pass"), eq(connection), isNull());
+      verify(channel1).send(any(ConnectResponseMessage.class));
+   }
+
+   /**
+    * When authMechanism is null (legacy client), authentication must be skipped
+    * regardless of security/coreConnectionSecurity settings.
+    */
+   @Test
+   public void testHandleConnectLegacyClientSkipsAuth() throws Exception {
+      when(securityStore.isSecurityEnabled()).thenReturn(true);
+      when(protocolManager.isCoreConnectionSecurityEnabled()).thenReturn(true);
+
+      CountDownLatch latch = countdownOnChannelSend();
+
+      ConnectMessage legacyMsg = new ConnectMessage("test-node", 1);
+      newHandler().handlePacket(legacyMsg);
+
+      latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+      verify(server, never()).validateUser(any(), any(), any(), any());
+      verify(channel1).send(any(ConnectResponseMessage.class));
+   }
+
+   /**
+    * When security is disabled, validateUser must not be called even when PLAIN
+    * credentials are present.
+    */
+   @Test
+   public void testHandleConnectSecurityDisabledSkipsAuth() throws Exception {
+      when(securityStore.isSecurityEnabled()).thenReturn(false);
+      when(protocolManager.isCoreConnectionSecurityEnabled()).thenReturn(true);
+
+      CountDownLatch latch = countdownOnChannelSend();
+
+      ConnectMessage msg = ConnectMessage.withPlainCredentials("test-node", 1, "u", "p");
+      newHandler().handlePacket(msg);
+
+      latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+      verify(server, never()).validateUser(any(), any(), any(), any());
+      verify(channel1).send(any(ConnectResponseMessage.class));
+   }
+
+   /**
+    * When coreConnectionSecurity is disabled, validateUser must not be called even
+    * when PLAIN credentials are present.
+    */
+   @Test
+   public void testHandleConnectCoreConnectionSecurityDisabledSkipsAuth() throws Exception {
+      when(securityStore.isSecurityEnabled()).thenReturn(true);
+      when(protocolManager.isCoreConnectionSecurityEnabled()).thenReturn(false);
+
+      CountDownLatch latch = countdownOnChannelSend();
+
+      ConnectMessage msg = ConnectMessage.withPlainCredentials("test-node", 1, "u", "p");
+      newHandler().handlePacket(msg);
+
+      latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+      verify(server, never()).validateUser(any(), any(), any(), any());
+      verify(channel1).send(any(ConnectResponseMessage.class));
+   }
+
+   /**
+    * When validateUser throws, an ActiveMQExceptionMessage containing an
+    * ActiveMQSecurityException must be sent back and the connection must be closed.
+    */
+   @Test
+   public void testHandleConnectBadCredentialsSendsExceptionAndClosesConnection() throws Exception {
+      when(securityStore.isSecurityEnabled()).thenReturn(true);
+      when(protocolManager.isCoreConnectionSecurityEnabled()).thenReturn(true);
+      when(server.validateUser(any(), any(), any(), any()))
+         .thenThrow(new ActiveMQSecurityException("bad credentials"));
+
+      CountDownLatch latch = countdownOnChannelSendAndFlush();
+
+      ConnectMessage msg = ConnectMessage.withPlainCredentials("test-node", 1, "wrong", "wrong");
+      newHandler().handlePacket(msg);
+
+      latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+      ArgumentCaptor<ActiveMQExceptionMessage> captor = ArgumentCaptor.forClass(ActiveMQExceptionMessage.class);
+      verify(channel1).sendAndFlush(captor.capture());
+      assertInstanceOf(ActiveMQSecurityException.class, captor.getValue().getException(),
+         "exception message must carry an ActiveMQSecurityException");
+      verify(connection).close();
+      verify(channel1, never()).send(any(ConnectResponseMessage.class));
+   }
+
+   /**
+    * When an unsupported auth mechanism is sent, an ActiveMQExceptionMessage containing
+    * an ActiveMQSecurityException must be sent back and the connection must be closed.
+    */
+   @Test
+   public void testHandleConnectUnsupportedMechanismSendsExceptionAndClosesConnection() throws Exception {
+      when(securityStore.isSecurityEnabled()).thenReturn(true);
+      when(protocolManager.isCoreConnectionSecurityEnabled()).thenReturn(true);
+
+      CountDownLatch latch = countdownOnChannelSendAndFlush();
+
+      ConnectMessage msg = new ConnectMessage("test-node", 1);
+      msg.setAuthMechanism("GSSAPI");
+
+      newHandler().handlePacket(msg);
+
+      latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+      ArgumentCaptor<ActiveMQExceptionMessage> captor = ArgumentCaptor.forClass(ActiveMQExceptionMessage.class);
+      verify(channel1).sendAndFlush(captor.capture());
+      assertInstanceOf(ActiveMQSecurityException.class, captor.getValue().getException(),
+         "exception message must carry an ActiveMQSecurityException");
+      verify(connection).close();
+      verify(channel1, never()).send(any(ConnectResponseMessage.class));
+   }
+
+   /**
+    * A second CONNECT packet on the same connection must be ignored and the
+    * connection must be closed — only one ConnectResponseMessage must be sent.
+    */
+   @Test
+   public void testHandleConnectDuplicateConnectClosesConnection() throws Exception {
+      when(securityStore.isSecurityEnabled()).thenReturn(false);
+
+      // Two packets: first latch counts down on the response, second triggers connection.close()
+      CountDownLatch responseLatch = countdownOnChannelSend();
+      CountDownLatch closeLatch = new CountDownLatch(1);
+      doAnswer((Answer<Void>) inv -> {
+         closeLatch.countDown();
+         return null;
+      }).when(connection).close();
+
+      ConnectMessage msg = new ConnectMessage("test-node", 1);
+      ActiveMQPacketHandler handler = newHandler();
+      handler.handlePacket(msg);
+      responseLatch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+      handler.handlePacket(msg);
+      closeLatch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+      verify(channel1).send(any(ConnectResponseMessage.class));
+      verify(connection).close();
+   }
+
+   private ActiveMQPacketHandler newHandler() {
+      return new ActiveMQPacketHandler(protocolManager, server, channel1, connection);
+   }
+
+   private CountDownLatch countdownOnChannelSend() {
+      CountDownLatch latch = new CountDownLatch(1);
+      doAnswer((Answer<Void>) inv -> {
+         latch.countDown();
+         return null;
+      }).when(channel1).send(any());
+      return latch;
+   }
+
+   private CountDownLatch countdownOnChannelSendAndFlush() {
+      CountDownLatch latch = new CountDownLatch(1);
+      doAnswer((Answer<Void>) inv -> {
+         latch.countDown();
+         return null;
+      }).when(channel1).sendAndFlush(any());
+      return latch;
+   }
+}
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImplTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImplTest.java
index 915ea76b03..d7a1b23ce0 100644
--- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImplTest.java
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImplTest.java
@@ -875,6 +875,43 @@ public class SecurityStoreImplTest {
       }
    }
 
+   @Test
+   public void testAuthenticateCallsSetAuthenticatedOnConnection() throws Exception {
+      SecurityStoreImpl securityStore = new SecurityStoreImpl(new HierarchicalObjectRepository<>(), permitAll, 999, true, "", null, null, 10, 10);
+      RemotingConnection connection = Mockito.mock(RemotingConnection.class);
+
+      securityStore.authenticate("user", "password", connection, null);
+
+      Mockito.verify(connection, Mockito.times(1)).setAuthenticated();
+   }
+
+   @Test
+   public void testClusterAuthenticateCallsSetAuthenticatedOnConnection() throws Exception {
+      final String clusterUser = "clusterUser";
+      final String clusterPassword = "clusterPassword";
+      SecurityStoreImpl securityStore = new SecurityStoreImpl(new HierarchicalObjectRepository<>(), permitAll, 999, true, clusterUser, clusterPassword, null, 10, 10);
+      RemotingConnection connection = Mockito.mock(RemotingConnection.class);
+
+      securityStore.authenticate(clusterUser, clusterPassword, connection, null);
+
+      Mockito.verify(connection, Mockito.times(1)).setAuthenticated();
+   }
+
+   @Test
+   public void testAuthenticateDoesNotCallSetAuthenticatedOnFailure() throws Exception {
+      SecurityStoreImpl securityStore = new SecurityStoreImpl(new HierarchicalObjectRepository<>(), denyAll, 999, true, "", null, null, 10, 10);
+      RemotingConnection connection = Mockito.mock(RemotingConnection.class);
+
+      try {
+         securityStore.authenticate("user", "password", connection, null);
+         fail("Authentication must fail");
+      } catch (Exception ignored) {
+         // expected
+      }
+
+      Mockito.verify(connection, Mockito.never()).setAuthenticated();
+   }
+
    private static SecurityAuth getSecurityAuth(String user, String password) {
       SecurityAuth session = Mockito.mock(SecurityAuth.class);
       Mockito.when(session.getUsername()).thenReturn(user);
diff --git a/artemis-service-extensions/src/main/java/org/apache/activemq/artemis/service/extensions/xa/recovery/ActiveMQXAResourceWrapper.java b/artemis-service-extensions/src/main/java/org/apache/activemq/artemis/service/extensions/xa/recovery/ActiveMQXAResourceWrapper.java
index b2b7b30c62..d10a472fed 100644
--- a/artemis-service-extensions/src/main/java/org/apache/activemq/artemis/service/extensions/xa/recovery/ActiveMQXAResourceWrapper.java
+++ b/artemis-service-extensions/src/main/java/org/apache/activemq/artemis/service/extensions/xa/recovery/ActiveMQXAResourceWrapper.java
@@ -289,7 +289,7 @@ public class ActiveMQXAResourceWrapper implements XAResource, SessionFailureList
                serverLocator.setLocatorConfig(xaRecoveryConfig.getLocatorConfig());
             }
             serverLocator.setProtocolManagerFactory(xaRecoveryConfig.getClientProtocolManager());
-            csf = serverLocator.createSessionFactory();
+            csf = serverLocator.createSessionFactory(xaRecoveryConfig.getUsername(), xaRecoveryConfig.getPassword());
             if (xaRecoveryConfig.getUsername() == null) {
                cs = csf.createSession(true, false, false);
             } else {
diff --git a/docs/user-manual/core.adoc b/docs/user-manual/core.adoc
index 2d4cef8eaf..dcc52915fd 100644
--- a/docs/user-manual/core.adoc
+++ b/docs/user-manual/core.adoc
@@ -61,6 +61,74 @@ In JMS terms think of them as JMS Connections.
 
 `ClientSessionFactory` instances are created using the `ServerLocator` class.
 
+[[core-connection-handshake]]
+=== Connection handshake
+
+When a Core client connects to a broker it sends:
+
+* the expected node id in case of failover
+* the client's protocol version
+* optional connection-level username and password
+
+Core application clients can pass credentials when creating a session factory to authenticate the underlying connection:
+
+[,java]
+----
+ServerLocator locator = ...;
+locator.setConnectionCredentials(user, password);
+ClientSessionFactory factory = locator.createSessionFactory(user, password);
+----
+
+The JMS connection factory sets connection credentials automatically when `setUser` / `setPassword` are called.
+
+Cluster-internal components on current brokers (cluster connections, bridges, replicated backups, federation links, etc.) pass their configured `cluster-user` / `cluster-password` or bridge credentials in the connect handshake automatically.
+
+When security is enabled and no connection credentials are supplied, the broker responds to topology subscription with a placeholder entry so the client can complete its handshake, but real broker topology is sent only after authentication succeeds.
+
+==== Server-side configuration
+
+Core connection security applies only when broker security is enabled (`security-enabled` in `broker.xml`).
+It is configured per acceptor on the Core protocol manager using URL parameters, in the same way as other protocol-specific acceptor settings (see xref:configuring-transports.adoc#configuring-the-transport[Configuring the Transport]).
+
+The following parameters are supported on any acceptor that handles the Core protocol:
+
+`coreConnectionSecurityEnabled`::
+Whether connection-level security is enabled for Core on this acceptor.
+When `true` (the default) and broker security is enabled, the broker:
+
+* validates optional credentials in the connect packet
+* sends a placeholder topology response to unauthenticated clients
+* installs a reduced Netty frame size limit until the connection is authenticated
+
+`coreInitialMaxFrameSize`::
+Maximum Core frame size in bytes allowed on a Netty connection *before* authentication completes.
+Default: `4096` (4 KiB).
+Frames larger than this limit are rejected with a decoder error and the connection is closed.
+
+`coreMaxFrameSize`::
+Maximum Core frame size in bytes allowed on a Netty connection *after* authentication completes.
+Default: `131072` (128 KiB).
+The limit is raised automatically once the connection `Subject` is set (for example after a successful `CONNECT` with credentials or after `CREATE_SESSION`).
+
+Example disabling Core connection security on an acceptor:
+
+[,xml]
+----
+<acceptor name="artemis">tcp://0.0.0.0:61616?protocols=CORE,AMQP;coreConnectionSecurityEnabled=false</acceptor>
+----
+
+Example keeping Core connection security enabled but increasing the post-authentication frame limit:
+
+[,xml]
+----
+<acceptor name="artemis">tcp://0.0.0.0:61616?protocols=CORE,AMQP;coreInitialMaxFrameSize=4096;coreMaxFrameSize=1048576</acceptor>
+----
+
+NOTE: These frame size limits apply to Netty-based Core connections.
+In-VM Core connections do not use the size-limited decoder.
+
+For embedded brokers, the same settings can be applied programmatically on `CoreProtocolManager` via `setCoreConnectionSecurityEnabled`, `setCoreInitialMaxFrameSize`, and `setCoreMaxFrameSize`.
+
 === ClientSession
 
 A client uses a ``ClientSession``for consuming and producing messages and for grouping them in transactions.
diff --git a/docs/user-manual/versions.adoc b/docs/user-manual/versions.adoc
index 518c121fbb..94a52598bd 100644
--- a/docs/user-manual/versions.adoc
+++ b/docs/user-manual/versions.adoc
@@ -30,6 +30,18 @@ See xref:persistence.adoc#configuring-the-message-journal[configuring the messag
 
 === Upgrading from 2.55.0
 
+* The Core protocol connect handshake packet was extended in a backward-compatible way on the wire (same packet type identifiers).
+When security is enabled, Core clients that authenticate at connection time should pass credentials via `ServerLocator.setConnectionCredentials(user, password)` or `ServerLocator.createSessionFactory(user, password)`.
+Cluster components on new brokers (cluster connections, bridges, backups, federation) do this automatically using the configured `cluster-user` / `cluster-password` or bridge `user` / `password`.
+See xref:core.adoc#core-connection-handshake[Core connection handshake] for details.
+* On secured Core connections the broker initially limits frame size to 4 KiB until the connection is authenticated, then raises the limit to 128 KiB.
+Unauthenticated clients no longer receive real cluster topology; they receive a placeholder response until authentication succeeds.
+Both behaviors can be tuned or disabled per acceptor; see xref:core.adoc#core-connection-handshake[Core connection handshake].
+* *Upgrade brokers before mixing old and new versions in a secured cluster.*
+An old broker connecting to a broker that has already been upgraded does not send connection-level credentials in the Core connect handshake.
+The upgraded broker therefore treats the connection as unauthenticated and responds with only the placeholder topology entry, not the real cluster view.
+Until that old broker is upgraded as well, it may be unable to discover or connect properly to the new brokers (cluster connections, bridges, replication, backup registration, and similar Core links can fail or stall).
+Upgrade every broker in the cluster, or temporarily disable `coreConnectionSecurityEnabled` on acceptors during a transitional window, before relying on cross-version cluster connectivity.
 * Due to https://issues.apache.org/jira/browse/ARTEMIS-6128[ARTEMIS-6128] the `Alder32` and `fileAlder32` properties have been deprecated in favor of `Adler32` and `fileAdler32` respectively.
 * Due to https://issues.apache.org/jira/browse/ARTEMIS-6172[ARTEMIS-6172] the HTTP-specific transport parameters deprecated by https://issues.apache.org/jira/browse/ARTEMIS-5819[ARTEMIS-5819] (`httpClientIdleTime`, `httpClientIdleScanPeriod`, `httpResponseTime`, `httpServerScanPeriod`) are restored.
 These are required so HTTP-tunneled Core connections can create response slots for server-initiated packets (for example cluster topology updates) without starving replies to client requests.
diff --git a/tests/compatibility-tests/src/main/resources/multiVersionReplica/mainServer.groovy b/tests/compatibility-tests/src/main/resources/multiVersionReplica/mainServer.groovy
index d2b361a2b3..f910ab4f26 100644
--- a/tests/compatibility-tests/src/main/resources/multiVersionReplica/mainServer.groovy
+++ b/tests/compatibility-tests/src/main/resources/multiVersionReplica/mainServer.groovy
@@ -38,11 +38,12 @@ String id = arg[1];
 String port = arg[2];
 String backupPort = arg[3]
 boolean security = Boolean.valueOf(arg[4]);
+boolean coreConnectionSecurity = Boolean.valueOf(arg[5]);
 
 configuration = new ConfigurationImpl();
 configuration.setJournalType(JournalType.NIO);
 configuration.setBrokerInstance(new File(folder + "/" + id));
-configuration.addAcceptorConfiguration("artemis", "tcp://localhost:" + port);
+configuration.addAcceptorConfiguration("artemis", "tcp://localhost:" + port + "?coreConnectionSecurityEnabled=" + coreConnectionSecurity);
 configuration.addConnectorConfiguration("local", "tcp://localhost:" + port);
 configuration.setSecurityEnabled(security);
 if (security) {
diff --git a/tests/compatibility-tests/src/test/java/org/apache/activemq/artemis/tests/compatibility/MultiVersionReplicaTest.java b/tests/compatibility-tests/src/test/java/org/apache/activemq/artemis/tests/compatibility/MultiVersionReplicaTest.java
index ead61556f0..b4925d7e14 100644
--- a/tests/compatibility-tests/src/test/java/org/apache/activemq/artemis/tests/compatibility/MultiVersionReplicaTest.java
+++ b/tests/compatibility-tests/src/test/java/org/apache/activemq/artemis/tests/compatibility/MultiVersionReplicaTest.java
@@ -110,7 +110,9 @@ public class MultiVersionReplicaTest extends ClasspathBase {
    @TestTemplate
    public void testReplica() throws Throwable {
       System.out.println("Starting live");
-      evaluate(mainClassloader, "multiVersionReplica/mainServer.groovy", serverFolder.getAbsolutePath(), "1", "61000", "61001", String.valueOf(security));
+      // To ensure backward compatibility, core connection security must be disabled on newer live brokers so legacy backup brokers can connect.
+      boolean coreConnectionSecurity = security && (!SNAPSHOT.equals(main) || SNAPSHOT.equals(backup));
+      evaluate(mainClassloader, "multiVersionReplica/mainServer.groovy", serverFolder.getAbsolutePath(), "1", "61000", "61001", String.valueOf(security), String.valueOf(coreConnectionSecurity));
       System.out.println("Starting backup");
       evaluate(backupClassLoader, "multiVersionReplica/backupServer.groovy", serverFolder.getAbsolutePath(), "2", "61001", "61000", String.valueOf(security));
 
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/client/CoreConnectHandshakeTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/client/CoreConnectHandshakeTest.java
new file mode 100644
index 0000000000..4b3e0aec55
--- /dev/null
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/client/CoreConnectHandshakeTest.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.tests.integration.client;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.activemq.artemis.api.core.ActiveMQSecurityException;
+import org.apache.activemq.artemis.api.core.QueueConfiguration;
+import org.apache.activemq.artemis.api.core.TransportConfiguration;
+import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
+import org.apache.activemq.artemis.api.core.client.ClientSession;
+import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
+import org.apache.activemq.artemis.api.core.client.ServerLocator;
+import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl;
+import org.apache.activemq.artemis.core.security.Role;
+import org.apache.activemq.artemis.core.server.ActiveMQServer;
+import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl;
+import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
+import org.apache.activemq.artemis.spi.core.security.jaas.InVMLoginModule;
+import org.apache.activemq.artemis.core.config.impl.SecurityConfiguration;
+import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
+import org.apache.activemq.artemis.tests.util.Wait;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class CoreConnectHandshakeTest extends ActiveMQTestBase {
+
+   private static final String CONNECT_USER = "connectUser";
+
+   private static final String CONNECT_PASS = "connectPass";
+
+   private ActiveMQServer server;
+
+   private TransportConfiguration connector;
+
+   @Override
+   @BeforeEach
+   public void setUp() throws Exception {
+      super.setUp();
+
+      connector = getNettyConnectorTransportConfiguration(true);
+
+      final ConfigurationImpl config = (ConfigurationImpl) createDefaultNettyConfig();
+      config.setSecurityEnabled(true);
+      config.setClusterUser("clusterUser");
+      config.setClusterPassword(CLUSTER_PASSWORD);
+      config.addConnectorConfiguration(connector.getName(), connector);
+      config.addClusterConfiguration(basicClusterConnectionConfig(connector.getName(), connector.getName()));
+
+      final SecurityConfiguration securityConfiguration = new SecurityConfiguration();
+      securityConfiguration.addUser(CONNECT_USER, CONNECT_PASS);
+      securityConfiguration.addRole(CONNECT_USER, "role");
+
+      final ActiveMQJAASSecurityManager securityManager =
+         new ActiveMQJAASSecurityManager(InVMLoginModule.class.getName(), securityConfiguration);
+
+      server = addServer(new ActiveMQServerImpl(config, securityManager));
+
+      final Role role = new Role("role", true, true, true, true, true, true, true, true, true, true, false, false);
+      final Set<Role> roles = new HashSet<>();
+      roles.add(role);
+      server.getSecurityRepository().addMatch("#", roles);
+
+      server.start();
+   }
+
+   @Test
+   public void testWrongConnectionCredentialsRejectedAtConnect() throws Exception {
+      try (ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(connector))) {
+         locator.setConnectionCredentials("wrong", "wrong");
+
+         assertThrows(ActiveMQSecurityException.class, () -> locator.createSessionFactory("wrong", "wrong"));
+      }
+   }
+
+   @Test
+   public void testPreAuthTopologyHiddenUntilSessionAuth() throws Exception {
+      try (ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(connector))) {
+         try (ClientSessionFactory sessionFactory = locator.createSessionFactory()) {
+            assertTrue(locator.getTopology().getMembers().isEmpty(),
+               "topology must not expose broker nodes before session authentication");
+
+            try (ClientSession session = sessionFactory.createSession(CONNECT_USER, CONNECT_PASS, false, true, true, false, 1)) {
+               Wait.assertTrue(() -> locator.getTopology().getMember(server.getNodeID().toString()) != null,
+                  5000, 100);
+            }
+         }
+      }
+   }
+
+   @Test
+   public void testConnectionCredentialsAuthenticateBeforeTopology() throws Exception {
+      try (ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(connector))) {
+         locator.setConnectionCredentials(CONNECT_USER, CONNECT_PASS);
+
+         try (ClientSessionFactory sessionFactory = locator.createSessionFactory(CONNECT_USER, CONNECT_PASS)) {
+            Wait.assertTrue(() -> locator.getTopology().getMember(server.getNodeID().toString()) != null,
+               5000, 100);
+
+            try (ClientSession session = sessionFactory.createSession(CONNECT_USER, CONNECT_PASS, false, true, true, false, 1)) {
+               session.createQueue(QueueConfiguration.of("queue-" + getName()));
+            }
+         }
+      }
+   }
+}
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/failover/BackupAuthenticationTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/failover/BackupAuthenticationTest.java
index 31ed9f1310..037ee7237b 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/failover/BackupAuthenticationTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/failover/BackupAuthenticationTest.java
@@ -19,16 +19,12 @@ package org.apache.activemq.artemis.tests.integration.cluster.failover;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
-import java.util.Arrays;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-import org.apache.activemq.artemis.api.core.ActiveMQException;
-import org.apache.activemq.artemis.api.core.Interceptor;
 import org.apache.activemq.artemis.api.core.TransportConfiguration;
-import org.apache.activemq.artemis.core.protocol.core.Packet;
-import org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl;
-import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
+import org.apache.activemq.artemis.core.server.ActiveMQServer;
+import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerBasePlugin;
 import org.apache.activemq.artemis.tests.util.TransportConfigurationUtils;
 import org.apache.activemq.artemis.tests.util.Wait;
 import org.junit.jupiter.api.BeforeEach;
@@ -53,11 +49,9 @@ public class BackupAuthenticationTest extends FailoverTestBase {
       waitForServerToStart(primaryServer.getServer());
       backupServer.start();
       backupServer.getServer().addExternalComponent(fakeServiceComponent, true);
+      // wait for the backup to register its broker plugins (server started) before
+      // asserting it stops due to the wrong cluster password
       assertTrue(latch.await(5, TimeUnit.SECONDS));
-      /*
-       * can't intercept the message at the backup, so we intercept the registration message at the
-       * live.
-       */
       Wait.waitFor(() -> !backupServer.isStarted());
       assertFalse(backupServer.isStarted(), "backup should have stopped");
       Wait.assertFalse(fakeServiceComponent::isStarted);
@@ -69,9 +63,14 @@ public class BackupAuthenticationTest extends FailoverTestBase {
    protected void createConfigs() throws Exception {
       createReplicatedConfigs();
       backupConfig.setClusterPassword("crocodile");
-      primaryConfig.setIncomingInterceptorClassNames(Arrays.asList(NotifyingInterceptor.class.getName()));
       backupConfig.setSecurityEnabled(true);
       primaryConfig.setSecurityEnabled(true);
+      backupConfig.registerBrokerPlugin(new ActiveMQServerBasePlugin() {
+         @Override
+         public void registered(ActiveMQServer server) {
+            latch.countDown();
+         }
+      });
    }
 
    @Override
@@ -83,17 +82,4 @@ public class BackupAuthenticationTest extends FailoverTestBase {
    protected TransportConfiguration getConnectorTransportConfiguration(boolean live) {
       return TransportConfigurationUtils.getInVMConnector(live);
    }
-
-   public static final class NotifyingInterceptor implements Interceptor {
-
-      @Override
-      public boolean intercept(Packet packet, RemotingConnection connection) throws ActiveMQException {
-         if (packet.getType() == PacketImpl.BACKUP_REGISTRATION) {
-            latch.countDown();
-         } else if (packet.getType() == PacketImpl.CLUSTER_CONNECT) {
-            latch.countDown();
-         }
-         return true;
-      }
-   }
 }
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/failover/SecurityFailoverTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/failover/SecurityFailoverTest.java
index 867e2dc48f..131fcffc1e 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/failover/SecurityFailoverTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/failover/SecurityFailoverTest.java
@@ -28,6 +28,7 @@ import org.apache.activemq.artemis.core.security.Role;
 import org.apache.activemq.artemis.core.server.impl.InVMNodeManager;
 import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
 import org.apache.activemq.artemis.tests.integration.cluster.util.TestableServer;
+import org.junit.jupiter.api.BeforeEach;
 
 public class SecurityFailoverTest extends FailoverTest {
 
@@ -94,6 +95,14 @@ public class SecurityFailoverTest extends FailoverTest {
       installSecurity(primaryServer1);
    }
 
+   @Override
+   @BeforeEach
+   public void setUp() throws Exception {
+      super.setUp();
+
+      locator.setConnectionCredentials("a", "b");
+   }
+
    protected ActiveMQJAASSecurityManager installSecurity(TestableServer server) {
       ActiveMQJAASSecurityManager securityManager = (ActiveMQJAASSecurityManager) server.getServer().getSecurityManager();
       securityManager.getConfiguration().addUser("a", "b");
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/failover/lockmanager/LockManagerBackupAuthenticationTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/failover/lockmanager/LockManagerBackupAuthenticationTest.java
index b9e32dfc02..bc030f7df9 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/failover/lockmanager/LockManagerBackupAuthenticationTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/failover/lockmanager/LockManagerBackupAuthenticationTest.java
@@ -20,17 +20,13 @@ import static java.util.Arrays.asList;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
-import java.util.Arrays;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-import org.apache.activemq.artemis.api.core.ActiveMQException;
-import org.apache.activemq.artemis.api.core.Interceptor;
 import org.apache.activemq.artemis.api.core.TransportConfiguration;
 import org.apache.activemq.artemis.core.config.ha.ReplicationBackupPolicyConfiguration;
-import org.apache.activemq.artemis.core.protocol.core.Packet;
-import org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl;
-import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
+import org.apache.activemq.artemis.core.server.ActiveMQServer;
+import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerBasePlugin;
 import org.apache.activemq.artemis.tests.extensions.parameterized.Parameter;
 import org.apache.activemq.artemis.tests.extensions.parameterized.ParameterizedTestExtension;
 import org.apache.activemq.artemis.tests.extensions.parameterized.Parameters;
@@ -70,11 +66,9 @@ public class LockManagerBackupAuthenticationTest extends FailoverTestBase {
       waitForServerToStart(primaryServer.getServer());
       backupServer.start();
       backupServer.getServer().addExternalComponent(fakeServiceComponent, true);
-      assertTrue(registrationStarted .await(5, TimeUnit.SECONDS));
-      /*
-       * can't intercept the message at the backup, so we intercept the registration message at the
-       * live.
-       */
+      // wait for the backup to register its broker plugins (server started) before
+      // asserting it stops due to the wrong cluster password
+      assertTrue(registrationStarted.await(5, TimeUnit.SECONDS));
       Wait.waitFor(() -> !backupServer.isStarted());
       assertFalse(backupServer.isStarted(), "backup should have stopped");
       Wait.assertFalse(fakeServiceComponent::isStarted);
@@ -86,9 +80,14 @@ public class LockManagerBackupAuthenticationTest extends FailoverTestBase {
    protected void createConfigs() throws Exception {
       createPluggableReplicatedConfigs();
       backupConfig.setClusterPassword("crocodile");
-      primaryConfig.setIncomingInterceptorClassNames(Arrays.asList(NotifyingInterceptor.class.getName()));
       backupConfig.setSecurityEnabled(true);
       primaryConfig.setSecurityEnabled(true);
+      backupConfig.registerBrokerPlugin(new ActiveMQServerBasePlugin() {
+         @Override
+         public void registered(ActiveMQServer server) {
+            registrationStarted.countDown();
+         }
+      });
    }
 
    @Override
@@ -107,17 +106,4 @@ public class LockManagerBackupAuthenticationTest extends FailoverTestBase {
       return useNetty ? getNettyConnectorTransportConfiguration(live) :
          TransportConfigurationUtils.getInVMConnector(live);
    }
-
-   public static final class NotifyingInterceptor implements Interceptor {
-
-      @Override
-      public boolean intercept(Packet packet, RemotingConnection connection) throws ActiveMQException {
-         if (packet.getType() == PacketImpl.BACKUP_REGISTRATION) {
-            registrationStarted.countDown();
-         } else if (packet.getType() == PacketImpl.CLUSTER_CONNECT) {
-            registrationStarted.countDown();
-         }
-         return true;
-      }
-   }
 }
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/connection/ExceptionListenerForConnectionTimedOutExceptionTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/connection/ExceptionListenerForConnectionTimedOutExceptionTest.java
index 5a8f49d8c9..7bd07a630c 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/connection/ExceptionListenerForConnectionTimedOutExceptionTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/connection/ExceptionListenerForConnectionTimedOutExceptionTest.java
@@ -222,7 +222,7 @@ public class ExceptionListenerForConnectionTimedOutExceptionTest extends JMSTest
       @Override
       public boolean intercept(Packet packet, RemotingConnection connection) throws ActiveMQException {
          // CheckForFailoverReply is ignored here, as this is simulating an issue where the server is completely not responding, the blocked call should throw an exception asynchrnously to the retry
-         if (packet.getType() == PacketImpl.CHECK_FOR_FAILOVER_REPLY) {
+         if (packet.getType() == PacketImpl.CONNECT_RESPONSE) {
             block();
             return true;
          }
@@ -239,7 +239,7 @@ public class ExceptionListenerForConnectionTimedOutExceptionTest extends JMSTest
       @Override
       public boolean intercept(Packet packet, RemotingConnection connection) throws ActiveMQException {
          // CheckForFailoverReply is ignored here, as this is simulating an issue where the server is completely not responding, the blocked call should throw an exception asynchrnously to the retry
-         if (packet.getType() == PacketImpl.CHECK_FOR_FAILOVER_REPLY) {
+         if (packet.getType() == PacketImpl.CONNECT_RESPONSE) {
             block();
             return true;
          }
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/remoting/compat/HQClientProtocolManagerTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/remoting/compat/HQClientProtocolManagerTest.java
index ebbe14c25e..cc948bf03e 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/remoting/compat/HQClientProtocolManagerTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/remoting/compat/HQClientProtocolManagerTest.java
@@ -88,7 +88,7 @@ public class HQClientProtocolManagerTest extends ActiveMQTestBase {
       private static Set<Byte> receivedTypes = new HashSet<>();
       private static Set<Byte> unsupportedTypes = new HashSet<>();
       static {
-         unsupportedTypes.add(PacketImpl.CHECK_FOR_FAILOVER);
+         unsupportedTypes.add(PacketImpl.CONNECT);
       }
 
       @Override
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/transports/netty/CoreFrameWithSizeLimitDecoderTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/transports/netty/CoreFrameWithSizeLimitDecoderTest.java
new file mode 100644
index 0000000000..bcde488146
--- /dev/null
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/transports/netty/CoreFrameWithSizeLimitDecoderTest.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.tests.integration.transports.netty;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.embedded.EmbeddedChannel;
+import io.netty.handler.codec.TooLongFrameException;
+import org.apache.activemq.artemis.core.protocol.core.impl.CoreProtocolManager;
+import org.apache.activemq.artemis.core.remoting.impl.netty.CoreFrameWithSizeLimitDecoder;
+import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
+import org.apache.activemq.artemis.utils.DataConstants;
+import org.junit.jupiter.api.Test;
+
+public class CoreFrameWithSizeLimitDecoderTest extends ActiveMQTestBase {
+
+   private static final int FRAME_PAYLOAD_SIZE = CoreProtocolManager.CORE_INITIAL_MAX_FRAME_SIZE + 1;
+
+   @Test
+   public void testRejectsOversizedFrameBeforeAuthentication() {
+      final EmbeddedChannel decoder = new EmbeddedChannel(new CoreFrameWithSizeLimitDecoder(CoreProtocolManager.CORE_INITIAL_MAX_FRAME_SIZE));
+
+      assertThrows(TooLongFrameException.class, () -> decoder.writeInbound(frameBuffer(FRAME_PAYLOAD_SIZE)));
+   }
+
+   @Test
+   public void testRejectsOversizedFrameFromLengthPrefixOnly() {
+      final EmbeddedChannel decoder = new EmbeddedChannel(new CoreFrameWithSizeLimitDecoder(CoreProtocolManager.CORE_INITIAL_MAX_FRAME_SIZE));
+
+      final ByteBuf lengthPrefixOnly = Unpooled.buffer(DataConstants.SIZE_INT);
+      lengthPrefixOnly.writeInt(FRAME_PAYLOAD_SIZE);
+
+      assertThrows(TooLongFrameException.class, () -> decoder.writeInbound(lengthPrefixOnly));
+      assertNull(decoder.readInbound());
+   }
+
+   @Test
+   public void testAcceptsLargerFrameAfterLimitRaised() throws Exception {
+      final EmbeddedChannel decoder = new EmbeddedChannel(new CoreFrameWithSizeLimitDecoder(CoreProtocolManager.CORE_INITIAL_MAX_FRAME_SIZE));
+
+      CoreFrameWithSizeLimitDecoder.setFrameSizeLimit(decoder, CoreProtocolManager.CORE_MAX_FRAME_SIZE);
+
+      decoder.writeInbound(frameBuffer(FRAME_PAYLOAD_SIZE));
+
+      final ByteBuf frame = decoder.readInbound();
+      assertNotNull(frame);
+      assertEquals(FRAME_PAYLOAD_SIZE, frame.readableBytes());
+      frame.release();
+      assertNull(decoder.readInbound());
+   }
+
+   private static ByteBuf frameBuffer(final int payloadSize) {
+      final ByteBuf buffer = Unpooled.buffer(4 + payloadSize);
+      buffer.writeInt(payloadSize);
+      buffer.writeZero(payloadSize);
+      return buffer;
+   }
+}
diff --git a/tests/joram-tests/src/test/java/org/apache/activemq/artemis/common/AbstractAdmin.java b/tests/joram-tests/src/test/java/org/apache/activemq/artemis/common/AbstractAdmin.java
index 3a842222db..a7a277e161 100644
--- a/tests/joram-tests/src/test/java/org/apache/activemq/artemis/common/AbstractAdmin.java
+++ b/tests/joram-tests/src/test/java/org/apache/activemq/artemis/common/AbstractAdmin.java
@@ -74,6 +74,7 @@ public class AbstractAdmin implements Admin {
    @Override
    public void start() throws Exception {
       serverLocator = ActiveMQClient.createServerLocatorWithoutHA(new TransportConfiguration(NettyConnectorFactory.class.getName()));
+      serverLocator.setConnectionCredentials(ActiveMQDefaultConfiguration.getDefaultClusterUser(), ActiveMQDefaultConfiguration.getDefaultClusterPassword());
       sf = serverLocator.createSessionFactory();
       clientSession = sf.createSession(ActiveMQDefaultConfiguration.getDefaultClusterUser(), ActiveMQDefaultConfiguration.getDefaultClusterPassword(), false, true, true, false, 1);
       requestor = new ClientRequestor(clientSession, ActiveMQDefaultConfiguration.getDefaultManagementAddress());
diff --git a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/ra/ResourceAdapterTest.java b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/ra/ResourceAdapterTest.java
index 6ff073c3a8..dbd384dfe3 100644
--- a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/ra/ResourceAdapterTest.java
+++ b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/ra/ResourceAdapterTest.java
@@ -42,6 +42,7 @@ import org.apache.activemq.artemis.ra.ConnectionFactoryProperties;
 import org.apache.activemq.artemis.ra.inflow.ActiveMQActivation;
 import org.apache.activemq.artemis.ra.inflow.ActiveMQActivationSpec;
 import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
+import org.apache.activemq.artemis.utils.Wait;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -646,7 +647,7 @@ public class ResourceAdapterTest extends ActiveMQTestBase {
             // ignore
          }
 
-         assertEquals(0, server.getRemotingService().getConnections().size());
+         Wait.assertEquals(0, () -> server.getRemotingService().getConnections().size());
       } finally {
          if (activation != null) {
             activation.stop();
@@ -657,6 +658,48 @@ public class ResourceAdapterTest extends ActiveMQTestBase {
          server.stop();
       }
    }
+   @Test
+   public void testConnectionFactoryPropagatesUserCredentials() throws Exception {
+      ActiveMQResourceAdapter ra = new ActiveMQResourceAdapter();
+      ra.setConnectorClassName(InVMConnectorFactory.class.getName());
+      ra.setUserName("testUser");
+      ra.setPassword("testPassword");
+      ra.setPasswordCodec("org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec");
+
+      ActiveMQConnectionFactory factory = ra.getConnectionFactory(new ConnectionFactoryProperties());
+      assertEquals("testUser", factory.getUser());
+      assertEquals("testPassword", factory.getPassword());
+      assertEquals("org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec", factory.getPasswordCodec());
+   }
+
+   @Test
+   public void testRecoveryConnectionFactoryPropagatesUserCredentials() throws Exception {
+      ActiveMQResourceAdapter ra = new ActiveMQResourceAdapter();
+      ra.setConnectorClassName(InVMConnectorFactory.class.getName());
+      ra.setUserName("recoveryUser");
+      ra.setPassword("recoveryPassword");
+      ra.setPasswordCodec("org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec");
+
+      ActiveMQConnectionFactory factory = ra.createRecoveryActiveMQConnectionFactory(new ConnectionFactoryProperties());
+      assertEquals("recoveryUser", factory.getUser());
+      assertEquals("recoveryPassword", factory.getPassword());
+      assertEquals("org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec", factory.getPasswordCodec());
+   }
+
+   @Test
+   public void testDefaultConnectionFactoryPropagatesUserCredentials() throws Exception {
+      ActiveMQResourceAdapter ra = new ActiveMQResourceAdapter();
+      ra.setConnectorClassName(InVMConnectorFactory.class.getName());
+      ra.setUserName("defaultUser");
+      ra.setPassword("defaultPassword");
+      ra.setPasswordCodec("org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec");
+
+      ActiveMQConnectionFactory factory = ra.getDefaultActiveMQConnectionFactory();
+      assertEquals("defaultUser", factory.getUser());
+      assertEquals("defaultPassword", factory.getPassword());
+      assertEquals("org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec", factory.getPasswordCodec());
+   }
+
    @Test
    public void testCloseConnectionFactoryMultipleTimesDoesNotThrow() throws Exception {
       ActiveMQResourceAdapter ra = new ActiveMQResourceAdapter();


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


(artemis) 08/11: Limiting Wildcards (%) on selectors

Posted by cl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 26d189a08607624797aeaea6379c1a8b9aebc2a1
Author: Clebert Suconic <cl...@gmail.com>
AuthorDate: Mon Aug 17 13:50:49 2026 -0400

    Limiting Wildcards (%) on selectors
    
    (cherry picked from commit ee39382a6626bb9b90caf041dcb8f17a61700ca4)
    
    ARTEMIS-6248
---
 .../artemis/utils/SystemPropertyHelper.java        |  31 +++++
 .../selector/filter/ComparisonExpression.java      |  37 ++++++
 .../activemq/artemis/selector/SelectorTest.java    |  69 ++++++++++-
 .../artemis/core/server/ActiveMQServerLogger.java  |   3 +
 .../core/server/impl/PostOfficeJournalLoader.java  |  11 +-
 docs/user-manual/versions.adoc                     |   3 +
 .../resources/selectorWildcard/sendMessages.groovy |  77 +++++++++++++
 .../filter/ComparisonExpressionTestAccessor.java   |  24 ++++
 .../SelectorWildcardCompatibilityTest.java         | 126 +++++++++++++++++++++
 9 files changed, 379 insertions(+), 2 deletions(-)

diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/SystemPropertyHelper.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/SystemPropertyHelper.java
new file mode 100644
index 0000000000..260ca9eaf1
--- /dev/null
+++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/SystemPropertyHelper.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.utils;
+
+public class SystemPropertyHelper {
+
+   public static String getProperty(String propertyName, String envName, String defaultValue) {
+      String value = System.getProperty(propertyName);
+      if (value == null) {
+         value = System.getenv(envName);
+      }
+      if (value == null) {
+         value = defaultValue;
+      }
+      return value;
+   }
+}
diff --git a/artemis-selector/src/main/java/org/apache/activemq/artemis/selector/filter/ComparisonExpression.java b/artemis-selector/src/main/java/org/apache/activemq/artemis/selector/filter/ComparisonExpression.java
index f5e7864408..101445e063 100755
--- a/artemis-selector/src/main/java/org/apache/activemq/artemis/selector/filter/ComparisonExpression.java
+++ b/artemis-selector/src/main/java/org/apache/activemq/artemis/selector/filter/ComparisonExpression.java
@@ -21,6 +21,8 @@ import java.util.List;
 import java.util.Set;
 import java.util.regex.Pattern;
 
+import org.apache.activemq.artemis.utils.SystemPropertyHelper;
+
 /**
  * A filter performing a comparison of two objects
  */
@@ -28,6 +30,12 @@ public abstract class ComparisonExpression extends BinaryExpression implements B
 
    public static final ThreadLocal<Boolean> CONVERT_STRING_EXPRESSIONS = new ThreadLocal<>();
 
+   public static int MAX_WILDCARDS = Integer.parseInt(SystemPropertyHelper.getProperty("org.apache.activemq.artemis.selector.maxWildcards", "ARTEMIS_SELECTOR_MAX_WILDCARDS", "5"));
+
+   static void setMaxWildcards(int maxWildcards) {
+      MAX_WILDCARDS = maxWildcards;
+   }
+
    boolean convertStringExpressions = false;
    private static final Set<Character> REGEXP_CONTROL_CHARS = new HashSet<>();
 
@@ -159,6 +167,35 @@ public abstract class ComparisonExpression extends BinaryExpression implements B
          c = 0xFFFF & escape.charAt(0);
       }
 
+      // Merge consecutive unescaped '%' wildcards into a single '%'
+      StringBuilder merged = new StringBuilder();
+      boolean lastWasWildcard = false;
+      int wildcardCount = 0;
+      for (int i = 0; i < right.length(); i++) {
+         char ch = right.charAt(i);
+         if (c == (0xFFFF & ch) && i + 1 < right.length()) {
+            merged.append(ch);
+            i++;
+            merged.append(right.charAt(i));
+            lastWasWildcard = false;
+         } else if (ch == '%') {
+            if (!lastWasWildcard) {
+               wildcardCount++;
+               merged.append(ch);
+            }
+            lastWasWildcard = true;
+         } else {
+            merged.append(ch);
+            lastWasWildcard = false;
+         }
+      }
+
+      right = String.valueOf(merged);
+
+      if (wildcardCount > MAX_WILDCARDS) {
+         throw new RuntimeException("Invalid LIKE pattern: too many '%' wildcards (max " + MAX_WILDCARDS + ", found " + wildcardCount + ").");
+      }
+
       return new LikeExpression(left, right, c);
    }
 
diff --git a/artemis-selector/src/test/java/org/apache/activemq/artemis/selector/SelectorTest.java b/artemis-selector/src/test/java/org/apache/activemq/artemis/selector/SelectorTest.java
index eaccfd02fa..ad579f83b3 100755
--- a/artemis-selector/src/test/java/org/apache/activemq/artemis/selector/SelectorTest.java
+++ b/artemis-selector/src/test/java/org/apache/activemq/artemis/selector/SelectorTest.java
@@ -21,6 +21,9 @@ import org.apache.activemq.artemis.selector.filter.FilterException;
 import org.apache.activemq.artemis.selector.impl.SelectorParser;
 import org.junit.jupiter.api.Test;
 
+import java.time.Duration;
+
+import static org.junit.jupiter.api.Assertions.assertTimeout;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.fail;
@@ -361,6 +364,70 @@ public class SelectorTest {
       assertSelector(message, "endingUnderScore LIKE '%D7xlJIQn\\_' ESCAPE '\\'", true);
    }
 
+   @Test
+   public void testLikeWithConsecutiveWildcardsAreMerged() throws Exception {
+      MockMessage message = createMessage();
+
+      assertTimeout(Duration.ofSeconds(5), () -> {
+         message.setStringProperty("prop", "helloZ");
+         assertSelector(message, "prop LIKE '%%%%%%%%%%%%Z'", true);
+
+         message.setStringProperty("prop", "anything");
+         assertSelector(message, "prop LIKE '%%'", true);
+
+         message.setStringProperty("prop", "aXb");
+         assertSelector(message, "prop LIKE 'a%%b'", true);
+         assertSelector(message, "prop LIKE 'a%%c'", false);
+      });
+   }
+
+   @Test
+   public void testLikeWithSingleWildcardsStillWorks() throws Exception {
+      MockMessage message = createMessage();
+
+      message.setStringProperty("prop", "helloZ");
+      assertSelector(message, "prop LIKE '%Z'", true);
+
+      message.setStringProperty("prop", "Z");
+      assertSelector(message, "prop LIKE '%Z'", true);
+
+      message.setStringProperty("prop", "abcZdef");
+      assertSelector(message, "prop LIKE '%Z%'", true);
+
+      message.setStringProperty("prop", "nothing");
+      assertSelector(message, "prop LIKE '%Z%'", false);
+   }
+
+   @Test
+   public void testLikeWithTooManyWildcardsIsRejected() throws Exception {
+      MockMessage message = createMessage();
+      assertInvalidSelector(message, "prop LIKE '%_%_%_%_%_%_%_%_%_%Z'");
+      assertInvalidSelector(message, "prop LIKE '%a%b%c%d%e%f'");
+   }
+
+   @Test
+   public void testLikeWithMaxAllowedWildcards() throws Exception {
+      MockMessage message = createMessage();
+
+      message.setStringProperty("prop", "aXbYcZd");
+      assertSelector(message, "prop LIKE '%X%Y%Z%'", true);
+      assertSelector(message, "prop LIKE '%X%Q%Z%'", false);
+   }
+
+   @Test
+   public void testLikeEscapedWildcardsDoNotCountTowardLimit() throws Exception {
+      MockMessage message = createMessage();
+
+      // 6 '%' characters but all escaped to literals — zero actual wildcards
+      message.setStringProperty("prop", "%%%%%%%%");
+      assertSelector(message, "prop LIKE '!%!%!%!%!%!%!%%' ESCAPE '!'", true);
+
+      // mix: 4 real wildcards + 3 escaped '%' literals — should be allowed
+      message.setStringProperty("prop", "a%b%c%end");
+      assertSelector(message, "prop LIKE '%a!%b!%c!%end%'  ESCAPE '!'", true);
+      assertSelector(message, "prop LIKE '%a!%b!%c!%XXX%' ESCAPE '!'", false);
+   }
+
    @Test
    public void testInvalidSelector() throws Exception {
       MockMessage message = createMessage();
@@ -401,7 +468,7 @@ public class SelectorTest {
       try {
          SelectorParser.parse(text);
          fail("Created a valid selector");
-      } catch (FilterException e) {
+      } catch (FilterException | RuntimeException e) {
       }
    }
 
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java
index 0b711bc118..6b7e5406dc 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java
@@ -1551,6 +1551,9 @@ public interface ActiveMQServerLogger {
    @LogMessage(id = 224166, value = "Multiple topology subscriptions to connection from {}. Closing connection.", level = LogMessage.Level.WARN)
    void multipleTopologySubscriptions(String remoteAddress);
 
+   @LogMessage(id = 224169, value = "Queue {} has an invalid filter expression persisted in the journal: {}. The server cannot load this journal unless this filter is allowed. To allow it, increase the wildcard limit using the system property 'org.apache.activemq.artemis.selector.maxWildcards' or environment variable 'ARTEMIS_SELECTOR_MAX_WILDCARDS'.", level = LogMessage.Level.WARN)
+   void invalidFilterExpressionOnJournalReload(String queueName, String filterString);
+
    @LogMessage(id = 224170, value = "Server is stopping. Unable to process redelivery during rollback; ref: {}; transaction: {}; exception message: {}", level = LogMessage.Level.WARN)
    void unableToProcessRedeliveryDuringRollback(String messageRef, String transaction, String exceptionMessage);
 
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 cc7811a9ea..9a402f5552 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
@@ -26,6 +26,7 @@ import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 
+import org.apache.activemq.artemis.api.core.ActiveMQInvalidFilterExpressionException;
 import org.apache.activemq.artemis.api.core.Message;
 import org.apache.activemq.artemis.api.core.Pair;
 import org.apache.activemq.artemis.api.core.QueueConfiguration;
@@ -122,7 +123,15 @@ public class PostOfficeJournalLoader implements JournalLoader {
       for (final QueueBindingInfo queueBindingInfo : queueBindingInfos) {
          QueueConfiguration queueConfig = queueBindingInfo.getQueueConfiguration();
          queueBindingInfosMap.put(queueConfig.getId(), queueBindingInfo);
-         Filter filter = FilterImpl.createFilter(queueConfig.getFilterString());
+
+         Filter filter = null;
+
+         try {
+            filter = FilterImpl.createFilter(queueConfig.getFilterString());
+         } catch (ActiveMQInvalidFilterExpressionException e) {
+            ActiveMQServerLogger.LOGGER.invalidFilterExpressionOnJournalReload(String.valueOf(queueConfig.getName()), queueConfig.getFilterString() != null ? queueConfig.getFilterString().toString() : "null");
+            throw e;
+         }
 
          if (postOffice.getBinding(queueConfig.getName()) != null) {
 
diff --git a/docs/user-manual/versions.adoc b/docs/user-manual/versions.adoc
index a92856d318..c55576153f 100644
--- a/docs/user-manual/versions.adoc
+++ b/docs/user-manual/versions.adoc
@@ -58,6 +58,9 @@ Of course, this is a traditional trade-off for increased reliability.
 The exact impact will depend on your specific use-case.
 Generally speaking, the recommendation is to use the lowest QoS possible to avoid unnecessary overhead.
 * Server discovery is disabled by default, see the xref:clusters.adoc#server-discovery[Server Discovery] section in the user manual for security requirements and enablement.
+* Selector filters now limit `%` wildcards to 5 by default.
+Configure via system property `org.apache.activemq.artemis.selector.maxWildcards` or environment variable `ARTEMIS_SELECTOR_MAX_WILDCARDS`.
+The broker will fail to start if existing durable subscriptions exceed this limit, so increase the value before upgrading if needed.
 
 == Version 2.55.0
 
diff --git a/tests/compatibility-tests/src/main/resources/selectorWildcard/sendMessages.groovy b/tests/compatibility-tests/src/main/resources/selectorWildcard/sendMessages.groovy
new file mode 100644
index 0000000000..d6d9b6086f
--- /dev/null
+++ b/tests/compatibility-tests/src/main/resources/selectorWildcard/sendMessages.groovy
@@ -0,0 +1,77 @@
+package selectorWildcard
+
+import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory
+import org.apache.activemq.artemis.tests.compatibility.GroovyRun
+
+import javax.jms.*
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ConnectionFactory cf = new ActiveMQConnectionFactory("tcp://localhost:61616?confirmationWindowSize=1048576&blockOnDurableSend=false&ha=true&reconnectAttempts=-1&retryInterval=100")
+
+Connection connection = cf.createConnection()
+
+try {
+   connection.setClientID("selectorWildcardClient")
+   connection.start()
+   Session session = connection.createSession(true, Session.SESSION_TRANSACTED)
+   Topic topic = session.createTopic("selectorWildcardTopic")
+
+
+   TopicSubscriber subscriber = session.createDurableSubscriber(topic, "my-wildcard-sub", "prop LIKE '%a%a%a%a%a%a'", false)
+
+   MessageProducer producer = session.createProducer(topic)
+   producer.setDeliveryMode(DeliveryMode.PERSISTENT)
+
+   int numMessages = 10
+   for (int i = 0; i < numMessages; i++) {
+      TextMessage msg = session.createTextMessage("msg" + i)
+      msg.setStringProperty("prop", "XaXaXaXaXaXa")
+      producer.send(msg)
+   }
+   session.commit()
+
+   TopicSubscriber subscriberConsecutive = session.createDurableSubscriber(topic, "my-consecutive-wildcard-sub", "prop LIKE '%%%S'", false)
+
+   for (int i = 0; i < numMessages; i++) {
+      TextMessage msg = session.createTextMessage("consecutive-msg" + i)
+      msg.setStringProperty("prop", "endsWithS");
+      producer.send(msg)
+   }
+   session.commit()
+
+// Verify messages are received before stop
+   for (int i = 0; i < numMessages; i++) {
+      TextMessage m = (TextMessage) subscriber.receive(5000)
+      GroovyRun.assertNotNull(m)
+      GroovyRun.assertEquals("msg" + i, m.getText())
+   }
+   session.rollback()
+
+   for (int i = 0; i < numMessages; i++) {
+      TextMessage m = (TextMessage) subscriberConsecutive.receive(5000)
+      GroovyRun.assertNotNull(m)
+      GroovyRun.assertEquals("consecutive-msg" + i, m.getText())
+   }
+   session.rollback()
+
+   subscriber.close()
+   subscriberConsecutive.close()
+} finally {
+   connection.close()
+}
diff --git a/tests/compatibility-tests/src/test/java/org/apache/activemq/artemis/selector/filter/ComparisonExpressionTestAccessor.java b/tests/compatibility-tests/src/test/java/org/apache/activemq/artemis/selector/filter/ComparisonExpressionTestAccessor.java
new file mode 100644
index 0000000000..195e21c1fd
--- /dev/null
+++ b/tests/compatibility-tests/src/test/java/org/apache/activemq/artemis/selector/filter/ComparisonExpressionTestAccessor.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.selector.filter;
+
+public class ComparisonExpressionTestAccessor {
+
+   public static void setMaxWildcards(int maxWildcards) {
+      ComparisonExpression.setMaxWildcards(maxWildcards);
+   }
+}
diff --git a/tests/compatibility-tests/src/test/java/org/apache/activemq/artemis/tests/compatibility/SelectorWildcardCompatibilityTest.java b/tests/compatibility-tests/src/test/java/org/apache/activemq/artemis/tests/compatibility/SelectorWildcardCompatibilityTest.java
new file mode 100644
index 0000000000..140d7dabcd
--- /dev/null
+++ b/tests/compatibility-tests/src/test/java/org/apache/activemq/artemis/tests/compatibility/SelectorWildcardCompatibilityTest.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.activemq.artemis.tests.compatibility;
+
+import static org.apache.activemq.artemis.tests.compatibility.GroovyRun.ARTEMIS_2_44_0;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import javax.jms.Connection;
+import javax.jms.MessageConsumer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+
+import java.lang.invoke.MethodHandles;
+
+import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl;
+import org.apache.activemq.artemis.core.server.JournalType;
+import org.apache.activemq.artemis.core.server.embedded.EmbeddedActiveMQ;
+import org.apache.activemq.artemis.core.settings.impl.AddressSettings;
+import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;
+import org.apache.activemq.artemis.logs.AssertionLoggerHandler;
+import org.apache.activemq.artemis.selector.filter.ComparisonExpressionTestAccessor;
+import org.apache.activemq.artemis.tests.compatibility.base.ClasspathBase;
+import org.apache.activemq.artemis.utils.FileUtil;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SelectorWildcardCompatibilityTest extends ClasspathBase {
+   private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+   @Test
+   public void testWildcardSelector_2_44_0_to_Snapshot() throws Throwable {
+      ClassLoader senderLoader = getClasspath(ARTEMIS_2_44_0);
+      FileUtil.deleteDirectory(serverFolder);
+      serverFolder.mkdirs();
+
+      try {
+         setVariable(senderLoader, "persistent", true);
+         startServer(serverFolder, senderLoader, "wildcardTest", null, true, "servers/artemisServer.groovy", ARTEMIS_2_44_0, ARTEMIS_2_44_0, ARTEMIS_2_44_0);
+         evaluate(senderLoader, "selectorWildcard/sendMessages.groovy");
+         stopServer(senderLoader);
+      } catch (Throwable t) {
+         try {
+            stopServer(senderLoader);
+         } catch (Throwable ignored) {
+         }
+         throw t;
+      }
+
+      // Restart with the current server directly.
+      // The journal contains a durable subscription with a selector that has too many wildcards.
+      // The server should fail to start and log the appropriate warning.
+      try (AssertionLoggerHandler loggerHandler = new AssertionLoggerHandler()) {
+         ConfigurationImpl configuration = new ConfigurationImpl();
+         configuration.setJournalType(JournalType.NIO);
+         configuration.setBrokerInstance(new java.io.File(serverFolder, "wildcardTest"));
+         configuration.addAcceptorConfiguration("artemis", "tcp://0.0.0.0:61616");
+         configuration.setSecurityEnabled(false);
+         configuration.setPersistenceEnabled(true);
+         configuration.addAddressesSetting("#", new AddressSettings().setAutoCreateAddresses(true));
+
+         EmbeddedActiveMQ server = new EmbeddedActiveMQ();
+         server.setConfiguration(configuration);
+
+         server.start();
+         assertTrue(loggerHandler.findText("AMQ224169"));
+         assertFalse(server.getActiveMQServer().isStarted());
+
+         try {
+            server.stop();
+         } catch (Throwable ignored) {
+         }
+
+
+         logger.info("Retrying now with a more permissive setting");
+
+         ComparisonExpressionTestAccessor.setMaxWildcards(10);
+         try {
+            server.start();
+
+            try (ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("tcp://localhost:61616");
+                 Connection connection = cf.createConnection()) {
+               connection.start();
+               Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+               Queue queue = session.createQueue("selectorWildcardClient.my-wildcard-sub");
+               MessageConsumer consumer = session.createConsumer(queue);
+               for (int i = 0; i < 10; i++) {
+                  TextMessage m = (TextMessage) consumer.receive(5000);
+                  assertNotNull(m);
+               }
+               consumer.close();
+
+               Queue consecutiveQueue = session.createQueue("selectorWildcardClient.my-consecutive-wildcard-sub");
+               MessageConsumer consecutiveConsumer = session.createConsumer(consecutiveQueue);
+               for (int i = 0; i < 10; i++) {
+                  TextMessage m = (TextMessage) consecutiveConsumer.receive(5000);
+                  assertNotNull(m);
+               }
+               consecutiveConsumer.close();
+            }
+         } finally {
+            ComparisonExpressionTestAccessor.setMaxWildcards(5);
+            server.stop();
+         }
+      }
+   }
+}


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


(artemis) 11/11: Further harden RemoveSubscriptionInfo handling

Posted by cl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit eb3c2b757ff103de5ed509dccc049d05a848cafe
Author: Timothy Bish <ta...@gmail.com>
AuthorDate: Tue Aug 18 16:25:03 2026 -0400

    Further harden RemoveSubscriptionInfo handling
    
    (cherry picked from commit 6fcc19573c73b7c064cdf1a7c1329ea606d5b9de)
---
 .../core/protocol/openwire/OpenWireConnection.java |  8 +++
 .../OpenWireRemoveSubscriptionInfoFrameTest.java   | 70 ++++++++++++++++++++--
 2 files changed, 74 insertions(+), 4 deletions(-)

diff --git a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireConnection.java b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireConnection.java
index 58b41dc05c..9473d02351 100644
--- a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireConnection.java
+++ b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireConnection.java
@@ -1360,6 +1360,14 @@ public class OpenWireConnection extends AbstractRemotingConnection implements Se
             throw new IllegalStateException("Cannot remove a subscription on a connection that had not been registered");
          }
 
+         if (!Objects.equals(cs.getInfo().getConnectionId(), subInfo.getConnectionId())) {
+            throw new IllegalStateException("Rejecting remove subscription command with invalid data");
+         }
+
+         if (!Objects.equals(cs.getInfo().getClientId(), subInfo.getClientId())) {
+            throw new IllegalStateException("Rejecting remove subscription command with invalid data");
+         }
+
          final SimpleString subQueueName = org.apache.activemq.artemis.jms.client.ActiveMQDestination.createQueueNameForSubscription(true, subInfo.getClientId(), subInfo.getSubscriptionName());
 
          ss.deleteQueue(subQueueName);
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/openwire/OpenWireRemoveSubscriptionInfoFrameTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/openwire/OpenWireRemoveSubscriptionInfoFrameTest.java
index 1e96b55a50..65b58f947f 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/openwire/OpenWireRemoveSubscriptionInfoFrameTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/openwire/OpenWireRemoveSubscriptionInfoFrameTest.java
@@ -41,6 +41,7 @@ import java.util.function.Consumer;
 
 import javax.jms.Connection;
 import javax.jms.DeliveryMode;
+import javax.jms.JMSException;
 import javax.jms.JMSSecurityException;
 import javax.jms.MessageConsumer;
 import javax.jms.MessageProducer;
@@ -105,7 +106,7 @@ public class OpenWireRemoveSubscriptionInfoFrameTest extends BasicOpenWireTest {
    protected void extraServerConfig(Configuration configuration) {
       super.extraServerConfig(configuration);
 
-      final Role allowed = new Role(ALLOWED_ROLE, true, true, true, true, false, false, false, false, true, false, false, false);
+      final Role allowed = new Role(ALLOWED_ROLE, true, true, true, true, false, false, false, false, true, true, false, false);
       final Role denied = new Role(DENIED_ROLE, false, false, false, false, false, false, false, false, false, false, false, false);
 
       final ActiveMQJAASSecurityManager securityManager = (ActiveMQJAASSecurityManager) server.getSecurityManager();
@@ -174,12 +175,73 @@ public class OpenWireRemoveSubscriptionInfoFrameTest extends BasicOpenWireTest {
       }
    }
 
+   @Test
+   public void testConnectionWithAuthorizationCannotDeleteSubscriptionQueueUsingAlternateClientId() throws Exception {
+      final String CLIENT_ID = "test-id";
+      final String SUBSCRIPTION_NAME = getTestMethodName();
+      final SimpleString SUBSCRIPTION_QUEUE =
+         org.apache.activemq.artemis.jms.client.ActiveMQDestination.createQueueNameForSubscription(true, CLIENT_ID, SUBSCRIPTION_NAME);
+
+      try (Connection connection = factory.createConnection(ALLOWED_USER, PASS)) {
+
+         connection.setClientID(CLIENT_ID);
+
+         final Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+         final Topic destination = session.createTopic(ADDRESS);
+
+         final MessageProducer producer = session.createProducer(destination);
+         final MessageConsumer consumer = session.createDurableConsumer(destination, SUBSCRIPTION_NAME);
+
+         consumer.close();
+
+         producer.setDeliveryMode(DeliveryMode.PERSISTENT); // ALLOWED USER
+         producer.send(session.createTextMessage("durable-message"));
+
+         Wait.assertTrue(() -> server.queueQuery(SUBSCRIPTION_QUEUE).isExists(), 1_000, 50);
+
+         final Queue subscriptionQueue = server.locateQueue(SUBSCRIPTION_QUEUE);
+
+         assertEquals(1, subscriptionQueue.getMessageCount());
+
+         final ConnectionInfo connectionInfo = new ConnectionInfo();
+         connectionInfo.setConnectionId(new ConnectionId("test:1"));
+         connectionInfo.setClientId(UUID.randomUUID().toString());
+         connectionInfo.setUserName(ALLOWED_USER);
+         connectionInfo.setPassword(PASS);
+         connectionInfo.setResponseRequired(true);
+
+         final RemoveSubscriptionInfo removeSubscription = new RemoveSubscriptionInfo();
+         removeSubscription.setClientId(CLIENT_ID);
+         removeSubscription.setConnectionId(new ConnectionId("test:1"));
+         removeSubscription.setResponseRequired(true);
+         removeSubscription.setSubcriptionName(SUBSCRIPTION_NAME);
+
+         try (OpenwireTestClient client = new OpenwireTestClient(OWHOST, OWPORT)) {
+            client.connect().get(2, TimeUnit.SECONDS);
+            client.send(connectionInfo).get(20, TimeUnit.SECONDS);
+
+            final Response response = client.send(removeSubscription).get(10, TimeUnit.SECONDS);
+
+            assertTrue(response.isException());
+            assertNull(client.getLastError());
+
+            final ExceptionResponse exResponse = (ExceptionResponse) response;
+
+            assertTrue(exResponse.getException() instanceof JMSException);
+         }
+
+         Thread.sleep(50); // Bit of buffer to allow the command to be handled.
+
+         Wait.assertTrue(() -> server.queueQuery(SUBSCRIPTION_QUEUE).isExists(), 1_000, 50);
+      }
+   }
+
    @Test
    public void testConnectionWithoutAuthorizationCannotDeleteSubscriptionQueue() throws Exception {
       final String CLIENT_ID = "test-id";
       final String SUBSCRIPTION_NAME = getTestMethodName();
-      final SimpleString SUBSCRIPTION_QUEUE = org.apache.activemq.artemis.jms.client.ActiveMQDestination.createQueueNameForSubscription(true, CLIENT_ID,
-         SUBSCRIPTION_NAME);
+      final SimpleString SUBSCRIPTION_QUEUE =
+         org.apache.activemq.artemis.jms.client.ActiveMQDestination.createQueueNameForSubscription(true, CLIENT_ID, SUBSCRIPTION_NAME);
 
       try (Connection connection = factory.createConnection(ALLOWED_USER, PASS)) {
 
@@ -206,7 +268,7 @@ public class OpenWireRemoveSubscriptionInfoFrameTest extends BasicOpenWireTest {
 
          final ConnectionInfo connectionInfo = new ConnectionInfo();
          connectionInfo.setConnectionId(new ConnectionId("test:1"));
-         connectionInfo.setClientId(UUID.randomUUID().toString());
+         connectionInfo.setClientId(CLIENT_ID);
          connectionInfo.setUserName(DENIED_USER);
          connectionInfo.setPassword(PASS);
          connectionInfo.setResponseRequired(true);


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


(artemis) 02/11: Reattachment should be disabled

Posted by cl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit ff256d7c821f8c8448de95e5db9b8ad5deea4142
Author: Clebert Suconic <cl...@gmail.com>
AuthorDate: Mon Jun 29 22:48:38 2026 -0400

    Reattachment should be disabled
    
    We have been removng the sessions' state after disconects for a few
    years already.
    
    The only place where reattachment could still occur would be when the
    network cable of a client is unplugged and the server takes some times
    to realize that.
    
    For that reason reattachment has been "dead code" for a few years
    already and we should depart with it.
    
    (cherry picked from commit 26ce5813dc9d640b18f18c436801454e0192895c)
    
    ARTEMIS-6245
---
 .../core/client/impl/ClientSessionImpl.java        |  106 +-
 .../artemis/core/protocol/core/Channel.java        |   14 -
 .../protocol/core/impl/ActiveMQSessionContext.java |   31 +-
 .../core/protocol/core/impl/ChannelImpl.java       |   18 -
 .../artemis/spi/core/remoting/SessionContext.java  |    7 -
 .../protocol/core/ServerSessionPacketHandler.java  |   59 -
 .../protocol/core/impl/ActiveMQPacketHandler.java  |   52 +-
 .../artemis/core/server/ActiveMQServerLogger.java  |    8 +-
 .../MultiThreadRandomReattachTestBase.java         | 1252 ------------------
 .../MultiThreadReattachSupportTestBase.java        |  268 ----
 .../reattachCompatibility/testValidReattach.groovy |  189 +++
 .../ValidateReattachCompatibilityTest.java         |  121 ++
 .../client/impl/ClientSessionImplAccessor.java     |  133 ++
 .../integration/client/TemporaryQueueTest.java     |   13 +-
 .../reattach/MultiThreadRandomReattachTest.java    |   25 -
 .../NettyMultiThreadRandomReattachTest.java        |   40 -
 .../cluster/reattach/OrderReattachTest.java        |  258 ----
 .../reattach/RandomReattachIntegrationTest.java    | 1354 --------------------
 .../cluster/reattach/ReattachDisabledTest.java     |  155 +++
 .../integration/cluster/reattach/ReattachTest.java | 1030 ---------------
 .../integration/cluster/util/BackupSyncDelay.java  |   10 -
 .../tests/integration/jms/client/RollbackTest.java |   29 +-
 .../integration/jms/cluster/JMSReconnectTest.java  |   14 -
 .../tests/integration/remoting/ReconnectTest.java  |   50 -
 24 files changed, 682 insertions(+), 4554 deletions(-)

diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientSessionImpl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientSessionImpl.java
index 3641ccf56f..75d09314f6 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientSessionImpl.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientSessionImpl.java
@@ -1296,76 +1296,70 @@ public final class ClientSessionImpl implements ClientSessionInternal, FailureLi
          }
 
          try {
+            sessionContext.transferConnection(backupConnection);
 
-            // TODO remove this and encapsulate it
+            // We change the name of the Session, otherwise the server could close it while we are still sending the recreate
+            // in certain failure scenarios
+            // For instance the fact we didn't change the name of the session after failover or reconnect
+            // was the reason allowing multiple Sessions to be closed simultaneously breaking concurrency
+            this.name = UUIDGenerator.getInstance().generateStringUUID();
 
-            boolean reattached = sessionContext.reattachOnNewConnection(backupConnection);
+            sessionContext.resetName(name);
 
-            if (!reattached) {
+            Map<ConsumerContext, ClientConsumerInternal> clonedConsumerEntries = cloneConsumerEntries();
 
-               // We change the name of the Session, otherwise the server could close it while we are still sending the recreate
-               // in certain failure scenarios
-               // For instance the fact we didn't change the name of the session after failover or reconnect
-               // was the reason allowing multiple Sessions to be closed simultaneously breaking concurrency
-               this.name = UUIDGenerator.getInstance().generateStringUUID();
-
-               sessionContext.resetName(name);
-
-               Map<ConsumerContext, ClientConsumerInternal> clonedConsumerEntries = cloneConsumerEntries();
-
-               for (ClientConsumerInternal consumer : clonedConsumerEntries.values()) {
-                  consumer.clearAtFailover();
-               }
+            for (ClientConsumerInternal consumer : clonedConsumerEntries.values()) {
+               consumer.clearAtFailover();
+            }
 
-               // The session wasn't found on the server - probably we're failing over onto a backup server where the
-               // session won't exist or the target server has been restarted - in this case the session will need to be
-               // recreated,
-               // and we'll need to recreate any consumers
-
-               // It could also be that the server hasn't been restarted, but the session is currently executing close,
-               // and
-               // that
-               // has already been executed on the server, that's why we can't find the session- in this case we *don't*
-               // want
-               // to recreate the session, we just want to unblock the blocking call
-               if (!inClose && mayAttemptToFailover) {
-                  sessionContext.recreateSession(username, password, minLargeMessageSize, xa, autoCommitSends, autoCommitAcks, preAcknowledge);
-
-                  for (Map.Entry<ConsumerContext, ClientConsumerInternal> entryx : clonedConsumerEntries.entrySet()) {
-
-                     ClientConsumerInternal consumerInternal = entryx.getValue();
-                     synchronized (consumerInternal) {
-                        if (!consumerInternal.isClosed()) {
-                           sessionContext.recreateConsumerOnServer(consumerInternal, entryx.getKey().getId(), started);
-                        }
+            // The session wasn't found on the server - probably we're failing over onto a backup server where the
+            // session won't exist or the target server has been restarted - in this case the session will need to be
+            // recreated,
+            // and we'll need to recreate any consumers
+
+            // It could also be that the server hasn't been restarted, but the session is currently executing close,
+            // and
+            // that
+            // has already been executed on the server, that's why we can't find the session- in this case we *don't*
+            // want
+            // to recreate the session, we just want to unblock the blocking call
+            if (!inClose && mayAttemptToFailover) {
+               sessionContext.recreateSession(username, password, minLargeMessageSize, xa, autoCommitSends, autoCommitAcks, preAcknowledge);
+
+               for (Map.Entry<ConsumerContext, ClientConsumerInternal> entryx : clonedConsumerEntries.entrySet()) {
+
+                  ClientConsumerInternal consumerInternal = entryx.getValue();
+                  synchronized (consumerInternal) {
+                     if (!consumerInternal.isClosed()) {
+                        sessionContext.recreateConsumerOnServer(consumerInternal, entryx.getKey().getId(), started);
                      }
                   }
+               }
 
-                  if ((!autoCommitAcks || !autoCommitSends) && workDone) {
-                     // this is protected by a lock, so we can guarantee nothing will sneak here
-                     // while we do our work here
-                     rollbackOnly = true;
-                  }
-                  if (currentXID != null) {
-                     sessionContext.xaFailed(currentXID);
-                     rollbackOnly = true;
-                  }
-
-                  // Now start the session if it was already started
-                  if (started) {
-                     for (ClientConsumerInternal consumer : clonedConsumerEntries.values()) {
-                        consumer.clearAtFailover();
-                        consumer.start();
-                     }
+               if ((!autoCommitAcks || !autoCommitSends) && workDone) {
+                  // this is protected by a lock, so we can guarantee nothing will sneak here
+                  // while we do our work here
+                  rollbackOnly = true;
+               }
+               if (currentXID != null) {
+                  sessionContext.xaFailed(currentXID);
+                  rollbackOnly = true;
+               }
 
-                     sessionContext.restartSession();
+               // Now start the session if it was already started
+               if (started) {
+                  for (ClientConsumerInternal consumer : clonedConsumerEntries.values()) {
+                     consumer.clearAtFailover();
+                     consumer.start();
                   }
 
-                  resetCreditManager = true;
+                  sessionContext.restartSession();
                }
 
-               sessionContext.returnBlocking(cause);
+               resetCreditManager = true;
             }
+
+            sessionContext.returnBlocking(cause);
          } catch (ActiveMQRoutingException e) {
             logger.info("failedToHandleFailover.ActiveMQRoutingException");
             suc = false;
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/Channel.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/Channel.java
index a0b7305351..c05cd407a9 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/Channel.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/Channel.java
@@ -171,20 +171,6 @@ public interface Channel {
     */
    void transferConnection(CoreRemotingConnection newConnection);
 
-   /**
-    * resends any packets that have not received confirmations yet.
-    * <p>
-    * Typically called after a connection has been transferred.
-    *
-    * @param lastConfirmedCommandID the last confirmed packet
-    */
-   void replayCommands(int lastConfirmedCommandID);
-
-   /**
-    * {@return the last confirmed packet command id}
-    */
-   int getLastConfirmedCommandID();
-
    /**
     * queries if this channel is locked. This method is designed for use in monitoring of the system state, not for
     * synchronization control.
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 8c966ce76a..da128e4898 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
@@ -69,8 +69,6 @@ import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSess
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSharedQueueMessage_V2;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectConsumerMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectConsumerWithKillMessage;
-import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ReattachSessionMessage;
-import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ReattachSessionResponseMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.RemoveProducerMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.RollbackMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.SessionAcknowledgeMessage;
@@ -862,34 +860,7 @@ public class ActiveMQSessionContext extends SessionContext {
    public void transferConnection(RemotingConnection newConnection) {
       this.remotingConnection = newConnection;
       sessionChannel.transferConnection((CoreRemotingConnection) newConnection);
-   }
-
-   @Override
-   public boolean reattachOnNewConnection(RemotingConnection newConnection) throws ActiveMQException {
-
-      transferConnection(newConnection);
-
-      Packet request = new ReattachSessionMessage(name, sessionChannel.getLastConfirmedCommandID());
-
-      Channel channel1 = getCoreConnection().getChannel(1, -1);
-
-      ReattachSessionResponseMessage response = (ReattachSessionResponseMessage) channel1.sendBlocking(request, PacketImpl.REATTACH_SESSION_RESP);
-
-      if (response.isReattached()) {
-         logger.debug("Replaying commands for channelID={} with lastCommandID from the server={}", sessionChannel.getID(), response.getLastConfirmedCommandID());
-         // The session was found on the server - we reattached transparently ok
-
-         sessionChannel.replayCommands(response.getLastConfirmedCommandID());
-
-         return true;
-      } else {
-         logger.debug("Couldn't reattach session {}, performing as a failover operation now and recreating objects", sessionChannel.getID());
-
-         sessionChannel.clearCommands();
-
-         return false;
-      }
-
+      sessionChannel.clearCommands();
    }
 
    @Override
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ChannelImpl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ChannelImpl.java
index f2d5c9cfec..2c7b5e1209 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ChannelImpl.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ChannelImpl.java
@@ -212,11 +212,6 @@ public final class ChannelImpl implements Channel {
       return id;
    }
 
-   @Override
-   public int getLastConfirmedCommandID() {
-      return lastConfirmedCommandID.get();
-   }
-
    @Override
    public Lock getLock() {
       return lock;
@@ -720,19 +715,6 @@ public final class ChannelImpl implements Channel {
       }
    }
 
-   @Override
-   public void replayCommands(final int otherLastConfirmedCommandID) {
-      if (resendCache != null) {
-         if (logger.isTraceEnabled()) {
-            logger.trace("RemotingConnectionID={} Replaying commands on channelID={}", connection.getID(), id);
-         }
-         clearUpTo(otherLastConfirmedCommandID);
-
-         for (final Packet packet : resendCache) {
-            doWrite(packet);
-         }
-      }
-   }
 
    @Override
    public boolean isLocked() {
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/SessionContext.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/SessionContext.java
index e2505ea406..e649cf994b 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/SessionContext.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/SessionContext.java
@@ -75,13 +75,6 @@ public abstract class SessionContext {
     */
    public abstract void transferConnection(RemotingConnection newConnection);
 
-   /**
-    * it will either reattach or reconnect, preferably reattaching it.
-    *
-    * @return true if it was possible to reattach
-    */
-   public abstract boolean reattachOnNewConnection(RemotingConnection newConnection) throws ActiveMQException;
-
    public RemotingConnection getRemotingConnection() {
       return remotingConnection;
    }
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 ca597e8f2d..24e497c792 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
@@ -105,8 +105,6 @@ import org.apache.activemq.artemis.spi.core.remoting.Connection;
 import org.apache.activemq.artemis.utils.UUIDGenerator;
 import org.apache.activemq.artemis.utils.pools.MpscPool;
 import org.apache.activemq.artemis.utils.pools.Pool;
-import org.apache.activemq.artemis.utils.SimpleFuture;
-import org.apache.activemq.artemis.utils.SimpleFutureImpl;
 import org.apache.activemq.artemis.utils.actors.Actor;
 import org.apache.activemq.artemis.utils.actors.ArtemisExecutor;
 import org.slf4j.Logger;
@@ -995,63 +993,6 @@ public class ServerSessionPacketHandler implements ChannelHandler {
       }
    }
 
-   public int transferConnection(final CoreRemotingConnection newConnection, final int lastReceivedCommandID) {
-
-      SimpleFuture<Integer> future = new SimpleFutureImpl<>();
-      callExecutor.execute(() -> {
-         int value = internaltransferConnection(newConnection, lastReceivedCommandID);
-         future.set(value);
-      });
-
-      try {
-         return future.get().intValue();
-      } catch (Exception e) {
-         throw new IllegalStateException(e);
-      }
-   }
-
-   private int internaltransferConnection(final CoreRemotingConnection newConnection, final int lastReceivedCommandID) {
-      // We need to disable delivery on all the consumers while the transfer is occurring- otherwise packets might get
-      // delivered
-      // after the channel has transferred but *before* packets have been replayed - this will give the client the wrong
-      // sequence of packets.
-      // It is not sufficient to just stop the session, since right after stopping the session, another session start
-      // might be executed
-      // before we have transferred the connection, leaving it in a started state
-      session.setTransferring(true);
-
-      // Note. We do not destroy the replicating connection here. In the case the primary server has really crashed
-      // then the connection will get cleaned up anyway when the server ping timeout kicks in.
-      // In the case the primary server is really still up, i.e. a split brain situation (or in tests), then closing
-      // the replicating connection will cause the outstanding responses to be replayed on the primary server,
-      // if these reach the client who then subsequently fails over, on reconnection to backup, it will have
-      // received responses that the backup did not know about.
-
-      channel.transferConnection(newConnection);
-
-      newConnection.syncIDGeneratorSequence(remotingConnection.getIDGeneratorSequence());
-
-      session.transferConnection(newConnection);
-
-      Connection oldTransportConnection = remotingConnection.getTransportConnection();
-
-      remotingConnection = newConnection;
-
-      int serverLastReceivedCommandID = channel.getLastConfirmedCommandID();
-
-      channel.replayCommands(lastReceivedCommandID);
-
-      channel.setTransferring(false);
-
-      session.setTransferring(false);
-
-      // We do this because the old connection could be out of credits on netty
-      // this will force anything to resume after the reattach through the ReadyListener callbacks
-      oldTransportConnection.fireReady(true);
-
-      return serverLastReceivedCommandID;
-   }
-
    // Large Message is part of the core protocol, we have these functions here as part of Packet handler
    private void sendLarge(final Message message) throws Exception {
       // need to create the LargeMessage before continue
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQPacketHandler.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQPacketHandler.java
index 789e14bafc..2f399498f8 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQPacketHandler.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQPacketHandler.java
@@ -39,7 +39,6 @@ import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateQueu
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSessionMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSessionMessage_V2;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSessionResponseMessage;
-import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ReattachSessionMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ReattachSessionResponseMessage;
 import org.apache.activemq.artemis.core.security.ActiveMQPrincipal;
 import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle;
@@ -117,10 +116,8 @@ public class ActiveMQPacketHandler implements ChannelHandler {
             break;
          }
          case PacketImpl.REATTACH_SESSION: {
-            ReattachSessionMessage request = (ReattachSessionMessage) packet;
-
-            handleReattachSession(request);
-
+            // We no longer use reattachment
+            channel1.send(new ReattachSessionResponseMessage(-1, false));
             break;
          }
          case PacketImpl.CREATE_QUEUE: {
@@ -263,49 +260,6 @@ public class ActiveMQPacketHandler implements ChannelHandler {
       }
    }
 
-   private void handleReattachSession(final ReattachSessionMessage request) {
-      Packet response = null;
-
-      try {
-
-         if (!server.isStarted()) {
-            response = new ReattachSessionResponseMessage(-1, false);
-         }
-
-         logger.debug("Reattaching request from {}", connection.getRemoteAddress());
-
-         ServerSessionPacketHandler sessionHandler = protocolManager.getSessionHandler(request.getName());
-
-         if (/*!server.checkActivate() || */ sessionHandler == null) {
-            response = new ReattachSessionResponseMessage(-1, false);
-         } else {
-            if (sessionHandler.getChannel().getConfirmationWindowSize() == -1) {
-               // Even though session exists, we can't reattach since confi window size == -1,
-               // i.e. we don't have a resend cache for commands, so we just close the old session
-               // and let the client recreate
-
-               ActiveMQServerLogger.LOGGER.reattachRequestFailed(connection.getRemoteAddress());
-
-               sessionHandler.closeListeners();
-               sessionHandler.close();
-
-               response = new ReattachSessionResponseMessage(-1, false);
-            } else {
-               // Reconnect the channel to the new connection
-               int serverLastConfirmedCommandID = sessionHandler.transferConnection(connection, request.getLastConfirmedCommandID());
-
-               response = new ReattachSessionResponseMessage(serverLastConfirmedCommandID, true);
-            }
-         }
-      } catch (Exception e) {
-         ActiveMQServerLogger.LOGGER.failedToReattachSession(e);
-
-         response = new ActiveMQExceptionMessage(new ActiveMQInternalErrorException());
-      }
-
-      channel1.send(response);
-   }
-
    private void handleCreateQueue(final CreateQueueMessage request) {
       try {
          server.createQueue(QueueConfiguration.of(request.getQueueName())
@@ -317,4 +271,4 @@ public class ActiveMQPacketHandler implements ChannelHandler {
          ActiveMQServerLogger.LOGGER.failedToHandleCreateQueue(e);
       }
    }
-}
+}
\ No newline at end of file
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java
index 33847c5cac..0b711bc118 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java
@@ -50,7 +50,7 @@ import org.apache.activemq.artemis.spi.core.remoting.Connection;
 /**
  * Logger Codes 220000 - 228999
  */
-@LogBundle(projectCode = "AMQ", regexID = "22[0-8][0-9]{3}", retiredIDs = {221026, 221052, 222003, 222012, 222015, 222020, 222021, 222022, 222024, 222027, 222028, 222029, 222048, 222052, 222058, 222064, 222071, 222078, 222079, 222083, 222084, 222088, 222090, 222102, 222105, 222110, 222113, 222128, 222134, 222135, 222152, 222159, 222163, 222167, 222170, 222171, 222182, 222190, 222192, 222193, 222204, 222249, 222252, 222255, 222257, 222259, 222260, 222276, 222277, 222288, 224001, 224002, 2 [...]
+@LogBundle(projectCode = "AMQ", regexID = "22[0-8][0-9]{3}", retiredIDs = {221026, 221052, 222003, 222012, 222015, 222020, 222021, 222022, 222024, 222027, 222028, 222029, 222048, 222052, 222058, 222064, 222066, 222071, 222078, 222079, 222083, 222084, 222088, 222090, 222102, 222105, 222110, 222113, 222128, 222134, 222135, 222152, 222159, 222163, 222167, 222170, 222171, 222182, 222190, 222192, 222193, 222204, 222249, 222252, 222255, 222257, 222259, 222260, 222276, 222277, 222288, 224001, 2 [...]
 public interface ActiveMQServerLogger {
 
    // Note: logger ID 224127 uses "org.apache.activemq.artemis.core.server.Queue" for its logger category, rather than ActiveMQServerLogger.class.getPackage().getName()
@@ -479,9 +479,6 @@ public interface ActiveMQServerLogger {
    @LogMessage(id = 222065, value = "Client is not being consistent on the request versioning. It just sent a version id={} while it informed {} previously", level = LogMessage.Level.DEBUG)
    void incompatibleVersionAfterConnect(int version, int clientVersion);
 
-   @LogMessage(id = 222066, value = "Reattach request from {} failed as there is no confirmationWindowSize configured, which may be ok for your system", level = LogMessage.Level.WARN)
-   void reattachRequestFailed(String remoteAddress);
-
    @LogMessage(id = 222067, value = "Connection failure has been detected: {} [code={}]", level = LogMessage.Level.WARN)
    void connectionFailureDetected(String message, ActiveMQExceptionType type);
 
@@ -1133,9 +1130,6 @@ public interface ActiveMQServerLogger {
    @LogMessage(id = 224018, value = "Failed to create session", level = LogMessage.Level.ERROR)
    void failedToCreateSession(Exception e);
 
-   @LogMessage(id = 224019, value = "Failed to reattach session", level = LogMessage.Level.ERROR)
-   void failedToReattachSession(Exception e);
-
    @LogMessage(id = 224020, value = "Failed to handle create queue", level = LogMessage.Level.ERROR)
    void failedToHandleCreateQueue(Exception e);
 
diff --git a/tests/artemis-test-support/src/main/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/MultiThreadRandomReattachTestBase.java b/tests/artemis-test-support/src/main/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/MultiThreadRandomReattachTestBase.java
deleted file mode 100644
index 81cb80329f..0000000000
--- a/tests/artemis-test-support/src/main/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/MultiThreadRandomReattachTestBase.java
+++ /dev/null
@@ -1,1252 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.activemq.artemis.tests.integration.cluster.reattach;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.activemq.artemis.api.core.ActiveMQException;
-import org.apache.activemq.artemis.api.core.QueueConfiguration;
-import org.apache.activemq.artemis.api.core.RoutingType;
-import org.apache.activemq.artemis.api.core.SimpleString;
-import org.apache.activemq.artemis.api.core.client.ClientConsumer;
-import org.apache.activemq.artemis.api.core.client.ClientMessage;
-import org.apache.activemq.artemis.api.core.client.ClientProducer;
-import org.apache.activemq.artemis.api.core.client.ClientSession;
-import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
-import org.apache.activemq.artemis.api.core.client.MessageHandler;
-import org.apache.activemq.artemis.api.core.client.ServerLocator;
-import org.apache.activemq.artemis.core.config.Configuration;
-import org.apache.activemq.artemis.core.config.CoreAddressConfiguration;
-import org.apache.activemq.artemis.core.remoting.impl.invm.InVMRegistry;
-import org.apache.activemq.artemis.core.server.ActiveMQServer;
-import org.apache.activemq.artemis.jms.client.ActiveMQBytesMessage;
-import org.apache.activemq.artemis.jms.client.ActiveMQTextMessage;
-import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
-import org.junit.jupiter.api.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import java.lang.invoke.MethodHandles;
-
-public abstract class MultiThreadRandomReattachTestBase extends MultiThreadReattachSupportTestBase {
-
-   private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-
-   private static final int RECEIVE_TIMEOUT = 30000;
-
-   private final int LATCH_WAIT = getLatchWait();
-
-   private final int NUM_THREADS = getNumThreads();
-
-   protected static final SimpleString ADDRESS = SimpleString.of("FailoverTestAddress");
-
-   protected ActiveMQServer server;
-
-   protected abstract int getNumIterations();
-
-   @Override
-   protected void start() throws Exception {
-      Configuration primaryConf = createDefaultInVMConfig();
-      server = createServer(false, primaryConf);
-      server.getConfiguration().getAddressConfigurations().add(new CoreAddressConfiguration().setName(ADDRESS.toString()).addRoutingType(RoutingType.MULTICAST));
-      server.start();
-      waitForServerToStart(server);
-   }
-
-   protected void setBody(final ClientMessage message) throws Exception {
-      // Give each msg a body
-      message.getBodyBuffer().writeBytes(new byte[250]);
-   }
-
-   protected boolean checkSize(final ClientMessage message) {
-      return message.getBodyBuffer().readableBytes() == 250;
-   }
-
-   @Test
-   public void testA() throws Exception {
-      runTestMultipleThreads(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf, final int threadNum) throws Exception {
-            doTestA(sf, threadNum);
-         }
-      }, NUM_THREADS, false);
-
-   }
-
-   @Test
-   public void testB() throws Exception {
-      runTestMultipleThreads(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf, final int threadNum) throws Exception {
-            doTestB(sf, threadNum);
-         }
-      }, NUM_THREADS, false);
-   }
-
-   @Test
-   public void testC() throws Exception {
-      runTestMultipleThreads(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf, final int threadNum) throws Exception {
-            doTestC(sf, threadNum);
-         }
-      }, NUM_THREADS, false);
-   }
-
-   @Test
-   public void testD() throws Exception {
-      runTestMultipleThreads(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf, final int threadNum) throws Exception {
-            doTestD(sf, threadNum);
-         }
-      }, NUM_THREADS, false);
-   }
-
-   @Test
-   public void testE() throws Exception {
-      runTestMultipleThreads(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf, final int threadNum) throws Exception {
-            doTestE(sf, threadNum);
-         }
-      }, NUM_THREADS, false);
-   }
-
-   @Test
-   public void testF() throws Exception {
-      runTestMultipleThreads(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf, final int threadNum) throws Exception {
-            doTestF(sf, threadNum);
-         }
-      }, NUM_THREADS, false);
-   }
-
-   @Test
-   public void testG() throws Exception {
-      runTestMultipleThreads(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf, final int threadNum) throws Exception {
-            doTestG(sf, threadNum);
-         }
-      }, NUM_THREADS, false);
-   }
-
-   @Test
-   public void testH() throws Exception {
-      runTestMultipleThreads(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf, final int threadNum) throws Exception {
-            doTestH(sf, threadNum);
-         }
-      }, NUM_THREADS, false);
-   }
-
-   @Test
-   public void testI() throws Exception {
-      runTestMultipleThreads(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf, final int threadNum) throws Exception {
-            doTestI(sf, threadNum);
-         }
-      }, NUM_THREADS, false);
-   }
-
-   @Test
-   public void testJ() throws Exception {
-      runTestMultipleThreads(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf, final int threadNum) throws Exception {
-            doTestJ(sf, threadNum);
-         }
-      }, NUM_THREADS, false);
-   }
-
-   @Test
-   public void testK() throws Exception {
-      runTestMultipleThreads(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf, final int threadNum) throws Exception {
-            doTestK(sf, threadNum);
-         }
-      }, NUM_THREADS, false);
-   }
-
-   @Test
-   public void testL() throws Exception {
-      runTestMultipleThreads(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf, final int threadNum) throws Exception {
-            doTestL(sf);
-         }
-      }, NUM_THREADS, true, 10);
-   }
-
-   // public void testM() throws Exception
-   // {
-   // runTestMultipleThreads(new RunnableT()
-   // {
-   // public void run(final ClientSessionFactory sf, final int threadNum) throws Exception
-   // {
-   // doTestM(sf, threadNum);
-   // }
-   // }, NUM_THREADS);
-   // }
-
-   @Test
-   public void testN() throws Exception {
-      runTestMultipleThreads(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf, final int threadNum) throws Exception {
-            doTestN(sf, threadNum);
-         }
-      }, NUM_THREADS, false);
-   }
-
-   @Test
-   public void testO() throws Exception {
-      runTestMultipleThreads(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf, final int threadNum) throws Exception {
-            doTestO(sf, threadNum);
-         }
-      }, NUM_THREADS, false);
-   }
-
-   protected ClientSession createAutoCommitSession(final ClientSessionFactory sf) throws Exception {
-      ClientSession session = sf.createSession(false, true, true);
-      return session;
-   }
-
-   protected ClientSession createTransactionalSession(final ClientSessionFactory sf) throws Exception {
-      ClientSession session = sf.createSession(false, false, false);
-
-      return session;
-   }
-
-   protected void doTestA(final ClientSessionFactory sf,
-                          final int threadNum,
-                          final ClientSession session2) throws Exception {
-      SimpleString subName = SimpleString.of("sub" + threadNum);
-
-      ClientSession session = addClientSession(sf.createSession(false, true, true));
-
-      session.createQueue(QueueConfiguration.of(subName).setAddress(ADDRESS).setDurable(false));
-
-      ClientProducer producer = session.createProducer(ADDRESS);
-
-      ClientConsumer consumer = session.createConsumer(subName);
-
-      final int numMessages = 100;
-
-      sendMessages(session, producer, numMessages, threadNum);
-
-      session.start();
-
-      MyHandler handler = new MyHandler(threadNum, numMessages);
-
-      consumer.setMessageHandler(handler);
-
-      boolean ok = handler.latch.await(LATCH_WAIT, TimeUnit.MILLISECONDS);
-
-      if (!ok) {
-         throw new Exception("Timed out waiting for messages on handler " + System.identityHashCode(handler) +
-                                " threadnum " +
-                                threadNum);
-      }
-
-      if (handler.failure != null) {
-         throw new Exception("Handler failed: " + handler.failure);
-      }
-
-      producer.close();
-
-      consumer.close();
-
-      session.deleteQueue(subName);
-
-      session.close();
-   }
-
-   protected void doTestA(final ClientSessionFactory sf, final int threadNum) throws Exception {
-      long start = System.currentTimeMillis();
-
-      ClientSession s = sf.createSession(false, false, false);
-
-      final int numMessages = 100;
-
-      final int numSessions = 10;
-
-      Set<ClientConsumer> consumers = new HashSet<>();
-      Set<ClientSession> sessions = new HashSet<>();
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of(threadNum + "sub" + i);
-
-         ClientSession sessConsume = createAutoCommitSession(sf);
-
-         sessConsume.start();
-
-         sessConsume.createQueue(QueueConfiguration.of(subName).setAddress(ADDRESS).setDurable(false));
-
-         ClientConsumer consumer = sessConsume.createConsumer(subName);
-
-         consumers.add(consumer);
-
-         sessions.add(sessConsume);
-
-      }
-
-      ClientSession sessSend = sf.createSession(false, true, true);
-
-      ClientProducer producer = sessSend.createProducer(ADDRESS);
-
-      sendMessages(sessSend, producer, numMessages, threadNum);
-
-      Set<MyHandler> handlers = new HashSet<>();
-
-      for (ClientConsumer consumer : consumers) {
-         MyHandler handler = new MyHandler(threadNum, numMessages);
-
-         consumer.setMessageHandler(handler);
-
-         handlers.add(handler);
-      }
-
-      for (MyHandler handler : handlers) {
-         boolean ok = handler.latch.await(LATCH_WAIT, TimeUnit.MILLISECONDS);
-
-         if (!ok) {
-            throw new Exception("Timed out waiting for messages on handler " + System.identityHashCode(handler) +
-                                   " threadnum " +
-                                   threadNum);
-         }
-
-         if (handler.failure != null) {
-            throw new Exception("Handler failed: " + handler.failure);
-         }
-      }
-
-      sessSend.close();
-
-      for (ClientSession session : sessions) {
-         session.close();
-      }
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of(threadNum + "sub" + i);
-
-         s.deleteQueue(subName);
-      }
-
-      s.close();
-
-      long end = System.currentTimeMillis();
-
-      logger.debug("duration {}", (end - start));
-   }
-
-   protected void doTestB(final ClientSessionFactory sf, final int threadNum) throws Exception {
-      long start = System.currentTimeMillis();
-
-      ClientSession s = sf.createSession(false, false, false);
-
-      final int numMessages = 100;
-
-      final int numSessions = 10;
-
-      Set<ClientConsumer> consumers = new HashSet<>();
-      Set<ClientSession> sessions = new HashSet<>();
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of(threadNum + "sub" + i);
-
-         ClientSession sessConsume = createAutoCommitSession(sf);
-
-         sessConsume.createQueue(QueueConfiguration.of(subName).setAddress(ADDRESS).setDurable(false));
-
-         ClientConsumer consumer = sessConsume.createConsumer(subName);
-
-         consumers.add(consumer);
-
-         sessions.add(sessConsume);
-      }
-
-      ClientSession sessSend = sf.createSession(false, true, true);
-
-      ClientProducer producer = sessSend.createProducer(ADDRESS);
-
-      sendMessages(sessSend, producer, numMessages, threadNum);
-
-      for (ClientSession session : sessions) {
-         session.start();
-      }
-
-      Set<MyHandler> handlers = new HashSet<>();
-
-      for (ClientConsumer consumer : consumers) {
-         MyHandler handler = new MyHandler(threadNum, numMessages);
-
-         consumer.setMessageHandler(handler);
-
-         handlers.add(handler);
-      }
-
-      for (MyHandler handler : handlers) {
-         boolean ok = handler.latch.await(LATCH_WAIT, TimeUnit.MILLISECONDS);
-
-         if (!ok) {
-            throw new Exception("Timed out waiting for messages on handler " + System.identityHashCode(handler) +
-                                   " threadnum " +
-                                   threadNum);
-         }
-
-         if (handler.failure != null) {
-            throw new Exception("Handler failed: " + handler.failure);
-         }
-      }
-
-      sessSend.close();
-
-      for (ClientSession session : sessions) {
-         session.close();
-      }
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of(threadNum + "sub" + i);
-
-         s.deleteQueue(subName);
-      }
-
-      s.close();
-
-      long end = System.currentTimeMillis();
-
-      logger.debug("duration {}", (end - start));
-
-   }
-
-   protected void doTestC(final ClientSessionFactory sf, final int threadNum) throws Exception {
-      long start = System.currentTimeMillis();
-
-      ClientSession s = sf.createSession(false, false, false);
-
-      final int numMessages = 100;
-
-      final int numSessions = 10;
-
-      Set<ClientConsumer> consumers = new HashSet<>();
-      Set<ClientSession> sessions = new HashSet<>();
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of(threadNum + "sub" + i);
-
-         ClientSession sessConsume = createTransactionalSession(sf);
-
-         sessConsume.start();
-
-         sessConsume.createQueue(QueueConfiguration.of(subName).setAddress(ADDRESS).setDurable(false));
-
-         ClientConsumer consumer = sessConsume.createConsumer(subName);
-
-         consumers.add(consumer);
-
-         sessions.add(sessConsume);
-      }
-
-      ClientSession sessSend = sf.createSession(false, false, false);
-
-      ClientProducer producer = sessSend.createProducer(ADDRESS);
-
-      sendMessages(sessSend, producer, numMessages, threadNum);
-
-      sessSend.rollback();
-
-      sendMessages(sessSend, producer, numMessages, threadNum);
-
-      sessSend.commit();
-
-      Set<MyHandler> handlers = new HashSet<>();
-
-      for (ClientConsumer consumer : consumers) {
-         MyHandler handler = new MyHandler(threadNum, numMessages);
-
-         consumer.setMessageHandler(handler);
-
-         handlers.add(handler);
-      }
-
-      for (MyHandler handler : handlers) {
-         boolean ok = handler.latch.await(LATCH_WAIT, TimeUnit.MILLISECONDS);
-
-         if (!ok) {
-            throw new Exception("Timed out waiting for messages on handler " + System.identityHashCode(handler) +
-                                   " threadnum " +
-                                   threadNum);
-         }
-
-         if (handler.failure != null) {
-            throw new Exception("Handler failed: " + handler.failure);
-         }
-
-         handler.reset();
-      }
-
-      for (ClientSession session : sessions) {
-         session.rollback();
-      }
-
-      for (MyHandler handler : handlers) {
-         boolean ok = handler.latch.await(LATCH_WAIT, TimeUnit.MILLISECONDS);
-
-         assertTrue(ok);
-      }
-
-      for (ClientSession session : sessions) {
-         session.commit();
-      }
-
-      sessSend.close();
-      for (ClientSession session : sessions) {
-         session.close();
-      }
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of(threadNum + "sub" + i);
-
-         s.deleteQueue(subName);
-      }
-
-      s.close();
-
-      long end = System.currentTimeMillis();
-
-      logger.debug("duration {}", (end - start));
-   }
-
-   protected void doTestD(final ClientSessionFactory sf, final int threadNum) throws Exception {
-      long start = System.currentTimeMillis();
-
-      ClientSession s = sf.createSession(false, false, false);
-
-      final int numMessages = 100;
-
-      final int numSessions = 10;
-
-      Set<ClientConsumer> consumers = new HashSet<>();
-      Set<ClientSession> sessions = new HashSet<>();
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of(threadNum + " sub" + i);
-
-         ClientSession sessConsume = sf.createSession(false, false, false);
-
-         sessConsume.createQueue(QueueConfiguration.of(subName).setAddress(ADDRESS).setDurable(false));
-
-         ClientConsumer consumer = sessConsume.createConsumer(subName);
-
-         consumers.add(consumer);
-
-         sessions.add(sessConsume);
-      }
-
-      ClientSession sessSend = sf.createSession(false, false, false);
-
-      ClientProducer producer = sessSend.createProducer(ADDRESS);
-
-      sendMessages(sessSend, producer, numMessages, threadNum);
-
-      sessSend.rollback();
-
-      sendMessages(sessSend, producer, numMessages, threadNum);
-
-      sessSend.commit();
-
-      for (ClientSession session : sessions) {
-         session.start();
-      }
-
-      Set<MyHandler> handlers = new HashSet<>();
-
-      for (ClientConsumer consumer : consumers) {
-         MyHandler handler = new MyHandler(threadNum, numMessages);
-
-         consumer.setMessageHandler(handler);
-
-         handlers.add(handler);
-      }
-
-      for (MyHandler handler : handlers) {
-         boolean ok = handler.latch.await(LATCH_WAIT, TimeUnit.MILLISECONDS);
-
-         if (!ok) {
-            throw new Exception("Timed out waiting for messages on handler " + System.identityHashCode(handler) +
-                                   " threadnum " +
-                                   threadNum);
-         }
-
-         if (handler.failure != null) {
-            throw new Exception("Handler failed: " + handler.failure);
-         }
-      }
-
-      handlers.clear();
-
-      // Set handlers to null
-      for (ClientConsumer consumer : consumers) {
-         consumer.setMessageHandler(null);
-      }
-
-      for (ClientSession session : sessions) {
-         session.rollback();
-      }
-
-      // New handlers
-      for (ClientConsumer consumer : consumers) {
-         MyHandler handler = new MyHandler(threadNum, numMessages);
-
-         consumer.setMessageHandler(handler);
-
-         handlers.add(handler);
-      }
-
-      for (MyHandler handler : handlers) {
-         boolean ok = handler.latch.await(LATCH_WAIT, TimeUnit.MILLISECONDS);
-
-         if (!ok) {
-            throw new Exception("Timed out waiting for messages on handler " + System.identityHashCode(handler) +
-                                   " threadnum " +
-                                   threadNum);
-         }
-
-         if (handler.failure != null) {
-            throw new Exception("Handler failed on rollback: " + handler.failure);
-         }
-      }
-
-      for (ClientSession session : sessions) {
-         session.commit();
-      }
-
-      sessSend.close();
-      for (ClientSession session : sessions) {
-         session.close();
-      }
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of(threadNum + " sub" + i);
-
-         s.deleteQueue(subName);
-      }
-
-      s.close();
-
-      long end = System.currentTimeMillis();
-
-      logger.debug("duration {}", (end - start));
-   }
-
-   // Now with synchronous receive()
-
-   protected void doTestE(final ClientSessionFactory sf, final int threadNum) throws Exception {
-      long start = System.currentTimeMillis();
-
-      ClientSession s = sf.createSession(false, false, false);
-
-      final int numMessages = 100;
-
-      final int numSessions = 10;
-
-      Set<ClientConsumer> consumers = new HashSet<>();
-      Set<ClientSession> sessions = new HashSet<>();
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of(threadNum + "sub" + i);
-
-         ClientSession sessConsume = sf.createSession(false, true, true);
-
-         sessConsume.start();
-
-         sessConsume.createQueue(QueueConfiguration.of(subName).setAddress(ADDRESS).setDurable(false));
-
-         ClientConsumer consumer = sessConsume.createConsumer(subName);
-
-         consumers.add(consumer);
-
-         sessions.add(sessConsume);
-      }
-
-      ClientSession sessSend = sf.createSession(false, true, true);
-
-      ClientProducer producer = sessSend.createProducer(ADDRESS);
-
-      sendMessages(sessSend, producer, numMessages, threadNum);
-
-      consumeMessages(consumers, numMessages, threadNum);
-
-      sessSend.close();
-      for (ClientSession session : sessions) {
-         session.close();
-      }
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of(threadNum + "sub" + i);
-
-         s.deleteQueue(subName);
-      }
-
-      s.close();
-
-      long end = System.currentTimeMillis();
-
-      logger.debug("duration {}", (end - start));
-   }
-
-   protected void doTestF(final ClientSessionFactory sf, final int threadNum) throws Exception {
-      long start = System.currentTimeMillis();
-
-      ClientSession s = sf.createSession(false, false, false);
-
-      final int numMessages = 100;
-
-      final int numSessions = 10;
-
-      Set<ClientConsumer> consumers = new HashSet<>();
-      Set<ClientSession> sessions = new HashSet<>();
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of(threadNum + "sub" + i);
-
-         ClientSession sessConsume = sf.createSession(false, true, true);
-
-         sessConsume.createQueue(QueueConfiguration.of(subName).setAddress(ADDRESS).setDurable(false));
-
-         ClientConsumer consumer = sessConsume.createConsumer(subName);
-
-         consumers.add(consumer);
-
-         sessions.add(sessConsume);
-      }
-
-      ClientSession sessSend = sf.createSession(false, true, true);
-
-      ClientProducer producer = sessSend.createProducer(ADDRESS);
-
-      sendMessages(sessSend, producer, numMessages, threadNum);
-
-      for (ClientSession session : sessions) {
-         session.start();
-      }
-
-      consumeMessages(consumers, numMessages, threadNum);
-
-      sessSend.close();
-      for (ClientSession session : sessions) {
-         session.close();
-      }
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of(threadNum + "sub" + i);
-
-         s.deleteQueue(subName);
-      }
-
-      s.close();
-
-      long end = System.currentTimeMillis();
-
-      logger.debug("duration {}", (end - start));
-   }
-
-   protected void doTestG(final ClientSessionFactory sf, final int threadNum) throws Exception {
-      long start = System.currentTimeMillis();
-
-      ClientSession s = sf.createSession(false, false, false);
-
-      final int numMessages = 100;
-
-      final int numSessions = 10;
-
-      Set<ClientConsumer> consumers = new HashSet<>();
-      Set<ClientSession> sessions = new HashSet<>();
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of(threadNum + "sub" + i);
-
-         ClientSession sessConsume = sf.createSession(false, false, false);
-
-         sessConsume.start();
-
-         sessConsume.createQueue(QueueConfiguration.of(subName).setAddress(ADDRESS).setDurable(false));
-
-         ClientConsumer consumer = sessConsume.createConsumer(subName);
-
-         consumers.add(consumer);
-
-         sessions.add(sessConsume);
-      }
-
-      ClientSession sessSend = sf.createSession(false, false, false);
-
-      ClientProducer producer = sessSend.createProducer(ADDRESS);
-
-      sendMessages(sessSend, producer, numMessages, threadNum);
-
-      sessSend.rollback();
-
-      sendMessages(sessSend, producer, numMessages, threadNum);
-
-      sessSend.commit();
-
-      consumeMessages(consumers, numMessages, threadNum);
-
-      for (ClientSession session : sessions) {
-         session.rollback();
-      }
-
-      consumeMessages(consumers, numMessages, threadNum);
-
-      for (ClientSession session : sessions) {
-         session.commit();
-      }
-
-      sessSend.close();
-      for (ClientSession session : sessions) {
-         session.close();
-      }
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of(threadNum + "sub" + i);
-
-         s.deleteQueue(subName);
-      }
-
-      s.close();
-
-      long end = System.currentTimeMillis();
-
-      logger.debug("duration {}", (end - start));
-   }
-
-   protected void doTestH(final ClientSessionFactory sf, final int threadNum) throws Exception {
-      long start = System.currentTimeMillis();
-
-      ClientSession s = sf.createSession(false, false, false);
-
-      final int numMessages = 100;
-
-      final int numSessions = 10;
-
-      Set<ClientConsumer> consumers = new HashSet<>();
-      Set<ClientSession> sessions = new HashSet<>();
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of(threadNum + "sub" + i);
-
-         ClientSession sessConsume = sf.createSession(false, false, false);
-
-         sessConsume.createQueue(QueueConfiguration.of(subName).setAddress(ADDRESS).setDurable(false));
-
-         ClientConsumer consumer = sessConsume.createConsumer(subName);
-
-         consumers.add(consumer);
-
-         sessions.add(sessConsume);
-      }
-
-      ClientSession sessSend = sf.createSession(false, false, false);
-
-      ClientProducer producer = sessSend.createProducer(ADDRESS);
-
-      sendMessages(sessSend, producer, numMessages, threadNum);
-
-      sessSend.rollback();
-
-      sendMessages(sessSend, producer, numMessages, threadNum);
-
-      sessSend.commit();
-
-      for (ClientSession session : sessions) {
-         session.start();
-      }
-
-      consumeMessages(consumers, numMessages, threadNum);
-
-      for (ClientSession session : sessions) {
-         session.rollback();
-      }
-
-      consumeMessages(consumers, numMessages, threadNum);
-
-      for (ClientSession session : sessions) {
-         session.commit();
-      }
-
-      sessSend.close();
-      for (ClientSession session : sessions) {
-         session.close();
-      }
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of(threadNum + "sub" + i);
-
-         s.deleteQueue(subName);
-      }
-
-      s.close();
-
-      long end = System.currentTimeMillis();
-
-      logger.debug("duration {}", (end - start));
-   }
-
-   protected void doTestI(final ClientSessionFactory sf, final int threadNum) throws Exception {
-      ClientSession sessCreate = sf.createSession(false, true, true);
-
-      sessCreate.createQueue(QueueConfiguration.of(SimpleString.of(threadNum + ADDRESS.toString())).setAddress(ADDRESS).setDurable(false));
-
-      ClientSession sess = sf.createSession(false, true, true);
-
-      sess.start();
-
-      ClientConsumer consumer = sess.createConsumer(SimpleString.of(threadNum + ADDRESS.toString()));
-
-      ClientProducer producer = sess.createProducer(ADDRESS);
-
-      ClientMessage message = sess.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-      producer.send(message);
-
-      ClientMessage message2 = consumer.receive(MultiThreadRandomReattachTestBase.RECEIVE_TIMEOUT);
-
-      assertNotNull(message2);
-
-      message2.acknowledge();
-
-      sess.close();
-
-      sessCreate.deleteQueue(SimpleString.of(threadNum + ADDRESS.toString()));
-
-      sessCreate.close();
-   }
-
-   protected void doTestJ(final ClientSessionFactory sf, final int threadNum) throws Exception {
-      ClientSession sessCreate = sf.createSession(false, true, true);
-
-      sessCreate.createQueue(QueueConfiguration.of(SimpleString.of(threadNum + ADDRESS.toString())).setAddress(ADDRESS).setDurable(false));
-
-      ClientSession sess = sf.createSession(false, true, true);
-
-      sess.start();
-
-      ClientConsumer consumer = sess.createConsumer(SimpleString.of(threadNum + ADDRESS.toString()));
-
-      ClientProducer producer = sess.createProducer(ADDRESS);
-
-      ClientMessage message = sess.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-      producer.send(message);
-
-      ClientMessage message2 = consumer.receive(MultiThreadRandomReattachTestBase.RECEIVE_TIMEOUT);
-
-      assertNotNull(message2);
-
-      message2.acknowledge();
-
-      sess.close();
-
-      sessCreate.deleteQueue(SimpleString.of(threadNum + ADDRESS.toString()));
-
-      sessCreate.close();
-   }
-
-   protected void doTestK(final ClientSessionFactory sf, final int threadNum) throws Exception {
-      ClientSession s = sf.createSession(false, false, false);
-
-      s.createQueue(QueueConfiguration.of(SimpleString.of(threadNum + ADDRESS.toString())).setAddress(ADDRESS).setDurable(false));
-
-      final int numConsumers = 100;
-
-      for (int i = 0; i < numConsumers; i++) {
-         ClientConsumer consumer = s.createConsumer(SimpleString.of(threadNum + ADDRESS.toString()));
-
-         consumer.close();
-      }
-
-      s.deleteQueue(SimpleString.of(threadNum + ADDRESS.toString()));
-
-      s.close();
-   }
-
-   // This test tests failure during create connection
-   protected void doTestL(final ClientSessionFactory sf) throws Exception {
-      final int numSessions = 100;
-
-      for (int i = 0; i < numSessions; i++) {
-         ClientSession session = sf.createSession(false, false, false);
-
-
-         session.close();
-      }
-   }
-
-   protected void doTestN(final ClientSessionFactory sf, final int threadNum) throws Exception {
-      ClientSession sessCreate = sf.createSession(false, true, true);
-
-      sessCreate.createQueue(QueueConfiguration.of(SimpleString.of(threadNum + ADDRESS.toString())).setAddress(ADDRESS).setDurable(false));
-
-      ClientSession sess = sf.createSession(false, true, true);
-
-      sess.stop();
-
-      sess.start();
-
-      sess.stop();
-
-      ClientConsumer consumer = sess.createConsumer(SimpleString.of(threadNum + ADDRESS.toString()));
-
-      ClientProducer producer = sess.createProducer(ADDRESS);
-
-      ClientMessage message = sess.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-      producer.send(message);
-
-      sess.start();
-
-      ClientMessage message2 = consumer.receive(MultiThreadRandomReattachTestBase.RECEIVE_TIMEOUT);
-
-      assertNotNull(message2);
-
-      message2.acknowledge();
-
-      sess.stop();
-
-      sess.start();
-
-      sess.close();
-
-      sessCreate.deleteQueue(SimpleString.of(threadNum + ADDRESS.toString()));
-
-      sessCreate.close();
-   }
-
-   protected void doTestO(final ClientSessionFactory sf, final int threadNum) throws Exception {
-      ClientSession sessCreate = sf.createSession(false, true, true);
-
-      sessCreate.createQueue(QueueConfiguration.of(SimpleString.of(threadNum + ADDRESS.toString())).setAddress(ADDRESS).setDurable(false));
-
-      ClientSession sess = sf.createSession(false, true, true);
-
-      sess.start();
-
-      ClientConsumer consumer = sess.createConsumer(SimpleString.of(threadNum + ADDRESS.toString()));
-
-      for (int i = 0; i < 100; i++) {
-         assertNull(consumer.receiveImmediate());
-      }
-
-      sess.close();
-
-      sessCreate.deleteQueue(SimpleString.of(threadNum + ADDRESS.toString()));
-
-      sessCreate.close();
-   }
-
-   protected int getLatchWait() {
-      return 60000;
-   }
-
-   protected int getNumThreads() {
-      return 10;
-   }
-
-
-   private void runTestMultipleThreads(final RunnableT runnable,
-                                       final int numThreads,
-                                       final boolean failOnCreateConnection) throws Exception {
-      runTestMultipleThreads(runnable, numThreads, failOnCreateConnection, 250);
-   }
-
-   private void runTestMultipleThreads(final RunnableT runnable,
-                                       final int numThreads,
-                                       final boolean failOnCreateConnection,
-                                       final long failDelay) throws Exception {
-
-      runMultipleThreadsFailoverTest(runnable, numThreads, getNumIterations(), failOnCreateConnection, failDelay);
-   }
-
-   @Override
-   protected ServerLocator createLocator() throws Exception {
-      ServerLocator locator = createInVMNonHALocator().setReconnectAttempts(15).setConfirmationWindowSize(1024 * 1024);
-      return locator;
-   }
-
-   @Override
-   protected void stop() throws Exception {
-      ActiveMQTestBase.stopComponent(server);
-
-      System.gc();
-
-      assertEquals(0, InVMRegistry.instance.size());
-   }
-
-   private void sendMessages(final ClientSession sessSend,
-                             final ClientProducer producer,
-                             final int numMessages,
-                             final int threadNum) throws Exception {
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = sessSend.createMessage(ActiveMQBytesMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-         message.putIntProperty(SimpleString.of("threadnum"), threadNum);
-         message.putIntProperty(SimpleString.of("count"), i);
-         setBody(message);
-         producer.send(message);
-      }
-   }
-
-   private void consumeMessages(final Set<ClientConsumer> consumers,
-                                final int numMessages,
-                                final int threadNum) throws Exception {
-      // We make sure the messages arrive in the order they were sent from a particular producer
-      Map<ClientConsumer, Map<Integer, Integer>> counts = new HashMap<>();
-
-      for (int i = 0; i < numMessages; i++) {
-         for (ClientConsumer consumer : consumers) {
-            Map<Integer, Integer> consumerCounts = counts.get(consumer);
-
-            if (consumerCounts == null) {
-               consumerCounts = new HashMap<>();
-               counts.put(consumer, consumerCounts);
-            }
-
-            ClientMessage msg = consumer.receive(MultiThreadRandomReattachTestBase.RECEIVE_TIMEOUT);
-
-            assertNotNull(msg);
-
-            int tn = (Integer) msg.getObjectProperty(SimpleString.of("threadnum"));
-            int cnt = (Integer) msg.getObjectProperty(SimpleString.of("count"));
-
-            Integer c = consumerCounts.get(tn);
-            if (c == null) {
-               c = cnt;
-            }
-
-            if (tn == threadNum && cnt != c) {
-               throw new Exception("Invalid count, expected " + tn + ": " + c + " got " + cnt);
-            }
-
-            c++;
-
-            // Wrap
-            if (c == numMessages) {
-               c = 0;
-            }
-
-            consumerCounts.put(tn, c);
-
-            msg.acknowledge();
-         }
-      }
-   }
-
-
-   private class MyHandler implements MessageHandler {
-
-      CountDownLatch latch = new CountDownLatch(1);
-
-      private final Map<Integer, Integer> counts = new HashMap<>();
-
-      volatile String failure;
-
-      final int tn;
-
-      final int numMessages;
-
-      volatile boolean done;
-
-      synchronized void reset() {
-         counts.clear();
-
-         done = false;
-
-         failure = null;
-
-         latch = new CountDownLatch(1);
-      }
-
-      MyHandler(final int threadNum, final int numMessages) {
-         tn = threadNum;
-
-         this.numMessages = numMessages;
-      }
-
-      @Override
-      public synchronized void onMessage(final ClientMessage message) {
-         try {
-            message.acknowledge();
-         } catch (ActiveMQException me) {
-            logger.error("Failed to process", me);
-         }
-
-         if (done) {
-            return;
-         }
-
-         int threadNum = (Integer) message.getObjectProperty(SimpleString.of("threadnum"));
-         int cnt = (Integer) message.getObjectProperty(SimpleString.of("count"));
-
-         Integer c = counts.get(threadNum);
-         if (c == null) {
-            c = cnt;
-         }
-
-         if (tn == threadNum && cnt != c) {
-            failure = "Invalid count, expected " + threadNum + ":" + c + " got " + cnt;
-            logger.error(failure);
-
-            latch.countDown();
-         }
-
-         if (!checkSize(message)) {
-            failure = "Invalid size on message";
-            logger.error(failure);
-            latch.countDown();
-         }
-
-         if (tn == threadNum && c == numMessages - 1) {
-            done = true;
-            latch.countDown();
-         }
-
-         c++;
-         // Wrap around at numMessages
-         if (c == numMessages) {
-            c = 0;
-         }
-
-         counts.put(threadNum, c);
-
-      }
-   }
-}
diff --git a/tests/artemis-test-support/src/main/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/MultiThreadReattachSupportTestBase.java b/tests/artemis-test-support/src/main/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/MultiThreadReattachSupportTestBase.java
deleted file mode 100644
index 8076224082..0000000000
--- a/tests/artemis-test-support/src/main/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/MultiThreadReattachSupportTestBase.java
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.activemq.artemis.tests.integration.cluster.reattach;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.fail;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Timer;
-import java.util.TimerTask;
-
-import org.apache.activemq.artemis.api.core.ActiveMQIllegalStateException;
-import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException;
-import org.apache.activemq.artemis.api.core.ActiveMQUnBlockedException;
-import org.apache.activemq.artemis.api.core.client.ClientSession;
-import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
-import org.apache.activemq.artemis.api.core.client.ServerLocator;
-import org.apache.activemq.artemis.core.client.impl.ClientSessionFactoryInternal;
-import org.apache.activemq.artemis.core.client.impl.ClientSessionInternal;
-import org.apache.activemq.artemis.core.protocol.core.impl.RemotingConnectionImpl;
-import org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnector;
-import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import java.lang.invoke.MethodHandles;
-
-public abstract class MultiThreadReattachSupportTestBase extends ActiveMQTestBase {
-
-   private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-   private Timer timer;
-
-   protected abstract void start() throws Exception;
-
-   protected abstract void stop() throws Exception;
-
-   protected abstract ServerLocator createLocator() throws Exception;
-
-   @Override
-   @BeforeEach
-   public void setUp() throws Exception {
-      super.setUp();
-      timer = new Timer();
-   }
-
-   @Override
-   @AfterEach
-   public void tearDown() throws Exception {
-      timer.cancel();
-      timer = null;
-      super.tearDown();
-   }
-
-   protected boolean shouldFail() {
-      return true;
-   }
-
-   protected void runMultipleThreadsFailoverTest(final RunnableT runnable,
-                                                 final int numThreads,
-                                                 final int numIts,
-                                                 final boolean failOnCreateConnection,
-                                                 final long failDelay) throws Exception {
-      for (int its = 0; its < numIts; its++) {
-         logger.debug("Beginning iteration {}", its);
-
-         start();
-
-         final ServerLocator locator = createLocator();
-
-         final ClientSessionFactoryInternal sf = (ClientSessionFactoryInternal) createSessionFactory(locator);
-
-         final ClientSession session = addClientSession(sf.createSession(false, true, true));
-
-         Failer failer = startFailer(failDelay, session, failOnCreateConnection);
-
-         class Runner extends Thread {
-
-            private volatile Throwable throwable;
-
-            private final RunnableT test;
-
-            private final int threadNum;
-
-            Runner(final RunnableT test, final int threadNum) {
-               this.test = test;
-
-               this.threadNum = threadNum;
-            }
-
-            @Override
-            public void run() {
-               boolean repeat = true;
-
-               while (repeat) {
-                  try {
-                     test.run(sf, threadNum);
-                     repeat = false;
-                  } catch (Throwable t) {
-                     if (expectedException(t)) {
-                        logger.info("Got an expected exception {}", t.getMessage(), t);
-
-                        for (int r = 0; r < 15; r++) {
-                           for (int i = 0; i < 10; i++) {
-                              System.err.println("################################ ");
-                           }
-                           try {
-                              Thread.sleep(1000);
-                           } catch (Throwable e) {
-                           }
-                           for (int i = 0; i < 10; i++) {
-                              System.err.println("################################ ");
-                           }
-
-                        }
-                        repeat = false;
-                     } else {
-                        repeat = false;
-                        throwable = t;
-
-                        logger.error("Failed to run test", t);
-
-                        // Case a failure happened here, it should print the Thread dump
-                        // Sending it to System.out, as it would show on the Tests report
-                        System.out.println(ActiveMQTestBase.threadDump(" - fired by MultiThreadRandomReattachTestBase::runTestMultipleThreads (" + t.getLocalizedMessage() + ")"));
-                     }
-                  }
-               }
-            }
-         }
-
-         do {
-            List<Runner> threads = new ArrayList<>();
-
-            for (int i = 0; i < numThreads; i++) {
-               Runner runner = new Runner(runnable, i);
-
-               threads.add(runner);
-
-               runner.start();
-            }
-
-            for (Runner thread : threads) {
-               thread.join();
-
-               if (thread.throwable != null) {
-                  throw new Exception("Exception on thread " + thread, thread.throwable);
-               }
-            }
-
-            runnable.checkFail();
-
-         }
-         while (!failer.isExecuted());
-
-         InVMConnector.resetFailures();
-
-         session.close();
-
-         locator.close();
-
-         assertEquals(0, sf.numSessions());
-
-         assertEquals(0, sf.numConnections());
-
-         sf.close();
-
-         stop();
-      }
-   }
-
-
-   private boolean expectedException(Throwable e) {
-      return e instanceof ActiveMQIllegalStateException && e.getMessage().contains("AMQ229027") ||
-             e instanceof ActiveMQUnBlockedException;
-   }
-
-   private Failer startFailer(final long time, final ClientSession session, final boolean failOnCreateConnection) {
-      Failer failer = new Failer(session, failOnCreateConnection);
-
-      // This is useful for debugging.. just change shouldFail to return false, and Failer will not be executed
-      if (shouldFail()) {
-         timer.schedule(failer, (long) (time * Math.random()), 100);
-      }
-
-      return failer;
-   }
-
-
-   protected abstract class RunnableT extends Thread {
-
-      private volatile String failReason;
-
-      private volatile Throwable throwable;
-
-      public void setFailed(final String reason, final Throwable throwable) {
-         failReason = reason;
-         this.throwable = throwable;
-      }
-
-      public void checkFail() {
-         if (throwable != null) {
-            logger.error("Test failed: {}", failReason, throwable);
-         }
-         if (failReason != null) {
-            fail(failReason);
-         }
-      }
-
-      public abstract void run(ClientSessionFactory sf, int threadNum) throws Exception;
-   }
-
-   private class Failer extends TimerTask {
-
-      private final ClientSession session;
-
-      private boolean executed;
-
-      private final boolean failOnCreateConnection;
-
-      private Failer(final ClientSession session, final boolean failOnCreateConnection) {
-         this.session = session;
-
-         this.failOnCreateConnection = failOnCreateConnection;
-      }
-
-      @Override
-      public synchronized void run() {
-         logger.debug("** Failing connection");
-
-         RemotingConnectionImpl conn = (RemotingConnectionImpl) ((ClientSessionInternal) session).getConnection();
-
-         if (failOnCreateConnection) {
-            InVMConnector.numberOfFailures = 1;
-            InVMConnector.failOnCreateConnection = true;
-         } else {
-            conn.fail(new ActiveMQNotConnectedException("blah"));
-         }
-
-         logger.debug("** Fail complete");
-
-         cancel();
-
-         executed = true;
-      }
-
-      public synchronized boolean isExecuted() {
-         return executed;
-      }
-   }
-
-}
diff --git a/tests/compatibility-tests/src/main/resources/reattachCompatibility/testValidReattach.groovy b/tests/compatibility-tests/src/main/resources/reattachCompatibility/testValidReattach.groovy
new file mode 100644
index 0000000000..c805779d3e
--- /dev/null
+++ b/tests/compatibility-tests/src/main/resources/reattachCompatibility/testValidReattach.groovy
@@ -0,0 +1,189 @@
+package takeoverReattach
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
+import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
+import org.apache.activemq.artemis.api.core.QueueConfiguration
+import org.apache.activemq.artemis.api.core.RoutingType
+import org.apache.activemq.artemis.api.core.SimpleString
+import org.apache.activemq.artemis.api.core.client.ActiveMQClient
+import org.apache.activemq.artemis.api.core.client.ClientConsumer
+import org.apache.activemq.artemis.api.core.client.ClientProducer
+import org.apache.activemq.artemis.api.core.client.ServerLocator
+import org.apache.activemq.artemis.core.client.impl.ClientSessionFactoryInternal
+import org.apache.activemq.artemis.core.client.impl.ClientSessionImpl
+import org.apache.activemq.artemis.core.client.impl.ClientSessionFactoryImpl
+import org.apache.activemq.artemis.core.client.impl.ClientSessionInternal
+import org.apache.activemq.artemis.core.protocol.core.impl.ActiveMQSessionContext
+import org.apache.activemq.artemis.core.protocol.core.impl.RemotingConnectionImpl
+import java.lang.reflect.Constructor
+import java.lang.reflect.Field
+
+// Create target session with "admin" user (full permissions)
+ServerLocator locator = ActiveMQClient.createServerLocator("tcp://localhost:61616")
+locator.setRetryInterval(100)
+      .setRetryIntervalMultiplier(1)
+      .setReconnectAttempts(-1)
+      .setConfirmationWindowSize(1024 * 1024)
+
+ClientSessionFactoryInternal sfTarget = locator.createSessionFactory()
+ClientSessionImpl targetSession = sfTarget.createSession("admin", "admin", false, true, true, false, 0)
+
+SimpleString address = SimpleString.of("TakeoverTestAddress")
+targetSession.createQueue(QueueConfiguration.of(address).setDurable(false))
+
+// Get session info for reattachment attempt
+String targetSessionName = targetSession.getName()
+int targetSessionChannel = targetSession.getSessionContext().getSessionChannel().getID()
+int version = targetSession.getSessionContext().getServerVersion()
+
+// Attempt to reattach with "guest" user (limited permissions)
+ServerLocator locator2 = ActiveMQClient.createServerLocator("tcp://localhost:61616")
+locator2.setRetryInterval(100)
+       .setRetryIntervalMultiplier(1)
+       .setReconnectAttempts(-1)
+       .setConfirmationWindowSize(1024 * 1024)
+
+ClientSessionFactoryInternal sfReattaching = locator2.createSessionFactory()
+RemotingConnectionImpl connection = sfReattaching.getConnection()
+
+// Create a reattach session using the target session's info but with "guest" credentials
+ActiveMQSessionContext context = new ActiveMQSessionContext(
+    targetSessionName,
+    sfReattaching.getConnection(),
+    connection.getChannel(targetSessionChannel, 1024 * 1024),
+    version,
+    1024 * 1024
+)
+
+// Using reflection to introduce a fake session into the ClientSessionFactory::sessions
+Constructor<ClientSessionImpl> constructor = ClientSessionImpl.class.getDeclaredConstructor(
+    ClientSessionFactoryInternal.class,
+    String.class,  // name
+    String.class,  // username
+    String.class,  // password
+    boolean.class, // xa
+    boolean.class, // autoCommitSends
+    boolean.class, // autoCommitAcks
+    boolean.class, // preAcknowledge
+    boolean.class, // blockOnAcknowledge
+    boolean.class, // autoGroup
+    int.class,     // ackBatchSize
+    int.class,     // consumerWindowSize
+    int.class,     // consumerMaxRate
+    int.class,     // confirmationWindowSize
+    int.class,     // producerWindowSize
+    int.class,     // producerMaxRate
+    boolean.class, // blockOnNonDurableSend
+    boolean.class, // blockOnDurableSend
+    boolean.class, // cacheLargeMessageClient
+    int.class,     // minLargeMessageSize
+    boolean.class, // compressLargeMessages
+    int.class,     // compressionLevel
+    int.class,     // initialMessagePacketSize
+    String.class,  // groupID
+    int.class,     // onMessageCloseTimeout
+    org.apache.activemq.artemis.spi.core.remoting.SessionContext.class, // sessionContext
+    java.util.concurrent.Executor.class, // executor
+    java.util.concurrent.Executor.class, // confirmationExecutor
+    java.util.concurrent.Executor.class, // flowControlExecutor
+    java.util.concurrent.Executor.class  // closeExecutor
+)
+constructor.setAccessible(true)
+
+ClientSessionInternal reattachSession = constructor.newInstance(
+    sfReattaching,
+    targetSessionName,
+    "guest",
+    "guest",
+    false, false, false, false, false, false,
+    1, 1024, -1, -1, -1, -1,
+    false, false, false,
+    100 * 1024,
+    false,
+    1, 1024,
+    null,
+    60000,
+    context,
+    null, null, null, null
+)
+
+// Add session to factory's sessions list using reflection
+ClientSessionFactoryImpl factoryImpl = (ClientSessionFactoryImpl) sfReattaching
+Field sessionsField = ClientSessionFactoryImpl.class.getDeclaredField("sessions")
+sessionsField.setAccessible(true)
+def sessions = sessionsField.get(factoryImpl)
+synchronized (sessions) {
+    sessions.add(reattachSession)
+}
+
+// Fail the connection to trigger reattachment
+connection.fail(new ActiveMQNotConnectedException())
+
+// Waiting half second to make sure reconnect had a chance to happen
+Thread.sleep(500)
+
+String nonExistingQueue = "iDontExist"
+
+// The session should not have permissions to create queues.
+// if this succeeds it means the session is using wrong credentials
+boolean securityExceptionThrown = false
+try {
+    reattachSession.createAddress(SimpleString.of(nonExistingQueue), RoutingType.ANYCAST, true)
+    reattachSession.createQueue(QueueConfiguration.of(nonExistingQueue)
+        .setAddress(nonExistingQueue)
+        .setRoutingType(RoutingType.ANYCAST)
+        .setDurable(true))
+} catch (ActiveMQSecurityException e) {
+    securityExceptionThrown = true
+} catch (Exception e) {
+    println "Unexpected exception: " + e.getMessage()
+    e.printStackTrace()
+    throw e
+}
+
+// Verify the session still works for permitted operations
+ClientProducer producer = reattachSession.createProducer(address)
+producer.send(reattachSession.createMessage(false))
+reattachSession.commit()
+
+ClientConsumer consumer = reattachSession.createConsumer(address)
+reattachSession.start()
+def receivedMessage = consumer.receive(5000)
+boolean messageReceived = receivedMessage != null
+reattachSession.commit()
+
+targetSession.close()
+sfTarget.close()
+locator.close()
+
+try {
+    reattachSession.close()
+} catch (Exception ignored) {
+}
+try {
+    sfReattaching.close()
+} catch (Exception ignored) {
+}
+try {
+    locator2.close()
+} catch (Exception ignored) {
+}
+
+// return good test conditions
+return securityExceptionThrown && messageReceived
diff --git a/tests/compatibility-tests/src/test/java/org/apache/activemq/artemis/tests/compatibility/ValidateReattachCompatibilityTest.java b/tests/compatibility-tests/src/test/java/org/apache/activemq/artemis/tests/compatibility/ValidateReattachCompatibilityTest.java
new file mode 100644
index 0000000000..9db3fa8068
--- /dev/null
+++ b/tests/compatibility-tests/src/test/java/org/apache/activemq/artemis/tests/compatibility/ValidateReattachCompatibilityTest.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.activemq.artemis.tests.compatibility;
+
+import static org.apache.activemq.artemis.tests.compatibility.GroovyRun.ARTEMIS_2_44_0;
+import static org.apache.activemq.artemis.tests.compatibility.GroovyRun.SNAPSHOT;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl;
+import org.apache.activemq.artemis.core.security.Role;
+import org.apache.activemq.artemis.core.server.ActiveMQServer;
+import org.apache.activemq.artemis.core.server.ActiveMQServers;
+import org.apache.activemq.artemis.core.server.JournalType;
+import org.apache.activemq.artemis.core.settings.HierarchicalRepository;
+import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
+import org.apache.activemq.artemis.tests.compatibility.base.ClasspathBase;
+import org.apache.activemq.artemis.tests.extensions.parameterized.ParameterizedTestExtension;
+import org.apache.activemq.artemis.tests.extensions.parameterized.Parameters;
+import org.apache.activemq.artemis.utils.FileUtil;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.TestTemplate;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+@ExtendWith(ParameterizedTestExtension.class)
+public class ValidateReattachCompatibilityTest extends ClasspathBase {
+
+   ClassLoader senderClassloader;
+
+   ActiveMQServer server;
+
+   @BeforeEach
+   protected void setup() throws Exception {
+
+      ConfigurationImpl configuration = new ConfigurationImpl();
+      configuration.setJournalType(JournalType.NIO);
+      configuration.addAcceptorConfiguration("artemis", "tcp://0.0.0.0:61616");
+      configuration.setSecurityEnabled(true);
+      configuration.setPersistenceEnabled(false);
+      server = ActiveMQServers.newActiveMQServer(configuration, false);
+
+
+      ActiveMQJAASSecurityManager securityManager = (ActiveMQJAASSecurityManager) server.getSecurityManager();
+
+      // Add users
+      securityManager.getConfiguration().addUser("guest", "guest");
+      securityManager.getConfiguration().addRole("guest", "limited");
+      securityManager.getConfiguration().addUser("admin", "admin");
+      securityManager.getConfiguration().addRole("admin", "full");
+
+      // Configure roles
+      HierarchicalRepository<Set<Role>> securityRepository = server.getSecurityRepository();
+      Set<Role> roles = new HashSet<>();
+
+      // "limited" role: can only send and consume, cannot create queues
+      roles.add(new Role("limited", true, true, false, false, false, false, false, false, false, false, false, false));
+
+      // "full" role: can do everything
+      roles.add(new Role("full", true, true, true, true, true, true, true, true, true, true, false, false));
+
+      securityRepository.addMatch("#", roles);
+
+      server.getConfiguration().setSecurityEnabled(true);
+
+      server.start();
+   }
+
+   @AfterEach
+   protected void teardown() throws Exception {
+      server.stop();
+   }
+
+   @Parameters(name = "client={0}")
+   public static Collection getParameters() {
+      List<Object[]> combinations = new ArrayList<>();
+      combinations.add(new Object[]{ARTEMIS_2_44_0});
+      combinations.add(new Object[]{SNAPSHOT});
+      return combinations;
+   }
+
+   public ValidateReattachCompatibilityTest(String sender) throws Exception {
+      this.senderClassloader = getClasspath(sender, false);
+      clearGroovy(senderClassloader);
+   }
+
+   @BeforeEach
+   public void setUp() throws Throwable {
+      FileUtil.deleteDirectory(serverFolder);
+   }
+
+   @TestTemplate
+   public void testValidateExceptionThrown() throws Throwable {
+      Boolean testOkay = (Boolean) evaluate(senderClassloader, "reattachCompatibility/testValidReattach.groovy");
+
+      assertTrue(testOkay);
+      assertNull(server.locateQueue("iDontExist"));
+   }
+}
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/core/client/impl/ClientSessionImplAccessor.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/core/client/impl/ClientSessionImplAccessor.java
new file mode 100644
index 0000000000..70fa76abb5
--- /dev/null
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/core/client/impl/ClientSessionImplAccessor.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.core.client.impl;
+
+import java.lang.reflect.Field;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+import org.apache.activemq.artemis.api.core.ActiveMQException;
+import org.apache.activemq.artemis.spi.core.remoting.SessionContext;
+
+public class ClientSessionImplAccessor {
+
+   public static void setName(ClientSessionImpl session, String name) {
+      try {
+         Field nameField = ClientSessionImpl.class.getDeclaredField("name");
+         nameField.setAccessible(true);
+         nameField.set(session, name);
+      } catch (Exception e) {
+         throw new RuntimeException("Failed to set name field", e);
+      }
+   }
+
+   public static ClientSessionImpl createSession(ClientSessionFactoryInternal sessionFactory,
+                                                   String name,
+                                                   String username,
+                                                   String password,
+                                                   boolean xa,
+                                                   boolean autoCommitSends,
+                                                   boolean autoCommitAcks,
+                                                   boolean preAcknowledge,
+                                                   boolean blockOnAcknowledge,
+                                                   boolean autoGroup,
+                                                   int ackBatchSize,
+                                                   int consumerWindowSize,
+                                                   int consumerMaxRate,
+                                                   int confirmationWindowSize,
+                                                   int producerWindowSize,
+                                                   int producerMaxRate,
+                                                   boolean blockOnNonDurableSend,
+                                                   boolean blockOnDurableSend,
+                                                   boolean cacheLargeMessageClient,
+                                                   int minLargeMessageSize,
+                                                   boolean compressLargeMessages,
+                                                   int compressionLevel,
+                                                   int initialMessagePacketSize,
+                                                   String groupID,
+                                                   int onMessageCloseTimeout,
+                                                   SessionContext sessionContext,
+                                                   Executor executor,
+                                                   Executor confirmationExecutor,
+                                                   Executor flowControlExecutor,
+                                                   Executor closeExecutor) throws ActiveMQException {
+
+      ClientSessionImpl session = new ClientSessionImpl(sessionFactory, name, username, password, xa, autoCommitSends,
+                                    autoCommitAcks, preAcknowledge, blockOnAcknowledge, autoGroup,
+                                    ackBatchSize, consumerWindowSize, consumerMaxRate, confirmationWindowSize,
+                                    producerWindowSize, producerMaxRate, blockOnNonDurableSend, blockOnDurableSend,
+                                    cacheLargeMessageClient, minLargeMessageSize, compressLargeMessages,
+                                    compressionLevel, initialMessagePacketSize, groupID, onMessageCloseTimeout,
+                                    sessionContext, executor, confirmationExecutor, flowControlExecutor, closeExecutor);
+
+      try {
+         ClientSessionFactoryImpl factoryImpl = (ClientSessionFactoryImpl) sessionFactory;
+         Field sessionsField = ClientSessionFactoryImpl.class.getDeclaredField("sessions");
+         sessionsField.setAccessible(true);
+         @SuppressWarnings("unchecked")
+         Set<ClientSessionInternal> sessions = (Set<ClientSessionInternal>) sessionsField.get(factoryImpl);
+         synchronized (sessions) {
+            sessions.add(session);
+         }
+      } catch (Exception e) {
+         throw new RuntimeException("Failed to access sessions field", e);
+      }
+
+      return session;
+   }
+
+   public static Executor getExecutor(ClientSessionImpl session) {
+      try {
+         Field executorField = ClientSessionImpl.class.getDeclaredField("executor");
+         executorField.setAccessible(true);
+         return (Executor) executorField.get(session);
+      } catch (Exception e) {
+         throw new RuntimeException("Failed to get executor field", e);
+      }
+   }
+
+   public static Executor getConfirmationExecutor(ClientSessionImpl session) {
+      try {
+         Field confirmationExecutorField = ClientSessionImpl.class.getDeclaredField("confirmationExecutor");
+         confirmationExecutorField.setAccessible(true);
+         return (Executor) confirmationExecutorField.get(session);
+      } catch (Exception e) {
+         throw new RuntimeException("Failed to get confirmationExecutor field", e);
+      }
+   }
+
+   public static Executor getFlowControlExecutor(ClientSessionImpl session) {
+      try {
+         Field flowControlExecutorField = ClientSessionImpl.class.getDeclaredField("flowControlExecutor");
+         flowControlExecutorField.setAccessible(true);
+         return (Executor) flowControlExecutorField.get(session);
+      } catch (Exception e) {
+         throw new RuntimeException("Failed to get flowControlExecutor field", e);
+      }
+   }
+
+   public static Executor getCloseExecutor(ClientSessionImpl session) {
+      try {
+         Field closeExecutorField = ClientSessionImpl.class.getDeclaredField("closeExecutor");
+         closeExecutorField.setAccessible(true);
+         return (Executor) closeExecutorField.get(session);
+      } catch (Exception e) {
+         throw new RuntimeException("Failed to get closeExecutor field", e);
+      }
+   }
+
+}
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/client/TemporaryQueueTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/client/TemporaryQueueTest.java
index b75ea4fba1..3815ec197c 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/client/TemporaryQueueTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/client/TemporaryQueueTest.java
@@ -72,7 +72,7 @@ public class TemporaryQueueTest extends SingleServerTestBase {
 
    @Override
    protected ActiveMQServer createServer() throws Exception {
-      ActiveMQServer server = super.createServer();
+      ActiveMQServer server = createServer(false, true);
       server.getConfiguration().setAddressQueueScanPeriod(100);
       return server;
    }
@@ -327,10 +327,11 @@ public class TemporaryQueueTest extends SingleServerTestBase {
 
    @Test
    public void testRecreateConsumerOverServerFailure() throws Exception {
-      ServerLocator serverWithReattach = createInVMNonHALocator().setReconnectAttempts(30).setRetryInterval(1000).setConfirmationWindowSize(-1).setConnectionTTL(TemporaryQueueTest.CONNECTION_TTL).setClientFailureCheckPeriod(TemporaryQueueTest.CONNECTION_TTL / 3);
-      ClientSessionFactory reattachSF = createSessionFactory(serverWithReattach);
+      // This test has to use Netty to make sure the original connection is closed during failover.
+      ServerLocator locatorWithFailover = createNettyNonHALocator().setReconnectAttempts(200).setRetryInterval(100).setConfirmationWindowSize(-1).setConnectionTTL(TemporaryQueueTest.CONNECTION_TTL).setClientFailureCheckPeriod(TemporaryQueueTest.CONNECTION_TTL / 3);
+      ClientSessionFactory sf = createSessionFactory(locatorWithFailover);
 
-      ClientSession session = reattachSF.createSession(false, false);
+      ClientSession session = sf.createSession(false, false);
       session.createQueue(QueueConfiguration.of("tmpQ").setAddress("tmpAd").setDurable(false).setTemporary(true));
       ClientConsumer consumer = session.createConsumer("tmpQ");
 
@@ -349,9 +350,9 @@ public class TemporaryQueueTest extends SingleServerTestBase {
 
       session.close();
 
-      reattachSF.close();
+      sf.close();
 
-      serverWithReattach.close();
+      locatorWithFailover.close();
 
    }
 
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/MultiThreadRandomReattachTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/MultiThreadRandomReattachTest.java
deleted file mode 100644
index 0ad7fee283..0000000000
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/MultiThreadRandomReattachTest.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.activemq.artemis.tests.integration.cluster.reattach;
-
-public class MultiThreadRandomReattachTest extends MultiThreadRandomReattachTestBase {
-
-   @Override
-   protected int getNumIterations() {
-      return 2;
-   }
-}
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/NettyMultiThreadRandomReattachTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/NettyMultiThreadRandomReattachTest.java
deleted file mode 100644
index bf5cddc723..0000000000
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/NettyMultiThreadRandomReattachTest.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.activemq.artemis.tests.integration.cluster.reattach;
-
-import org.apache.activemq.artemis.api.core.RoutingType;
-import org.apache.activemq.artemis.api.core.client.ServerLocator;
-import org.apache.activemq.artemis.core.config.Configuration;
-import org.apache.activemq.artemis.core.config.CoreAddressConfiguration;
-
-public class NettyMultiThreadRandomReattachTest extends MultiThreadRandomReattachTest {
-
-   @Override
-   protected void start() throws Exception {
-      Configuration primaryConf = createDefaultNettyConfig();
-      server = createServer(false, primaryConf);
-      server.getConfiguration().getAddressConfigurations().add(new CoreAddressConfiguration().setName(ADDRESS.toString()).addRoutingType(RoutingType.MULTICAST));
-      server.start();
-      waitForServerToStart(server);
-   }
-
-   @Override
-   protected ServerLocator createLocator() throws Exception {
-      return createNettyNonHALocator().setReconnectAttempts(15).setConfirmationWindowSize(1024 * 1024).setAckBatchSize(0);
-   }
-
-}
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/OrderReattachTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/OrderReattachTest.java
deleted file mode 100644
index 578bdd9ce8..0000000000
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/OrderReattachTest.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.activemq.artemis.tests.integration.cluster.reattach;
-
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import java.lang.invoke.MethodHandles;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.LinkedBlockingDeque;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException;
-import org.apache.activemq.artemis.api.core.QueueConfiguration;
-import org.apache.activemq.artemis.api.core.SimpleString;
-import org.apache.activemq.artemis.api.core.client.ClientConsumer;
-import org.apache.activemq.artemis.api.core.client.ClientMessage;
-import org.apache.activemq.artemis.api.core.client.ClientProducer;
-import org.apache.activemq.artemis.api.core.client.ClientSession;
-import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
-import org.apache.activemq.artemis.api.core.client.MessageHandler;
-import org.apache.activemq.artemis.api.core.client.ServerLocator;
-import org.apache.activemq.artemis.core.client.impl.ClientSessionInternal;
-import org.apache.activemq.artemis.core.protocol.core.impl.RemotingConnectionImpl;
-import org.apache.activemq.artemis.core.server.ActiveMQServer;
-import org.apache.activemq.artemis.jms.client.ActiveMQTextMessage;
-import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
-import org.junit.jupiter.api.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class OrderReattachTest extends ActiveMQTestBase {
-
-   private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-   final SimpleString ADDRESS = SimpleString.of("address");
-
-   private ActiveMQServer server;
-
-
-
-   @Test
-   public void testOrderOnSendInVM() throws Throwable {
-      doTestOrderOnSend(false);
-   }
-
-   public void doTestOrderOnSend(final boolean isNetty) throws Throwable {
-      server = createServer(false, isNetty);
-
-      server.start();
-      ServerLocator locator = createFactory(isNetty).setReconnectAttempts(15).setConfirmationWindowSize(1024 * 1024).setBlockOnNonDurableSend(false).setBlockOnAcknowledge(false);
-
-      ClientSessionFactory sf = createSessionFactory(locator);
-
-      final ClientSession session = sf.createSession(false, true, true);
-
-      final LinkedBlockingDeque<Boolean> failureQueue = new LinkedBlockingDeque<>();
-
-      final CountDownLatch ready = new CountDownLatch(1);
-
-      // this test will use a queue. Whenever the test wants a failure.. it can just send TRUE to failureQueue
-      // This Thread will be reading the queue
-      Thread failer = new Thread(() -> {
-         ready.countDown();
-         while (true) {
-            try {
-               Boolean poll = false;
-               try {
-                  poll = failureQueue.poll(60, TimeUnit.SECONDS);
-               } catch (InterruptedException e) {
-                  e.printStackTrace();
-                  break;
-               }
-
-               Thread.sleep(1);
-
-               final RemotingConnectionImpl conn = (RemotingConnectionImpl) ((ClientSessionInternal) session).getConnection();
-
-               // True means... fail session
-               if (poll) {
-                  conn.fail(new ActiveMQNotConnectedException("poop"));
-               } else {
-                  // false means... finish thread
-                  break;
-               }
-            } catch (Exception e) {
-               e.printStackTrace();
-            }
-         }
-      });
-
-      failer.start();
-
-      ready.await();
-
-      try {
-         doSend2(1, sf, failureQueue);
-      } finally {
-         try {
-            session.close();
-         } catch (Exception e) {
-            e.printStackTrace();
-         }
-
-         try {
-            locator.close();
-         } catch (Exception e) {
-            //
-         }
-         try {
-            sf.close();
-         } catch (Exception e) {
-            e.printStackTrace();
-         }
-
-         failureQueue.put(false);
-
-         failer.join();
-      }
-
-   }
-
-   public void doSend2(final int order,
-                       final ClientSessionFactory sf,
-                       final LinkedBlockingDeque<Boolean> failureQueue) throws Exception {
-      ClientSession s = sf.createSession(false, false, false);
-
-      final int numMessages = 500;
-
-      final int numSessions = 10;
-
-      Set<ClientConsumer> consumers = new HashSet<>();
-      Set<ClientSession> sessions = new HashSet<>();
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of("sub" + i);
-
-         // failureQueue.push(true);
-
-         ClientSession sessConsume = sf.createSession(false, true, true);
-
-         sessConsume.createQueue(QueueConfiguration.of(subName).setAddress(ADDRESS).setDurable(false));
-
-         ClientConsumer consumer = sessConsume.createConsumer(subName);
-
-         consumers.add(consumer);
-
-         sessions.add(sessConsume);
-      }
-
-      ClientSession sessSend = sf.createSession(false, true, true);
-
-      ClientProducer producer = sessSend.createProducer(ADDRESS);
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = sessSend.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-
-         if (i % 10 == 0) {
-            // failureQueue.push(true);
-         }
-         message.putIntProperty(SimpleString.of("count"), i);
-         producer.send(message);
-      }
-
-      for (ClientSession session : sessions) {
-         session.start();
-      }
-
-      class MyHandler implements MessageHandler {
-
-         final CountDownLatch latch = new CountDownLatch(1);
-
-         int count;
-
-         Exception failure;
-
-         @Override
-         public void onMessage(final ClientMessage message) {
-            if (count >= numMessages) {
-               failure = new Exception("too many messages");
-               latch.countDown();
-            }
-
-            if (message.getIntProperty("count") != count) {
-               failure = new Exception("counter " + count + " was not as expected (" + message.getIntProperty("count") + ")");
-               logger.warn("Failure on receiving message ", failure);
-               failure.printStackTrace();
-               latch.countDown();
-            }
-
-            count++;
-
-            if (count % 100 == 0) {
-               failureQueue.push(true);
-            }
-
-            if (count == numMessages) {
-               latch.countDown();
-            }
-         }
-      }
-
-      Set<MyHandler> handlers = new HashSet<>();
-
-      for (ClientConsumer consumer : consumers) {
-         MyHandler handler = new MyHandler();
-
-         consumer.setMessageHandler(handler);
-
-         handlers.add(handler);
-      }
-
-      for (MyHandler handler : handlers) {
-         boolean ok = handler.latch.await(60000, TimeUnit.MILLISECONDS);
-
-         assertTrue(ok);
-
-         if (handler.failure != null) {
-            throw handler.failure;
-         }
-      }
-
-      // failureQueue.push(true);
-
-      sessSend.close();
-
-      for (ClientSession session : sessions) {
-         // failureQueue.push(true);
-         session.close();
-      }
-
-      for (int i = 0; i < numSessions; i++) {
-
-         failureQueue.push(true);
-
-         SimpleString subName = SimpleString.of("sub" + i);
-
-         s.deleteQueue(subName);
-      }
-
-      s.close();
-   }
-}
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/RandomReattachIntegrationTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/RandomReattachIntegrationTest.java
deleted file mode 100644
index 3d738697a2..0000000000
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/RandomReattachIntegrationTest.java
+++ /dev/null
@@ -1,1354 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.activemq.artemis.tests.integration.cluster.reattach;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.Timer;
-import java.util.TimerTask;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.activemq.artemis.api.core.ActiveMQException;
-import org.apache.activemq.artemis.api.core.ActiveMQIllegalStateException;
-import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException;
-import org.apache.activemq.artemis.api.core.QueueConfiguration;
-import org.apache.activemq.artemis.api.core.SimpleString;
-import org.apache.activemq.artemis.api.core.client.ClientConsumer;
-import org.apache.activemq.artemis.api.core.client.ClientMessage;
-import org.apache.activemq.artemis.api.core.client.ClientProducer;
-import org.apache.activemq.artemis.api.core.client.ClientSession;
-import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
-import org.apache.activemq.artemis.api.core.client.MessageHandler;
-import org.apache.activemq.artemis.api.core.client.ServerLocator;
-import org.apache.activemq.artemis.core.client.impl.ClientSessionFactoryImpl;
-import org.apache.activemq.artemis.core.client.impl.ClientSessionInternal;
-import org.apache.activemq.artemis.core.remoting.impl.invm.InVMRegistry;
-import org.apache.activemq.artemis.core.server.ActiveMQServer;
-import org.apache.activemq.artemis.core.server.ActiveMQServers;
-import org.apache.activemq.artemis.jms.client.ActiveMQTextMessage;
-import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
-import org.apache.activemq.artemis.utils.Wait;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import java.lang.invoke.MethodHandles;
-
-public final class RandomReattachIntegrationTest extends ActiveMQTestBase {
-
-   private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-
-   private static final int RECEIVE_TIMEOUT = 10000;
-
-
-   private static final SimpleString ADDRESS = SimpleString.of("FailoverTestAddress");
-
-   private ActiveMQServer server;
-
-   private Timer timer;
-
-   private int getNumIterations() {
-      return 2;
-   }
-
-
-   @Test
-   public void testA() throws Exception {
-      runTest(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf) throws Exception {
-            doTestA(sf);
-         }
-      });
-   }
-
-   @Test
-   public void testB() throws Exception {
-      runTest(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf) throws Exception {
-            doTestB(sf);
-         }
-      });
-   }
-
-   @Test
-   public void testC() throws Exception {
-      runTest(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf) throws Exception {
-            doTestC(sf);
-         }
-      });
-   }
-
-   @Test
-   public void testD() throws Exception {
-      runTest(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf) throws Exception {
-            doTestD(sf);
-         }
-      });
-   }
-
-   @Test
-   public void testE() throws Exception {
-      runTest(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf) throws Exception {
-            doTestE(sf);
-         }
-      });
-   }
-
-   @Test
-   public void testF() throws Exception {
-      runTest(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf) throws Exception {
-            doTestF(sf);
-         }
-      });
-   }
-
-   @Test
-   public void testG() throws Exception {
-      runTest(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf) throws Exception {
-            doTestG(sf);
-         }
-      });
-   }
-
-   @Test
-   public void testH() throws Exception {
-      runTest(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf) throws Exception {
-            doTestH(sf);
-         }
-      });
-   }
-
-   @Test
-   public void testI() throws Exception {
-      runTest(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf) throws Exception {
-            doTestI(sf);
-         }
-      });
-   }
-
-   @Test
-   public void testJ() throws Exception {
-      runTest(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf) throws Exception {
-            doTestJ(sf);
-         }
-      });
-   }
-
-   @Test
-   public void testK() throws Exception {
-      runTest(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf) throws Exception {
-            doTestK(sf);
-         }
-      });
-   }
-
-   @Test
-   public void testL() throws Exception {
-      runTest(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf) throws Exception {
-            doTestL(sf);
-         }
-      });
-   }
-
-   @Test
-   public void testN() throws Exception {
-      runTest(new RunnableT() {
-         @Override
-         public void run(final ClientSessionFactory sf) throws Exception {
-            doTestN(sf);
-         }
-      });
-   }
-
-   public void runTest(final RunnableT runnable) throws Exception {
-      int numIts = getNumIterations();
-
-      for (int its = 0; its < numIts; its++) {
-         logger.debug("####{} iteration #{}", getName(), its);
-         start();
-         ServerLocator locator = createInVMNonHALocator().setReconnectAttempts(15).setConfirmationWindowSize(1024 * 1024);
-
-         ClientSessionFactory sf = createSessionFactory(locator);
-
-         ClientSession session = sf.createSession(false, false, false);
-
-         Failer failer = startFailer(250, session);
-
-         try {
-            do {
-               runnable.run(sf);
-            }
-            while (!failer.isExecuted());
-         } catch (Exception e) {
-            if (expectedException(e)) {
-               logger.info("an expected exception was thrown - {}", e.getMessage(), e);
-               // one more time
-               numIts++;
-            } else {
-               throw e;
-            }
-         } finally {
-            try {
-               sf.close();
-               session.close();
-            } catch (Throwable ignored) {
-            }
-            try {
-               locator.close();
-            } catch (Throwable ignored) {
-            }
-
-            try {
-               server.stop();
-            } catch (Throwable ignored) {
-            } finally {
-               server = null;
-            }
-         }
-      }
-   }
-
-   private boolean expectedException(Throwable e) {
-      // during a reattachment it's not possible to know if the previous ack was sent or not.
-      // this is expected and clients should deal with this situation
-      return e instanceof ActiveMQIllegalStateException && e.getMessage().contains("AMQ229027");
-   }
-
-
-
-   protected void doTestA(final ClientSessionFactory sf) throws Exception {
-      long start = System.currentTimeMillis();
-
-      ClientSession s = sf.createSession(false, false, false);
-
-      final int numMessages = 100;
-
-      final int numSessions = 10;
-
-      Set<ClientConsumer> consumers = new HashSet<>();
-      Set<ClientSession> sessions = new HashSet<>();
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of("sub" + i);
-
-         ClientSession sessConsume = sf.createSession(false, true, true);
-
-         sessConsume.start();
-
-         sessConsume.createQueue(QueueConfiguration.of(subName).setAddress(ADDRESS).setDurable(false));
-
-         ClientConsumer consumer = sessConsume.createConsumer(subName);
-
-         consumers.add(consumer);
-
-         sessions.add(sessConsume);
-      }
-
-      ClientSession sessSend = sf.createSession(false, true, true);
-
-      ClientProducer producer = sessSend.createProducer(ADDRESS);
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = sessSend.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-         message.putIntProperty(SimpleString.of("count"), i);
-         producer.send(message);
-      }
-
-      class MyHandler extends AssertionCheckMessageHandler {
-
-         final CountDownLatch latch = new CountDownLatch(1);
-
-         int count;
-
-         @Override
-         public void onMessageAssert(final ClientMessage message) {
-            if (count == numMessages) {
-               fail("Too many messages");
-            }
-
-            assertEquals(count, message.getObjectProperty(SimpleString.of("count")));
-
-            count++;
-
-            try {
-               message.acknowledge();
-            } catch (ActiveMQException me) {
-               logger.error("Failed to process", me);
-            }
-
-            if (count == numMessages) {
-               latch.countDown();
-            }
-         }
-      }
-
-      Set<MyHandler> handlers = new HashSet<>();
-
-      for (ClientConsumer consumer : consumers) {
-         MyHandler handler = new MyHandler();
-
-         consumer.setMessageHandler(handler);
-
-         handlers.add(handler);
-      }
-
-      for (MyHandler handler : handlers) {
-         boolean ok = handler.latch.await(5000, TimeUnit.MILLISECONDS);
-
-         handler.checkAssertions();
-
-         assertTrue(ok, "Didn't receive all messages");
-      }
-
-      sessSend.close();
-      for (ClientSession session : sessions) {
-         session.close();
-      }
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of("sub" + i);
-
-         s.deleteQueue(subName);
-      }
-
-      s.close();
-
-      long end = System.currentTimeMillis();
-
-      logger.debug("duration {}", (end - start));
-   }
-
-   protected void doTestB(final ClientSessionFactory sf) throws Exception {
-      long start = System.currentTimeMillis();
-
-      ClientSession s = sf.createSession(false, false, false);
-
-      final int numMessages = 100;
-
-      final int numSessions = 50;
-
-      Set<ClientConsumer> consumers = new HashSet<>();
-      Set<ClientSession> sessions = new HashSet<>();
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of("sub" + i);
-
-         ClientSession sessConsume = sf.createSession(false, true, true);
-
-         sessConsume.createQueue(QueueConfiguration.of(subName).setAddress(ADDRESS).setDurable(false));
-
-         ClientConsumer consumer = sessConsume.createConsumer(subName);
-
-         consumers.add(consumer);
-
-         sessions.add(sessConsume);
-      }
-
-      ClientSession sessSend = sf.createSession(false, true, true);
-
-      ClientProducer producer = sessSend.createProducer(ADDRESS);
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = sessSend.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-         message.putIntProperty(SimpleString.of("count"), i);
-         producer.send(message);
-      }
-
-      for (ClientSession session : sessions) {
-         session.start();
-      }
-
-      class MyHandler extends AssertionCheckMessageHandler {
-
-         final CountDownLatch latch = new CountDownLatch(1);
-
-         int count;
-
-         @Override
-         public void onMessageAssert(final ClientMessage message) {
-            if (count == numMessages) {
-               fail("Too many messages");
-            }
-
-            assertEquals(count, message.getObjectProperty(SimpleString.of("count")));
-
-            count++;
-
-            if (count == numMessages) {
-               latch.countDown();
-            }
-         }
-      }
-
-      Set<MyHandler> handlers = new HashSet<>();
-
-      for (ClientConsumer consumer : consumers) {
-         MyHandler handler = new MyHandler();
-
-         consumer.setMessageHandler(handler);
-
-         handlers.add(handler);
-      }
-
-      for (MyHandler handler : handlers) {
-         boolean ok = handler.latch.await(10000, TimeUnit.MILLISECONDS);
-
-         handler.checkAssertions();
-
-         assertTrue(ok);
-      }
-
-      sessSend.close();
-
-      for (ClientSession session : sessions) {
-         session.close();
-      }
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of("sub" + i);
-
-         s.deleteQueue(subName);
-      }
-
-      s.close();
-
-      long end = System.currentTimeMillis();
-
-      logger.debug("duration {}", (end - start));
-
-   }
-
-   protected void doTestC(final ClientSessionFactory sf) throws Exception {
-      long start = System.currentTimeMillis();
-
-      ClientSession s = sf.createSession(false, false, false);
-
-      final int numMessages = 100;
-
-      final int numSessions = 1;
-
-      Set<ClientConsumer> consumers = new HashSet<>();
-      Set<ClientSession> sessions = new HashSet<>();
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of("sub" + i);
-
-         ClientSession sessConsume = sf.createSession(false, false, false);
-
-         sessConsume.start();
-
-         sessConsume.createQueue(QueueConfiguration.of(subName).setAddress(ADDRESS).setDurable(false));
-
-         ClientConsumer consumer = sessConsume.createConsumer(subName);
-
-         consumers.add(consumer);
-
-         sessions.add(sessConsume);
-      }
-
-      ClientSession sessSend = sf.createSession(false, false, true);
-
-      ClientProducer producer = sessSend.createProducer(ADDRESS);
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = sessSend.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-         message.putIntProperty(SimpleString.of("count"), i);
-         producer.send(message);
-      }
-
-      sessSend.rollback();
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = sessSend.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-         message.putIntProperty(SimpleString.of("count"), i);
-         producer.send(message);
-      }
-
-      sessSend.commit();
-
-      class MyHandler extends AssertionCheckMessageHandler {
-
-         final CountDownLatch latch = new CountDownLatch(1);
-
-         int count;
-
-         @Override
-         public void onMessageAssert(final ClientMessage message) {
-            if (count == numMessages) {
-               fail("Too many messages, expected " + count);
-            }
-
-            assertEquals(count, message.getObjectProperty(SimpleString.of("count")));
-
-            count++;
-
-            try {
-               message.acknowledge();
-            } catch (ActiveMQException e) {
-               e.printStackTrace();
-               throw new RuntimeException(e.getMessage(), e);
-            }
-
-            if (count == numMessages) {
-               latch.countDown();
-            }
-         }
-      }
-
-      Set<MyHandler> handlers = new HashSet<>();
-
-      for (ClientConsumer consumer : consumers) {
-         MyHandler handler = new MyHandler();
-
-         consumer.setMessageHandler(handler);
-
-         handlers.add(handler);
-      }
-
-      for (MyHandler handler : handlers) {
-         boolean ok = handler.latch.await(10000, TimeUnit.MILLISECONDS);
-
-         assertTrue(ok);
-
-         handler.checkAssertions();
-      }
-
-      handlers.clear();
-
-      // New handlers
-      for (ClientConsumer consumer : consumers) {
-         MyHandler handler = new MyHandler();
-
-         consumer.setMessageHandler(handler);
-
-         handlers.add(handler);
-      }
-
-      for (ClientSession session : sessions) {
-         session.rollback();
-      }
-
-      for (MyHandler handler : handlers) {
-         boolean ok = handler.latch.await(10000, TimeUnit.MILLISECONDS);
-
-         assertTrue(ok);
-
-         handler.checkAssertions();
-      }
-
-      for (ClientSession session : sessions) {
-         session.commit();
-      }
-
-      sessSend.close();
-      for (ClientSession session : sessions) {
-         session.close();
-      }
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of("sub" + i);
-
-         s.deleteQueue(subName);
-      }
-
-      s.close();
-
-      long end = System.currentTimeMillis();
-
-      logger.debug("duration {}", (end - start));
-   }
-
-   protected void doTestD(final ClientSessionFactory sf) throws Exception {
-      long start = System.currentTimeMillis();
-
-      ClientSession s = sf.createSession(false, false, false);
-
-      final int numMessages = 100;
-
-      final int numSessions = 10;
-
-      Set<ClientConsumer> consumers = new HashSet<>();
-      Set<ClientSession> sessions = new HashSet<>();
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of("sub" + i);
-
-         ClientSession sessConsume = sf.createSession(false, false, false);
-
-         sessConsume.createQueue(QueueConfiguration.of(subName).setAddress(ADDRESS).setDurable(false));
-
-         ClientConsumer consumer = sessConsume.createConsumer(subName);
-
-         consumers.add(consumer);
-
-         sessions.add(sessConsume);
-      }
-
-      ClientSession sessSend = sf.createSession(false, false, true);
-
-      ClientProducer producer = sessSend.createProducer(ADDRESS);
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = sessSend.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-         message.putIntProperty(SimpleString.of("count"), i);
-         producer.send(message);
-      }
-
-      sessSend.rollback();
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = sessSend.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-         message.putIntProperty(SimpleString.of("count"), i);
-         producer.send(message);
-      }
-
-      sessSend.commit();
-
-      for (ClientSession session : sessions) {
-         session.start();
-      }
-
-      class MyHandler extends AssertionCheckMessageHandler {
-
-         final CountDownLatch latch = new CountDownLatch(1);
-
-         int count;
-
-         @Override
-         public void onMessageAssert(final ClientMessage message) {
-            if (count == numMessages) {
-               fail("Too many messages, " + count);
-            }
-
-            assertEquals(count, message.getObjectProperty(SimpleString.of("count")));
-
-            count++;
-
-            if (count == numMessages) {
-               latch.countDown();
-            }
-         }
-      }
-
-      Set<MyHandler> handlers = new HashSet<>();
-
-      for (ClientConsumer consumer : consumers) {
-         MyHandler handler = new MyHandler();
-
-         consumer.setMessageHandler(handler);
-
-         handlers.add(handler);
-      }
-
-      for (MyHandler handler : handlers) {
-         boolean ok = handler.latch.await(20000, TimeUnit.MILLISECONDS);
-
-         assertTrue(ok);
-
-         handler.checkAssertions();
-      }
-
-      handlers.clear();
-
-      // New handlers
-      for (ClientConsumer consumer : consumers) {
-         MyHandler handler = new MyHandler();
-
-         consumer.setMessageHandler(handler);
-
-         handlers.add(handler);
-      }
-
-      for (ClientSession session : sessions) {
-         session.rollback();
-      }
-
-      for (MyHandler handler : handlers) {
-         boolean ok = handler.latch.await(10000, TimeUnit.MILLISECONDS);
-
-         assertTrue(ok);
-
-         handler.checkAssertions();
-      }
-
-      for (ClientSession session : sessions) {
-         session.commit();
-      }
-
-      sessSend.close();
-      for (ClientSession session : sessions) {
-         session.close();
-      }
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of("sub" + i);
-
-         s.deleteQueue(subName);
-      }
-
-      s.close();
-
-      long end = System.currentTimeMillis();
-
-      logger.debug("duration {}", (end - start));
-   }
-
-   // Now with synchronous receive()
-
-   protected void doTestE(final ClientSessionFactory sf) throws Exception {
-      long start = System.currentTimeMillis();
-
-      ClientSession s = sf.createSession(false, false, false);
-
-      final int numMessages = 100;
-
-      final int numSessions = 10;
-
-      Set<ClientConsumer> consumers = new HashSet<>();
-      Set<ClientSession> sessions = new HashSet<>();
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of("sub" + i);
-
-         ClientSession sessConsume = sf.createSession(false, true, true);
-
-         sessConsume.start();
-
-         sessConsume.createQueue(QueueConfiguration.of(subName).setAddress(ADDRESS).setDurable(false));
-
-         ClientConsumer consumer = sessConsume.createConsumer(subName);
-
-         consumers.add(consumer);
-
-         sessions.add(sessConsume);
-      }
-
-      ClientSession sessSend = sf.createSession(false, true, true);
-
-      ClientProducer producer = sessSend.createProducer(ADDRESS);
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = sessSend.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-         message.putIntProperty(SimpleString.of("count"), i);
-         producer.send(message);
-      }
-
-      for (int i = 0; i < numMessages; i++) {
-         for (ClientConsumer consumer : consumers) {
-            ClientMessage msg = consumer.receive(RECEIVE_TIMEOUT);
-
-            assertNotNull(msg);
-
-            assertEquals(i, msg.getObjectProperty(SimpleString.of("count")));
-
-            msg.acknowledge();
-         }
-      }
-
-      for (int i = 0; i < numMessages; i++) {
-         for (ClientConsumer consumer : consumers) {
-            ClientMessage msg = consumer.receiveImmediate();
-
-            assertNull(msg);
-         }
-      }
-
-      sessSend.close();
-      for (ClientSession session : sessions) {
-         session.close();
-      }
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of("sub" + i);
-
-         s.deleteQueue(subName);
-      }
-
-      s.close();
-
-      long end = System.currentTimeMillis();
-
-      logger.debug("duration {}", (end - start));
-   }
-
-   protected void doTestF(final ClientSessionFactory sf) throws Exception {
-      long start = System.currentTimeMillis();
-
-      ClientSession s = sf.createSession(false, false, false);
-
-      final int numMessages = 100;
-
-      final int numSessions = 10;
-
-      Set<ClientConsumer> consumers = new HashSet<>();
-      Set<ClientSession> sessions = new HashSet<>();
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of("sub" + i);
-
-         ClientSession sessConsume = sf.createSession(false, true, true);
-
-         sessConsume.createQueue(QueueConfiguration.of(subName).setAddress(ADDRESS).setDurable(false));
-
-         ClientConsumer consumer = sessConsume.createConsumer(subName);
-
-         consumers.add(consumer);
-
-         sessions.add(sessConsume);
-      }
-
-      ClientSession sessSend = sf.createSession(false, true, true);
-
-      ClientProducer producer = sessSend.createProducer(ADDRESS);
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = sessSend.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-         message.putIntProperty(SimpleString.of("count"), i);
-         producer.send(message);
-      }
-
-      for (ClientSession session : sessions) {
-         session.start();
-      }
-
-      for (int i = 0; i < numMessages; i++) {
-         for (ClientConsumer consumer : consumers) {
-            ClientMessage msg = consumer.receive(RECEIVE_TIMEOUT);
-
-            if (msg == null) {
-               throw new IllegalStateException("Failed to receive message " + i);
-            }
-
-            assertNotNull(msg);
-
-            assertEquals(i, msg.getObjectProperty(SimpleString.of("count")));
-
-            msg.acknowledge();
-         }
-      }
-
-      for (int i = 0; i < numMessages; i++) {
-         for (ClientConsumer consumer : consumers) {
-            ClientMessage msg = consumer.receiveImmediate();
-
-            assertNull(msg);
-         }
-      }
-
-      sessSend.close();
-      for (ClientSession session : sessions) {
-         session.close();
-      }
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of("sub" + i);
-
-         s.deleteQueue(subName);
-      }
-
-      s.close();
-
-      assertEquals(1, ((ClientSessionFactoryImpl) sf).numSessions());
-
-      long end = System.currentTimeMillis();
-
-      logger.debug("duration {}", (end - start));
-   }
-
-   protected void doTestG(final ClientSessionFactory sf) throws Exception {
-      long start = System.currentTimeMillis();
-
-      ClientSession s = sf.createSession(false, false, false);
-
-      final int numMessages = 100;
-
-      final int numSessions = 10;
-
-      Set<ClientConsumer> consumers = new HashSet<>();
-      Set<ClientSession> sessions = new HashSet<>();
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of("sub" + i);
-
-         ClientSession sessConsume = sf.createSession(false, false, false);
-
-         sessConsume.start();
-
-         sessConsume.createQueue(QueueConfiguration.of(subName).setAddress(ADDRESS).setDurable(false));
-
-         ClientConsumer consumer = sessConsume.createConsumer(subName);
-
-         consumers.add(consumer);
-
-         sessions.add(sessConsume);
-      }
-
-      ClientSession sessSend = sf.createSession(false, false, false);
-
-      ClientProducer producer = sessSend.createProducer(ADDRESS);
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = sessSend.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-         message.putIntProperty(SimpleString.of("count"), i);
-         producer.send(message);
-      }
-
-      sessSend.rollback();
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = sessSend.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-         message.putIntProperty(SimpleString.of("count"), i);
-         producer.send(message);
-      }
-
-      sessSend.commit();
-
-      for (int i = 0; i < numMessages; i++) {
-         for (ClientConsumer consumer : consumers) {
-            ClientMessage msg = consumer.receive(RECEIVE_TIMEOUT);
-
-            assertNotNull(msg);
-
-            assertEquals(i, msg.getObjectProperty(SimpleString.of("count")));
-
-            msg.acknowledge();
-         }
-      }
-
-      for (ClientConsumer consumer : consumers) {
-         ClientMessage msg = consumer.receiveImmediate();
-
-         assertNull(msg);
-      }
-
-      for (ClientSession session : sessions) {
-         session.rollback();
-      }
-
-      for (int i = 0; i < numMessages; i++) {
-         for (ClientConsumer consumer : consumers) {
-            ClientMessage msg = consumer.receive(RECEIVE_TIMEOUT);
-
-            assertNotNull(msg);
-
-            assertEquals(i, msg.getObjectProperty(SimpleString.of("count")));
-
-            msg.acknowledge();
-         }
-      }
-
-      for (int i = 0; i < numMessages; i++) {
-         for (ClientConsumer consumer : consumers) {
-            ClientMessage msg = consumer.receiveImmediate();
-
-            assertNull(msg);
-         }
-      }
-
-      for (ClientSession session : sessions) {
-         session.commit();
-      }
-
-      sessSend.close();
-      for (ClientSession session : sessions) {
-         session.close();
-      }
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of("sub" + i);
-
-         s.deleteQueue(subName);
-      }
-
-      s.close();
-
-      long end = System.currentTimeMillis();
-
-      logger.debug("duration {}", (end - start));
-   }
-
-   protected void doTestH(final ClientSessionFactory sf) throws Exception {
-      long start = System.currentTimeMillis();
-
-      ClientSession s = sf.createSession(false, false, false);
-
-      final int numMessages = 100;
-
-      final int numSessions = 10;
-
-      Set<ClientConsumer> consumers = new HashSet<>();
-      Set<ClientSession> sessions = new HashSet<>();
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of("sub" + i);
-
-         ClientSession sessConsume = sf.createSession(false, false, false);
-
-         sessConsume.createQueue(QueueConfiguration.of(subName).setAddress(ADDRESS).setDurable(false));
-
-         ClientConsumer consumer = sessConsume.createConsumer(subName);
-
-         consumers.add(consumer);
-
-         sessions.add(sessConsume);
-      }
-
-      ClientSession sessSend = sf.createSession(false, false, false);
-
-      ClientProducer producer = sessSend.createProducer(ADDRESS);
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = sessSend.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-         message.putIntProperty(SimpleString.of("count"), i);
-         producer.send(message);
-      }
-
-      sessSend.rollback();
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = sessSend.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-         message.putIntProperty(SimpleString.of("count"), i);
-         producer.send(message);
-      }
-
-      sessSend.commit();
-
-      for (ClientSession session : sessions) {
-         session.start();
-      }
-
-      for (int i = 0; i < numMessages; i++) {
-         for (ClientConsumer consumer : consumers) {
-            ClientMessage msg = consumer.receive(RECEIVE_TIMEOUT);
-
-            assertNotNull(msg);
-
-            assertEquals(i, msg.getObjectProperty(SimpleString.of("count")));
-
-            msg.acknowledge();
-         }
-      }
-
-      for (int i = 0; i < numMessages; i++) {
-         for (ClientConsumer consumer : consumers) {
-            ClientMessage msg = consumer.receiveImmediate();
-
-            assertNull(msg);
-         }
-      }
-
-      for (ClientSession session : sessions) {
-         session.rollback();
-      }
-
-      for (int i = 0; i < numMessages; i++) {
-         for (ClientConsumer consumer : consumers) {
-            ClientMessage msg = consumer.receive(RECEIVE_TIMEOUT);
-
-            assertNotNull(msg);
-
-            assertEquals(i, msg.getObjectProperty(SimpleString.of("count")));
-
-            msg.acknowledge();
-         }
-      }
-
-      for (int i = 0; i < numMessages; i++) {
-         for (ClientConsumer consumer : consumers) {
-            ClientMessage msg = consumer.receiveImmediate();
-
-            assertNull(msg);
-         }
-      }
-
-      for (ClientSession session : sessions) {
-         session.commit();
-      }
-
-      sessSend.close();
-      for (ClientSession session : sessions) {
-         session.close();
-      }
-
-      for (int i = 0; i < numSessions; i++) {
-         SimpleString subName = SimpleString.of("sub" + i);
-
-         s.deleteQueue(subName);
-      }
-
-      s.close();
-
-      long end = System.currentTimeMillis();
-
-      logger.debug("duration {}", (end - start));
-   }
-
-   protected void doTestI(final ClientSessionFactory sf) throws Exception {
-      ClientSession sessCreate = sf.createSession(false, true, true);
-
-      sessCreate.createQueue(QueueConfiguration.of(ADDRESS).setDurable(false));
-
-      ClientSession sess = sf.createSession(false, true, true);
-
-      sess.start();
-
-      ClientConsumer consumer = sess.createConsumer(ADDRESS);
-
-      ClientProducer producer = sess.createProducer(ADDRESS);
-
-      ClientMessage message = sess.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-      producer.send(message);
-
-      ClientMessage message2 = consumer.receive(RECEIVE_TIMEOUT);
-
-      assertNotNull(message2);
-
-      message2.acknowledge();
-
-      sess.close();
-
-      sessCreate.deleteQueue(ADDRESS);
-
-      sessCreate.close();
-   }
-
-   protected void doTestJ(final ClientSessionFactory sf) throws Exception {
-      ClientSession sessCreate = sf.createSession(false, true, true);
-
-      sessCreate.createQueue(QueueConfiguration.of(ADDRESS).setDurable(false));
-
-      ClientSession sess = sf.createSession(false, true, true);
-
-      sess.start();
-
-      ClientConsumer consumer = sess.createConsumer(ADDRESS);
-
-      ClientProducer producer = sess.createProducer(ADDRESS);
-
-      ClientMessage message = sess.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-      producer.send(message);
-
-      ClientMessage message2 = consumer.receive(RECEIVE_TIMEOUT);
-
-      assertNotNull(message2);
-
-      message2.acknowledge();
-
-      sess.close();
-
-      sessCreate.deleteQueue(ADDRESS);
-
-      sessCreate.close();
-   }
-
-   protected void doTestK(final ClientSessionFactory sf) throws Exception {
-      ClientSession s = sf.createSession(false, false, false);
-
-      s.createQueue(QueueConfiguration.of(ADDRESS).setDurable(false));
-
-      final int numConsumers = 100;
-
-      for (int i = 0; i < numConsumers; i++) {
-         ClientConsumer consumer = s.createConsumer(ADDRESS);
-
-         consumer.close();
-      }
-
-      s.deleteQueue(ADDRESS);
-
-      s.close();
-   }
-
-   protected void doTestL(final ClientSessionFactory sf) throws Exception {
-      final int numSessions = 10;
-
-      for (int i = 0; i < numSessions; i++) {
-         ClientSession session = sf.createSession(false, false, false);
-
-         session.close();
-      }
-   }
-
-   protected void doTestN(final ClientSessionFactory sf) throws Exception {
-      ClientSession sessCreate = sf.createSession(false, true, true);
-
-      sessCreate.createQueue(QueueConfiguration.of(ADDRESS).setDurable(false));
-
-      ClientSession sess = sf.createSession(false, true, true);
-
-      sess.stop();
-
-      sess.start();
-
-      sess.stop();
-
-      ClientConsumer consumer = sess.createConsumer(ADDRESS);
-
-      ClientProducer producer = sess.createProducer(ADDRESS);
-
-      ClientMessage message = sess.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-      producer.send(message);
-
-      sess.start();
-
-      ClientMessage message2 = consumer.receive(RECEIVE_TIMEOUT);
-
-      assertNotNull(message2);
-
-      message2.acknowledge();
-
-      sess.stop();
-
-      sess.start();
-
-      sess.close();
-
-      sessCreate.deleteQueue(SimpleString.of(ADDRESS.toString()));
-
-      sessCreate.close();
-   }
-
-   @Override
-   @BeforeEach
-   public void setUp() throws Exception {
-      super.setUp();
-
-      timer = new Timer(true);
-   }
-
-   @Override
-   @AfterEach
-   public void tearDown() throws Exception {
-      timer.cancel();
-
-      super.tearDown();
-   }
-
-
-   private Failer startFailer(final long time, final ClientSession session) {
-      Failer failer = new Failer((ClientSessionInternal) session);
-
-      timer.schedule(failer, (long) (time * Math.random()), 100);
-
-      return failer;
-   }
-
-   private void start() throws Exception {
-      server = addServer(ActiveMQServers.newActiveMQServer(createDefaultInVMConfig(), false));
-      server.start();
-   }
-
-   private void stop() throws Exception {
-      server.stop();
-      Wait.assertFalse(server::isStarted, 5_000, 100);
-
-      Wait.assertEquals(0, () -> InVMRegistry.instance.size(), 5_000, 100);
-
-      server = null;
-   }
-
-
-   class Failer extends TimerTask {
-
-      private final ClientSessionInternal session;
-
-      private boolean executed;
-
-      Failer(final ClientSessionInternal session) {
-         this.session = session;
-      }
-
-      @Override
-      public synchronized void run() {
-         logger.debug("** Failing connection");
-
-         session.getConnection().fail(new ActiveMQNotConnectedException("oops"));
-
-         logger.debug("** Fail complete");
-
-         cancel();
-
-         executed = true;
-      }
-
-      public synchronized boolean isExecuted() {
-         return executed;
-      }
-   }
-
-   public abstract class RunnableT {
-
-      abstract void run(ClientSessionFactory sf) throws Exception;
-   }
-
-   abstract static class AssertionCheckMessageHandler implements MessageHandler {
-
-      public void checkAssertions() {
-         for (AssertionError e : errors) {
-            // it will throw the first error
-            throw e;
-         }
-      }
-
-      private final List<AssertionError> errors = new ArrayList<>();
-
-      @Override
-      public void onMessage(ClientMessage message) {
-         try {
-            onMessageAssert(message);
-         } catch (AssertionError e) {
-            e.printStackTrace(); // System.out -> junit reports
-            errors.add(e);
-         }
-      }
-
-      public abstract void onMessageAssert(ClientMessage message);
-   }
-}
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/ReattachDisabledTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/ReattachDisabledTest.java
new file mode 100644
index 0000000000..87ec5998de
--- /dev/null
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/ReattachDisabledTest.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.tests.integration.cluster.reattach;
+
+import java.lang.invoke.MethodHandles;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException;
+import org.apache.activemq.artemis.api.core.ActiveMQSecurityException;
+import org.apache.activemq.artemis.api.core.QueueConfiguration;
+import org.apache.activemq.artemis.api.core.RoutingType;
+import org.apache.activemq.artemis.api.core.SimpleString;
+import org.apache.activemq.artemis.api.core.client.ClientConsumer;
+import org.apache.activemq.artemis.api.core.client.ClientProducer;
+import org.apache.activemq.artemis.api.core.client.ServerLocator;
+import org.apache.activemq.artemis.core.client.impl.ClientSessionFactoryInternal;
+import org.apache.activemq.artemis.core.client.impl.ClientSessionImpl;
+import org.apache.activemq.artemis.core.client.impl.ClientSessionImplAccessor;
+import org.apache.activemq.artemis.core.client.impl.ClientSessionInternal;
+import org.apache.activemq.artemis.core.protocol.core.impl.ActiveMQSessionContext;
+import org.apache.activemq.artemis.core.protocol.core.impl.RemotingConnectionImpl;
+import org.apache.activemq.artemis.core.security.Role;
+import org.apache.activemq.artemis.core.server.ActiveMQServer;
+import org.apache.activemq.artemis.core.settings.HierarchicalRepository;
+import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
+import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
+import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
+import org.apache.activemq.artemis.utils.RandomUtil;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class ReattachDisabledTest extends ActiveMQTestBase {
+
+   private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+   private static final SimpleString ADDRESS = SimpleString.of(getTestClassName());
+   private ActiveMQServer server;
+   private ServerLocator locator;
+   private ServerLocator locator2;
+
+   @Test
+   public void testValidateExceptionThrown() throws Exception {
+      locator.setRetryInterval(100).setRetryIntervalMultiplier(1).setReconnectAttempts(-1).setConfirmationWindowSize(1024 * 1024);
+      locator2.setRetryInterval(100).setRetryIntervalMultiplier(1).setReconnectAttempts(-1).setConfirmationWindowSize(1024 * 1024);
+      ClientSessionImpl targetSessionImpl;
+      {
+
+         ClientSessionFactoryInternal sfTarget = (ClientSessionFactoryInternal) createSessionFactory(locator);
+         runAfter(sfTarget::close);
+
+         targetSessionImpl = (ClientSessionImpl) sfTarget.createSession("admin", "admin", false, true, true, false, 0);
+
+         targetSessionImpl.createQueue(QueueConfiguration.of(ReattachDisabledTest.ADDRESS).setDurable(false));
+
+      }
+
+      ClientSessionFactoryInternal sfReattaching = (ClientSessionFactoryInternal) createSessionFactory(locator2);
+      runAfter(sfReattaching::close);
+      RemotingConnectionImpl connection = (RemotingConnectionImpl) sfReattaching.getConnection();
+      int version = targetSessionImpl.getSessionContext().getServerVersion();
+      ActiveMQSessionContext targetSessionContext = (ActiveMQSessionContext) targetSessionImpl.getSessionContext();
+
+      ActiveMQSessionContext context = new ActiveMQSessionContext(targetSessionImpl.getName(), sfReattaching.getConnection(), connection.getChannel(targetSessionContext.getSessionChannel().getID(), 1024 * 1024), version, 1024 * 1024);
+      ClientSessionInternal reattachSession = ClientSessionImplAccessor.createSession(sfReattaching, targetSessionImpl.getName(), "guest", "guest", false, false, false, false, false, false, 1, 1024, -1, -1, -1, -1, false, false, false, 100 * 1024, false, 1, 1024, null, 60000, context, ClientSessionImplAccessor.getExecutor(targetSessionImpl), ClientSessionImplAccessor.getConfirmationExecutor(targetSessionImpl), ClientSessionImplAccessor.getFlowControlExecutor(targetSessionImpl), ClientSes [...]
+
+      RemotingConnection conn = reattachSession.getConnection();
+      conn.fail(new ActiveMQNotConnectedException());
+
+      Thread.sleep(500);
+
+      String nonExistingQueue = "IDontExist_" + RandomUtil.randomUUIDString();
+
+      // The user should not have permissions to create the queue after the reconnect
+      assertThrows(ActiveMQSecurityException.class, () -> {
+         reattachSession.createAddress(SimpleString.of(nonExistingQueue), RoutingType.ANYCAST, true);
+         reattachSession.createQueue(QueueConfiguration.of(nonExistingQueue).setAddress(nonExistingQueue).setRoutingType(RoutingType.ANYCAST).setDurable(true));
+      });
+
+      assertNull(server.locateQueue(nonExistingQueue));
+
+      ClientProducer producer = reattachSession.createProducer(ReattachDisabledTest.ADDRESS);
+      runAfter(reattachSession::close);
+      producer.send(reattachSession.createMessage(false));
+      reattachSession.commit();
+
+      ClientConsumer consumer = reattachSession.createConsumer(ReattachDisabledTest.ADDRESS);
+      reattachSession.start();
+      assertNotNull(consumer.receive(5000));
+      reattachSession.commit();
+   }
+
+   @Override
+   @BeforeEach
+   public void setUp() throws Exception {
+      super.setUp();
+
+      server = createServer(false, true);
+
+      ActiveMQJAASSecurityManager securityManager = (ActiveMQJAASSecurityManager) server.getSecurityManager();
+
+      // Add users
+      securityManager.getConfiguration().addUser("guest", "guest");
+      securityManager.getConfiguration().addRole("guest", "limited");
+      securityManager.getConfiguration().addUser("admin", "admin");
+      securityManager.getConfiguration().addRole("admin", "full");
+
+      // Configure roles
+      HierarchicalRepository<Set<Role>> securityRepository = server.getSecurityRepository();
+      Set<Role> roles = new HashSet<>();
+
+      // "limited" role: can only send and consume, cannot create queues
+      roles.add(new Role("limited", true, true, false, false, false, false, false, false, false, false, false, false));
+
+      // "full" role: can do everything
+      roles.add(new Role("full", true, true, true, true, true, true, true, true, true, true, false, false));
+
+      securityRepository.addMatch("#", roles);
+
+      server.getConfiguration().setSecurityEnabled(true);
+
+      server.start();
+
+      locator = createNettyNonHALocator();
+      locator2 = createNettyNonHALocator();
+   }
+
+   @Override
+   @AfterEach
+   public void tearDown() throws Exception {
+      super.tearDown();
+   }
+
+}
\ No newline at end of file
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/ReattachTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/ReattachTest.java
deleted file mode 100644
index 531f7eb07e..0000000000
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/reattach/ReattachTest.java
+++ /dev/null
@@ -1,1030 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.activemq.artemis.tests.integration.cluster.reattach;
-
-import java.lang.invoke.MethodHandles;
-import java.util.Set;
-import java.util.Timer;
-import java.util.TimerTask;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.apache.activemq.artemis.api.core.ActiveMQException;
-import org.apache.activemq.artemis.api.core.ActiveMQExceptionType;
-import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException;
-import org.apache.activemq.artemis.api.core.ActiveMQObjectClosedException;
-import org.apache.activemq.artemis.api.core.Interceptor;
-import org.apache.activemq.artemis.api.core.QueueConfiguration;
-import org.apache.activemq.artemis.api.core.RoutingType;
-import org.apache.activemq.artemis.api.core.SimpleString;
-import org.apache.activemq.artemis.api.core.client.ClientConsumer;
-import org.apache.activemq.artemis.api.core.client.ClientMessage;
-import org.apache.activemq.artemis.api.core.client.ClientProducer;
-import org.apache.activemq.artemis.api.core.client.ClientSession;
-import org.apache.activemq.artemis.api.core.client.ServerLocator;
-import org.apache.activemq.artemis.api.core.client.SessionFailureListener;
-import org.apache.activemq.artemis.core.client.impl.ClientSessionFactoryInternal;
-import org.apache.activemq.artemis.core.client.impl.ClientSessionInternal;
-import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.SessionProducerCreditsMessage;
-import org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnector;
-import org.apache.activemq.artemis.core.server.ActiveMQServer;
-import org.apache.activemq.artemis.core.server.ServerSession;
-import org.apache.activemq.artemis.jms.client.ActiveMQTextMessage;
-import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
-import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
-
-public class ReattachTest extends ActiveMQTestBase {
-
-   private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-   private static final SimpleString ADDRESS = SimpleString.of("FailoverTestAddress");
-   private ActiveMQServer server;
-   private ServerLocator locator;
-
-   /**
-    * Test failure on connection, but server is still up so should immediately reconnect
-    */
-   @Test
-   public void testImmediateReattach() throws Exception {
-      final long retryInterval = 50;
-
-      final double retryMultiplier = 1d;
-
-      final int reconnectAttempts = 10;
-
-      locator.setRetryInterval(retryInterval).setRetryIntervalMultiplier(retryMultiplier).setReconnectAttempts(reconnectAttempts).setConfirmationWindowSize(1024 * 1024);
-
-      ClientSessionFactoryInternal sf = (ClientSessionFactoryInternal) createSessionFactory(locator);
-
-      ClientSession session = sf.createSession(false, true, true);
-
-      session.createQueue(QueueConfiguration.of(ReattachTest.ADDRESS).setDurable(false));
-
-      final int numIterations = 10;
-
-      for (int j = 0; j < numIterations; j++) {
-         ClientProducer producer = session.createProducer(ReattachTest.ADDRESS);
-
-         final int numMessages = 100;
-
-         for (int i = 0; i < numMessages; i++) {
-            ClientMessage message = session.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-            message.putIntProperty(SimpleString.of("count"), i);
-            message.getBodyBuffer().writeString("aardvarks");
-            producer.send(message);
-         }
-
-         ClientConsumer consumer = session.createConsumer(ReattachTest.ADDRESS);
-
-         RemotingConnection conn = ((ClientSessionInternal) session).getConnection();
-
-         conn.fail(new ActiveMQNotConnectedException());
-
-         session.start();
-
-         for (int i = 0; i < numMessages; i++) {
-            ClientMessage message = consumer.receive(500);
-
-            assertNotNull(message);
-
-            assertEquals("aardvarks", message.getBodyBuffer().readString());
-
-            assertEquals(i, message.getObjectProperty(SimpleString.of("count")));
-
-            message.acknowledge();
-         }
-
-         ClientMessage message = consumer.receiveImmediate();
-
-         assertNull(message);
-
-         producer.close();
-
-         consumer.close();
-      }
-
-      session.close();
-
-      sf.close();
-   }
-
-   @Test
-   public void testReattachTransferConnectionOnSession() throws Exception {
-      final long retryInterval = 50;
-      final double retryMultiplier = 1d;
-      final int reconnectAttempts = 10;
-
-      locator.setRetryInterval(retryInterval).setRetryIntervalMultiplier(retryMultiplier).setReconnectAttempts(reconnectAttempts).setConfirmationWindowSize(1024 * 1024);
-      ClientSessionFactoryInternal sf = (ClientSessionFactoryInternal) createSessionFactory(locator);
-      ClientSession session = sf.createSession(false, true, true);
-
-      // there's only one session on the broker
-      Object originalConnectionID = ((ServerSession)server.getSessions().toArray()[0]).getConnectionID();
-
-      // trigger re-attach
-      ((ClientSessionInternal) session).getConnection().fail(new ActiveMQNotConnectedException());
-
-      session.start();
-
-      assertNotEquals(((ServerSession) server.getSessions().toArray()[0]).getConnectionID(), originalConnectionID);
-
-      session.close();
-      sf.close();
-   }
-
-   @Test
-   public void testReattachTransferConnectionOnSession2() throws Exception {
-      final long retryInterval = 50;
-      final double retryMultiplier = 1d;
-      final int reconnectAttempts = 10;
-
-      locator.setRetryInterval(retryInterval).setRetryIntervalMultiplier(retryMultiplier).setReconnectAttempts(reconnectAttempts).setConfirmationWindowSize(1024 * 1024);
-      ClientSessionFactoryInternal sf = (ClientSessionFactoryInternal) createSessionFactory(locator);
-      ClientSession session = sf.createSession(false, true, true);
-      ClientSession secondSession = sf.createSession(false, true, true);
-
-      // there's only one connection on the broker
-      Object originalConnectionID = ((ServerSession) server.getSessions().toArray()[0]).getConnectionID();
-      RemotingConnection oldConnection = ((ServerSession) server.getSessions().toArray()[0]).getRemotingConnection();
-
-      // ensure sessions are set as failure listeners on old connection
-      Set<ServerSession> originalServerSessions = server.getSessions();
-      assertTrue(oldConnection.getFailureListeners().containsAll(originalServerSessions));
-
-      // trigger re-attach
-      ((ClientSessionInternal) session).getConnection().fail(new ActiveMQNotConnectedException());
-
-      session.start();
-      secondSession.start();
-
-      assertNotEquals(((ServerSession) server.getSessions().toArray()[0]).getConnectionID(), originalConnectionID);
-
-      // ensure sessions were removed as failure listeners of old connection and are now failure listeners of new connection
-      assertTrue(originalServerSessions.stream().noneMatch(oldConnection.getFailureListeners()::contains));
-      RemotingConnection newConnection = ((ServerSession) server.getSessions().toArray()[0]).getRemotingConnection();
-      assertTrue(newConnection.getFailureListeners().containsAll(originalServerSessions));
-
-      session.close();
-      secondSession.close();
-      sf.close();
-   }
-
-   /**
-    * Test failure on connection, but server is still up so should immediately reconnect
-    */
-   @Test
-   public void testOverflowCredits() throws Exception {
-      final long retryInterval = 500;
-
-      final double retryMultiplier = 1d;
-
-      final int reconnectAttempts = 1;
-
-      locator.setRetryInterval(retryInterval).setRetryIntervalMultiplier(retryMultiplier).setReconnectAttempts(reconnectAttempts).setConfirmationWindowSize(1024 * 1024).setProducerWindowSize(1000);
-
-      final AtomicInteger count = new AtomicInteger(0);
-
-      Interceptor intercept = (packet, connection) -> {
-         if (packet instanceof SessionProducerCreditsMessage credit) {
-
-            if (count.incrementAndGet() == 2) {
-               connection.fail(new ActiveMQException(ActiveMQExceptionType.UNSUPPORTED_PACKET, "bye"));
-               return false;
-            }
-         }
-         return true;
-      };
-
-      locator.addIncomingInterceptor(intercept);
-
-      ClientSessionFactoryInternal sf = (ClientSessionFactoryInternal) createSessionFactory(locator);
-
-      ClientSession session = sf.createSession(false, true, true);
-
-      session.createQueue(QueueConfiguration.of(ReattachTest.ADDRESS).setDurable(false));
-
-      ClientProducer producer = session.createProducer(ReattachTest.ADDRESS);
-
-      final int numMessages = 10;
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = session.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-         message.putIntProperty(SimpleString.of("count"), i);
-         message.getBodyBuffer().writeBytes(new byte[5000]);
-         producer.send(message);
-      }
-
-      session.close();
-
-      sf.close();
-   }
-
-   /*
-    * Test failure on connection, simulate failure to create connection for a while, then
-    * allow connection to be recreated
-    */
-   @Test
-   public void testDelayedReattach() throws Exception {
-      final long retryInterval = 50;
-
-      final double retryMultiplier = 1d;
-
-      final int reconnectAttempts = 60;
-
-      locator.setRetryInterval(retryInterval).setRetryIntervalMultiplier(retryMultiplier).setReconnectAttempts(reconnectAttempts).setConfirmationWindowSize(1024 * 1024);
-
-      ClientSessionFactoryInternal sf = (ClientSessionFactoryInternal) createSessionFactory(locator);
-
-      ClientSession session = sf.createSession(false, true, true);
-
-      session.createQueue(QueueConfiguration.of(ReattachTest.ADDRESS).setDurable(false));
-
-      ClientProducer producer = session.createProducer(ReattachTest.ADDRESS);
-
-      final int numMessages = 1000;
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = session.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-         message.putIntProperty(SimpleString.of("count"), i);
-         message.getBodyBuffer().writeString("aardvarks");
-         producer.send(message);
-      }
-
-      ClientConsumer consumer = session.createConsumer(ReattachTest.ADDRESS);
-
-      InVMConnector.failOnCreateConnection = true;
-
-      RemotingConnection conn = ((ClientSessionInternal) session).getConnection();
-
-      Thread t = new Thread(() -> {
-         try {
-            Thread.sleep(retryInterval * 3);
-         } catch (InterruptedException ignore) {
-         }
-
-         InVMConnector.failOnCreateConnection = false;
-      });
-
-      t.start();
-
-      conn.fail(new ActiveMQNotConnectedException());
-
-      session.start();
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = consumer.receive(500);
-
-         assertNotNull(message);
-
-         assertEquals("aardvarks", message.getBodyBuffer().readString());
-
-         assertEquals(i, message.getIntProperty("count").intValue());
-
-         message.acknowledge();
-      }
-
-      ClientMessage message = consumer.receiveImmediate();
-
-      assertNull(message);
-
-      session.close();
-
-      sf.close();
-
-      t.join();
-   }
-
-   // Test an async (e.g. pinger) failure coming in while a connection manager is already reconnecting
-   @Test
-   public void testAsyncFailureWhileReattaching() throws Exception {
-      final long retryInterval = 50;
-
-      final double retryMultiplier = 1d;
-
-      final int reconnectAttempts = 60;
-
-      final long asyncFailDelay = 200;
-
-      locator.setRetryInterval(retryInterval).setRetryIntervalMultiplier(retryMultiplier).setReconnectAttempts(reconnectAttempts).setConfirmationWindowSize(1024 * 1024);
-
-      ClientSessionFactoryInternal sf = (ClientSessionFactoryInternal) createSessionFactory(locator);
-
-      ClientSession session = sf.createSession(false, true, true);
-
-      ClientSession session2 = sf.createSession(false, true, true);
-
-      class MyFailureListener implements SessionFailureListener {
-
-         volatile boolean failed;
-
-         @Override
-         public void connectionFailed(final ActiveMQException me, boolean failedOver) {
-            failed = true;
-         }
-
-         @Override
-         public void connectionFailed(final ActiveMQException me, boolean failedOver, String scaleDownTargetNodeID) {
-            connectionFailed(me, failedOver);
-         }
-
-         @Override
-         public void beforeReconnect(final ActiveMQException exception) {
-         }
-      }
-
-      MyFailureListener listener = new MyFailureListener();
-
-      session2.addFailureListener(listener);
-
-      session.createQueue(QueueConfiguration.of(ReattachTest.ADDRESS).setDurable(false));
-
-      ClientProducer producer = session.createProducer(ReattachTest.ADDRESS);
-
-      final int numMessages = 1000;
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = session.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-         message.putIntProperty(SimpleString.of("count"), i);
-         message.getBodyBuffer().writeString("aardvarks");
-         producer.send(message);
-      }
-
-      ClientConsumer consumer = session.createConsumer(ReattachTest.ADDRESS);
-
-      InVMConnector.numberOfFailures = 10;
-      InVMConnector.failOnCreateConnection = true;
-
-      final RemotingConnection conn = ((ClientSessionInternal) session).getConnection();
-
-      final RemotingConnection conn2 = ((ClientSessionInternal) session2).getConnection();
-
-      Thread t = new Thread(() -> {
-         try {
-            Thread.sleep(asyncFailDelay);
-         } catch (InterruptedException ignore) {
-         }
-
-         conn2.fail(new ActiveMQNotConnectedException("Did not receive pong from server"));
-      });
-
-      t.start();
-
-      conn.fail(new ActiveMQNotConnectedException());
-
-      assertTrue(listener.failed);
-
-      session.start();
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = consumer.receive(500);
-
-         assertNotNull(message);
-
-         assertEquals("aardvarks", message.getBodyBuffer().readString());
-
-         assertEquals(i, message.getIntProperty("count").intValue());
-
-         message.acknowledge();
-      }
-
-      ClientMessage message = consumer.receiveImmediate();
-
-      assertNull(message);
-
-      session.close();
-
-      session2.close();
-
-      sf.close();
-
-      t.join();
-   }
-
-   @Test
-   public void testReattachAttemptsFailsToReconnect() throws Exception {
-      final long retryInterval = 50;
-
-      final double retryMultiplier = 1d;
-
-      final int reconnectAttempts = 3;
-
-      locator.setRetryInterval(retryInterval).setRetryIntervalMultiplier(retryMultiplier).setReconnectAttempts(reconnectAttempts).setConfirmationWindowSize(1024 * 1024);
-
-      ClientSessionFactoryInternal sf = (ClientSessionFactoryInternal) createSessionFactory(locator);
-
-      ClientSession session = sf.createSession(false, true, true);
-
-      session.createQueue(QueueConfiguration.of(ReattachTest.ADDRESS).setDurable(false));
-
-      ClientProducer producer = session.createProducer(ReattachTest.ADDRESS);
-
-      final int numMessages = 1000;
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = session.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-         message.putIntProperty(SimpleString.of("count"), i);
-         message.getBodyBuffer().writeString("aardvarks");
-         producer.send(message);
-      }
-
-      session.createConsumer(ReattachTest.ADDRESS);
-
-      InVMConnector.failOnCreateConnection = true;
-
-      RemotingConnection conn = ((ClientSessionInternal) session).getConnection();
-
-      // Sleep for longer than max retries so should fail to reconnect
-
-      Thread t = new Thread(() -> {
-         try {
-            Thread.sleep(retryInterval * (reconnectAttempts + 1));
-         } catch (InterruptedException ignore) {
-         }
-
-         InVMConnector.failOnCreateConnection = false;
-      });
-
-      t.start();
-
-      conn.fail(new ActiveMQNotConnectedException());
-
-      // Should throw exception since didn't reconnect
-
-      try {
-         session.start();
-
-         fail("Should throw exception");
-      } catch (ActiveMQObjectClosedException oce) {
-         //ok
-      } catch (ActiveMQException e) {
-         fail("Invalid Exception type:" + e.getType());
-      }
-
-      session.close();
-
-      sf.close();
-
-      t.join();
-   }
-
-   @Test
-   public void testCreateSessionFailAfterSendSeveralThreads() throws Throwable {
-
-      Timer timer = new Timer();
-      ClientSession session = null;
-
-      try {
-
-         final long retryInterval = 100;
-
-         final double retryMultiplier = 1d;
-
-         final int reconnectAttempts = 300;
-
-         locator.setRetryInterval(retryInterval).setRetryIntervalMultiplier(retryMultiplier).setReconnectAttempts(reconnectAttempts).setConfirmationWindowSize(1024 * 1024);
-
-         final ClientSessionFactoryInternal sf = (ClientSessionFactoryInternal) createSessionFactory(locator);
-
-         session = sf.createSession();
-
-         final RemotingConnection connFailure = ((ClientSessionInternal) session).getConnection();
-
-         int numberOfThreads = 100;
-         final int numberOfSessionsToCreate = 10;
-
-         final CountDownLatch alignLatch = new CountDownLatch(numberOfThreads);
-         final CountDownLatch startFlag = new CountDownLatch(1);
-
-         class CreateSessionThread extends Thread {
-
-            Throwable failure;
-
-            @Override
-            public void run() {
-               try {
-                  alignLatch.countDown();
-                  startFlag.await();
-                  for (int i = 0; i < numberOfSessionsToCreate; i++) {
-                     Thread.yield();
-                     ClientSession session = sf.createSession(false, true, true);
-
-                     session.close();
-                  }
-               } catch (Throwable e) {
-                  e.printStackTrace();
-                  failure = e;
-               }
-            }
-         }
-
-         CreateSessionThread[] threads = new CreateSessionThread[numberOfThreads];
-         for (int i = 0; i < numberOfThreads; i++) {
-            threads[i] = new CreateSessionThread();
-            threads[i].start();
-         }
-
-         waitForLatch(alignLatch);
-
-         timer.schedule(new TimerTask() {
-            @Override
-            public void run() {
-               try {
-                  connFailure.fail(new ActiveMQNotConnectedException());
-               } catch (Exception e) {
-                  logger.warn("Error on the timer {}", e);
-               }
-            }
-
-         }, 10, 10);
-
-         startFlag.countDown();
-
-         Throwable failure = null;
-
-         for (CreateSessionThread thread : threads) {
-            thread.join();
-            if (thread.failure != null) {
-               System.out.println("Thread " + thread.getName() + " failed - " + thread.failure);
-               failure = thread.failure;
-            }
-         }
-
-         if (failure != null) {
-            throw failure;
-         }
-
-         sf.close();
-
-      } finally {
-         timer.cancel();
-
-         if (session != null) {
-            session.close();
-         }
-      }
-   }
-
-   @Test
-   public void testCreateSessionFailBeforeSendSeveralThreads() throws Throwable {
-      final long retryInterval = 50;
-
-      final double retryMultiplier = 1d;
-
-      final int reconnectAttempts = 60;
-
-      locator.setRetryInterval(retryInterval).setRetryIntervalMultiplier(retryMultiplier).setReconnectAttempts(reconnectAttempts).setConfirmationWindowSize(1024 * 1024);
-
-      final ClientSessionFactoryInternal sf = (ClientSessionFactoryInternal) createSessionFactory(locator);
-
-      InVMConnector.failOnCreateConnection = true;
-
-      int numberOfThreads = 100;
-
-      final CountDownLatch alignLatch = new CountDownLatch(numberOfThreads);
-      final CountDownLatch startFlag = new CountDownLatch(1);
-
-      class CreateSessionThread extends Thread {
-
-         Throwable failure;
-
-         @Override
-         public void run() {
-            try {
-               alignLatch.countDown();
-               startFlag.await();
-               ClientSession session = sf.createSession(false, true, true);
-
-               session.close();
-            } catch (Throwable e) {
-               e.printStackTrace();
-               failure = e;
-            }
-         }
-      }
-
-      CreateSessionThread[] threads = new CreateSessionThread[numberOfThreads];
-      for (int i = 0; i < numberOfThreads; i++) {
-         threads[i] = new CreateSessionThread();
-         threads[i].start();
-      }
-
-      // Sleep 3 times retryInterval, so it should at least have 3 retries
-
-      Thread t = new Thread(() -> {
-         try {
-            Thread.sleep(retryInterval * 3);
-         } catch (InterruptedException ignore) {
-         }
-
-         InVMConnector.failOnCreateConnection = false;
-      });
-
-      waitForLatch(alignLatch);
-
-      t.start();
-
-      startFlag.countDown();
-
-      Throwable failure = null;
-
-      for (CreateSessionThread thread : threads) {
-         thread.join();
-         if (thread.failure != null) {
-            System.out.println("Thread " + thread.getName() + " failed - " + thread.failure);
-            failure = thread.failure;
-         }
-      }
-
-      if (failure != null) {
-         throw failure;
-      }
-
-      sf.close();
-
-      t.join();
-   }
-
-   @Test
-   public void testCreateQueue() throws Exception {
-      final long retryInterval = 100;
-
-      final double retryMultiplier = 1d;
-
-      final int reconnectAttempts = 300;
-
-      locator.setRetryInterval(retryInterval).setRetryIntervalMultiplier(retryMultiplier).setReconnectAttempts(reconnectAttempts).setConfirmationWindowSize(1024 * 1024);
-
-      ClientSessionFactoryInternal sf = (ClientSessionFactoryInternal) createSessionFactory(locator);
-
-      ClientSession session = sf.createSession(false, true, true);
-
-      // Sleep 3 times retryInterval, so it should at least have 3 retries
-
-      RemotingConnection conn = ((ClientSessionInternal) session).getConnection();
-
-      InVMConnector.failOnCreateConnection = false;
-
-      conn.fail(new ActiveMQNotConnectedException());
-
-      Thread t = new Thread(() -> {
-         try {
-            Thread.sleep(retryInterval * 3);
-         } catch (InterruptedException ignore) {
-         }
-
-         InVMConnector.failOnCreateConnection = false;
-      });
-
-      t.start();
-
-      for (int i = 0; i < 10; i++) {
-         session.createQueue(QueueConfiguration.of("queue" + i).setAddress("address").setRoutingType(RoutingType.ANYCAST));
-      }
-
-      //
-      // InVMConnector.failOnCreateConnection = true;
-
-      //
-      // //Should throw exception since didn't reconnect
-      //
-      // try
-      // {
-      // session.start();
-      //
-      // fail("Should throw exception");
-      // }
-      // catch (ActiveMQException e)
-      // {
-      // assertEquals(ActiveMQException.OBJECT_CLOSED, e.getCode());
-      // }
-
-      session.close();
-
-      sf.close();
-
-      t.join();
-   }
-
-   @Test
-   public void testReattachAttemptsSucceedsInReconnecting() throws Exception {
-      final long retryInterval = 50;
-
-      final double retryMultiplier = 1d;
-
-      final int reconnectAttempts = 10;
-
-      locator.setRetryInterval(retryInterval).setRetryIntervalMultiplier(retryMultiplier).setReconnectAttempts(reconnectAttempts).setConfirmationWindowSize(1024 * 1024);
-
-      ClientSessionFactoryInternal sf = (ClientSessionFactoryInternal) createSessionFactory(locator);
-
-      ClientSession session = sf.createSession(false, true, true);
-
-      session.createQueue(QueueConfiguration.of(ReattachTest.ADDRESS).setDurable(false));
-
-      ClientProducer producer = session.createProducer(ReattachTest.ADDRESS);
-
-      final int numMessages = 1000;
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = session.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-         message.putIntProperty(SimpleString.of("count"), i);
-         message.getBodyBuffer().writeString("aardvarks");
-         producer.send(message);
-      }
-
-      ClientConsumer consumer = session.createConsumer(ReattachTest.ADDRESS);
-
-      InVMConnector.failOnCreateConnection = true;
-      InVMConnector.numberOfFailures = reconnectAttempts - 1;
-
-      RemotingConnection conn = ((ClientSessionInternal) session).getConnection();
-
-      conn.fail(new ActiveMQNotConnectedException());
-
-      session.start();
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = consumer.receive(500);
-
-         assertNotNull(message);
-
-         assertEquals("aardvarks", message.getBodyBuffer().readString());
-
-         assertEquals(i, message.getIntProperty("count").intValue());
-
-         message.acknowledge();
-      }
-
-      ClientMessage message = consumer.receiveImmediate();
-
-      assertNull(message);
-
-      session.close();
-
-      sf.close();
-   }
-
-   @Test
-   public void testRetryInterval() throws Exception {
-      final long retryInterval = 500;
-
-      final double retryMultiplier = 1d;
-
-      final int reconnectAttempts = 60;
-
-      locator.setRetryInterval(retryInterval).setRetryIntervalMultiplier(retryMultiplier).setReconnectAttempts(reconnectAttempts).setConfirmationWindowSize(1024 * 1024);
-
-      ClientSessionFactoryInternal sf = (ClientSessionFactoryInternal) createSessionFactory(locator);
-
-      ClientSession session = sf.createSession(false, true, true);
-
-      session.createQueue(QueueConfiguration.of(ReattachTest.ADDRESS).setDurable(false));
-
-      ClientProducer producer = session.createProducer(ReattachTest.ADDRESS);
-
-      final int numMessages = 1000;
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = session.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-         message.putIntProperty(SimpleString.of("count"), i);
-         message.getBodyBuffer().writeString("aardvarks");
-         producer.send(message);
-      }
-
-      ClientConsumer consumer = session.createConsumer(ReattachTest.ADDRESS);
-
-      InVMConnector.failOnCreateConnection = true;
-
-      RemotingConnection conn = ((ClientSessionInternal) session).getConnection();
-
-      long start = System.currentTimeMillis();
-
-      Thread t = new Thread(() -> {
-         try {
-            Thread.sleep(retryInterval / 2);
-         } catch (InterruptedException ignore) {
-         }
-         InVMConnector.failOnCreateConnection = false;
-      });
-
-      t.start();
-
-      conn.fail(new ActiveMQNotConnectedException());
-
-      session.start();
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = consumer.receive(500);
-
-         assertNotNull(message);
-
-         assertEquals("aardvarks", message.getBodyBuffer().readString());
-
-         assertEquals(i, message.getIntProperty("count").intValue());
-
-         message.acknowledge();
-      }
-
-      ClientMessage message = consumer.receiveImmediate();
-
-      assertNull(message);
-
-      long end = System.currentTimeMillis();
-
-      assertTrue(end - start >= retryInterval);
-
-      session.close();
-
-      sf.close();
-
-      t.join();
-   }
-
-   @Test
-   public void testExponentialBackoff() throws Exception {
-      final long retryInterval = 50;
-
-      final double retryMultiplier = 2d;
-
-      final int reconnectAttempts = 60;
-
-      locator.setRetryInterval(retryInterval).setRetryIntervalMultiplier(retryMultiplier).setReconnectAttempts(reconnectAttempts).setConfirmationWindowSize(1024 * 1024);
-
-      ClientSessionFactoryInternal sf = (ClientSessionFactoryInternal) createSessionFactory(locator);
-
-      ClientSession session = sf.createSession(false, true, true);
-
-      session.createQueue(QueueConfiguration.of(ReattachTest.ADDRESS).setDurable(false));
-
-      ClientProducer producer = session.createProducer(ReattachTest.ADDRESS);
-
-      final int numMessages = 1000;
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = session.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-         message.putIntProperty(SimpleString.of("count"), i);
-         message.getBodyBuffer().writeString("aardvarks");
-         producer.send(message);
-      }
-
-      ClientConsumer consumer = session.createConsumer(ReattachTest.ADDRESS);
-
-      InVMConnector.failOnCreateConnection = true;
-      InVMConnector.numberOfFailures = 3;
-
-      RemotingConnection conn = ((ClientSessionInternal) session).getConnection();
-
-      long start = System.currentTimeMillis();
-
-      conn.fail(new ActiveMQNotConnectedException());
-
-      session.start();
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = consumer.receive(500);
-
-         assertNotNull(message);
-
-         assertEquals("aardvarks", message.getBodyBuffer().readString());
-
-         assertEquals(i, message.getObjectProperty(SimpleString.of("count")));
-
-         message.acknowledge();
-      }
-
-      ClientMessage message = consumer.receiveImmediate();
-
-      assertNull(message);
-
-      long end = System.currentTimeMillis();
-
-      double wait = retryInterval + retryMultiplier * retryInterval + retryMultiplier * retryMultiplier * retryInterval;
-
-      assertTrue(end - start >= wait);
-
-      session.close();
-
-      sf.close();
-   }
-
-   @Test
-   public void testExponentialBackoffMaxRetryInterval() throws Exception {
-      final long retryInterval = 50;
-
-      final double retryMultiplier = 2d;
-
-      final int reconnectAttempts = 60;
-
-      final long maxRetryInterval = 100;
-
-      locator.setRetryInterval(retryInterval).setRetryIntervalMultiplier(retryMultiplier).setReconnectAttempts(reconnectAttempts).setMaxRetryInterval(maxRetryInterval).setConfirmationWindowSize(1024 * 1024);
-
-      ClientSessionFactoryInternal sf = (ClientSessionFactoryInternal) createSessionFactory(locator);
-
-      ClientSession session = sf.createSession(false, true, true);
-
-      session.createQueue(QueueConfiguration.of(ReattachTest.ADDRESS).setDurable(false));
-
-      ClientProducer producer = session.createProducer(ReattachTest.ADDRESS);
-
-      final int numMessages = 1000;
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = session.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);
-         message.putIntProperty(SimpleString.of("count"), i);
-         message.getBodyBuffer().writeString("aardvarks");
-         producer.send(message);
-      }
-
-      ClientConsumer consumer = session.createConsumer(ReattachTest.ADDRESS);
-
-      InVMConnector.failOnCreateConnection = true;
-      InVMConnector.numberOfFailures = 3;
-
-      RemotingConnection conn = ((ClientSessionInternal) session).getConnection();
-
-      long start = System.currentTimeMillis();
-
-      conn.fail(new ActiveMQNotConnectedException());
-
-      session.start();
-
-      for (int i = 0; i < numMessages; i++) {
-         ClientMessage message = consumer.receive(500);
-
-         assertNotNull(message);
-
-         assertEquals("aardvarks", message.getBodyBuffer().readString());
-
-         assertEquals(i, message.getIntProperty("count").intValue());
-
-         message.acknowledge();
-      }
-
-      ClientMessage message = consumer.receiveImmediate();
-
-      assertNull(message);
-
-      long end = System.currentTimeMillis();
-
-      double wait = retryInterval + retryMultiplier * 2 * retryInterval + retryMultiplier;
-
-      assertTrue(end - start >= wait);
-
-      assertTrue(end - start < wait + 500);
-
-      session.close();
-
-      sf.close();
-   }
-
-
-
-   @Override
-   @BeforeEach
-   public void setUp() throws Exception {
-      super.setUp();
-
-      server = createServer(false, false);
-
-      server.start();
-
-      locator = createInVMNonHALocator();
-   }
-
-   @Override
-   @AfterEach
-   public void tearDown() throws Exception {
-      InVMConnector.resetFailures();
-
-      super.tearDown();
-   }
-
-
-}
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/util/BackupSyncDelay.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/util/BackupSyncDelay.java
index 0adb3e21bf..76d04b6bf3 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/util/BackupSyncDelay.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/util/BackupSyncDelay.java
@@ -291,16 +291,6 @@ public class BackupSyncDelay implements Interceptor {
          return null;
       }
 
-      @Override
-      public void replayCommands(int lastConfirmedCommandID) {
-         throw new UnsupportedOperationException();
-      }
-
-      @Override
-      public int getLastConfirmedCommandID() {
-         throw new UnsupportedOperationException();
-      }
-
       @Override
       public boolean isLocked() {
          return false;
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/client/RollbackTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/client/RollbackTest.java
index 342556c975..064cad919e 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/client/RollbackTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/client/RollbackTest.java
@@ -20,9 +20,11 @@ package org.apache.activemq.artemis.tests.integration.jms.client;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
 import javax.jms.Connection;
+import javax.jms.ExceptionListener;
 import javax.jms.JMSException;
 import javax.jms.Message;
 import javax.jms.MessageConsumer;
@@ -30,6 +32,9 @@ import javax.jms.MessageProducer;
 import javax.jms.Session;
 import javax.jms.Topic;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 import org.apache.activemq.artemis.api.core.ActiveMQException;
 import org.apache.activemq.artemis.api.core.Interceptor;
 import org.apache.activemq.artemis.core.protocol.core.Packet;
@@ -49,7 +54,7 @@ public class RollbackTest extends ActiveMQTestBase {
    @BeforeEach
    public void setUp() throws Exception {
       super.setUp();
-      server = createServer(false, createDefaultInVMConfig());
+      server = createServer(false, true);
       server.getConfiguration().getIncomingInterceptorClassNames().add(MyInterceptor.class.getName());
       server.start();
    }
@@ -59,12 +64,20 @@ public class RollbackTest extends ActiveMQTestBase {
       final String TOPIC = "myTopic";
       final String SUBSCRIPTION = "mySub";
 
-      try (ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("vm://0")) {
+      try (ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616")) {
          connectionFactory.setCallTimeout(1000); // fail fast
          connectionFactory.setReconnectAttempts(-1);
-         connectionFactory.setConfirmationWindowSize(1024 * 1024);
 
          try (Connection consumerConnection = connectionFactory.createConnection()) {
+
+            CountDownLatch failed = new CountDownLatch(1);
+            consumerConnection.setExceptionListener(new ExceptionListener() {
+               @Override
+               public void onException(JMSException exception) {
+                  failed.countDown();
+               }
+            });
+
             consumerConnection.start();
             final Session session = consumerConnection.createSession(Session.SESSION_TRANSACTED);
             Topic topic = session.createTopic(TOPIC);
@@ -77,17 +90,19 @@ public class RollbackTest extends ActiveMQTestBase {
             try {
                Message m = messageConsumer.receive(2000);
                assertNotNull(m);
-               // the interceptor will block this first rollback and trigger a failure, the failure will cause the client to re-attach its session
+               // the interceptor will block this first rollback and trigger a failure
                session.rollback();
                fail();
             } catch (JMSException jmsException) {
                // expected
             }
 
+            assertTrue(failed.await(10, TimeUnit.SECONDS));
+
             try {
                session.rollback();
             } catch (JMSException e) {
-               fail("Rollback failed again! Giving up. " + e.getMessage());
+               fail(e.getMessage(), e);
             }
 
             Message m = messageConsumer.receive(2000);
@@ -95,7 +110,7 @@ public class RollbackTest extends ActiveMQTestBase {
             try {
                session.commit();
             } catch (JMSException e) {
-               fail("Commit failed. " + e.getMessage());
+               fail("Commit failed. " + e.getMessage(), e);
             }
          }
       }
@@ -115,4 +130,4 @@ public class RollbackTest extends ActiveMQTestBase {
          return true;
       }
    }
-}
+}
\ No newline at end of file
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/cluster/JMSReconnectTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/cluster/JMSReconnectTest.java
index 2febe4aad2..b101924b76 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/cluster/JMSReconnectTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/cluster/JMSReconnectTest.java
@@ -58,19 +58,9 @@ public class JMSReconnectTest extends ActiveMQTestBase {
 
    private ActiveMQServer server;
 
-   //In this test we re-attach to the same node without restarting the server
-   @Test
-   public void testReattachSameNode() throws Exception {
-      testReconnectOrReattachSameNode(true);
-   }
-
    //In this test, we reconnect to the same node without restarting the server
    @Test
    public void testReconnectSameNode() throws Exception {
-      testReconnectOrReattachSameNode(false);
-   }
-
-   private void testReconnectOrReattachSameNode(boolean reattach) throws Exception {
       ActiveMQConnectionFactory jbcf = ActiveMQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF, new TransportConfiguration(INVM_CONNECTOR_FACTORY));
 
       jbcf.setBlockOnDurableSend(true);
@@ -78,10 +68,6 @@ public class JMSReconnectTest extends ActiveMQTestBase {
 
       jbcf.setReconnectAttempts(-1);
 
-      if (reattach) {
-         jbcf.setConfirmationWindowSize(1024 * 1024);
-      }
-
       // Note we set consumer window size to a value so we can verify that consumer credit re-sending
       // works properly on failover
       // The value is small enough that credits will have to be resent several time
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/remoting/ReconnectTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/remoting/ReconnectTest.java
index 931285da26..5debddfdb9 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/remoting/ReconnectTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/remoting/ReconnectTest.java
@@ -26,26 +26,22 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.activemq.artemis.api.core.ActiveMQException;
-import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException;
 import org.apache.activemq.artemis.api.core.Interceptor;
 import org.apache.activemq.artemis.api.core.QueueConfiguration;
 import org.apache.activemq.artemis.api.core.RoutingType;
 import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.api.core.client.ClientConsumer;
-import org.apache.activemq.artemis.api.core.client.ClientSession;
 import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
 import org.apache.activemq.artemis.api.core.client.FailoverEventType;
 import org.apache.activemq.artemis.api.core.client.ServerLocator;
 import org.apache.activemq.artemis.api.core.client.SessionFailureListener;
 import org.apache.activemq.artemis.core.client.impl.ClientSessionFactoryInternal;
 import org.apache.activemq.artemis.core.client.impl.ClientSessionInternal;
-import org.apache.activemq.artemis.core.protocol.core.Packet;
 import org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl;
 import org.apache.activemq.artemis.core.server.ActiveMQServer;
 import org.apache.activemq.artemis.core.server.ServerConsumer;
 import org.apache.activemq.artemis.core.server.ServerSession;
 import org.apache.activemq.artemis.core.server.impl.AddressInfo;
-import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
 import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
 import org.apache.activemq.artemis.utils.Wait;
 import org.junit.jupiter.api.Test;
@@ -315,52 +311,6 @@ public class ReconnectTest extends ActiveMQTestBase {
 
    }
 
-   @Test
-   public void testReattachTimeout() throws Exception {
-      ActiveMQServer server = createServer(true, true);
-      server.start();
-      // imitate session reattach timeout
-      Interceptor reattachInterceptor = new Interceptor() {
-
-         boolean reattached;
-
-         @Override
-         public boolean intercept(Packet packet, RemotingConnection connection) throws ActiveMQException {
-            if (!reattached && packet.getType() == PacketImpl.REATTACH_SESSION) {
-               reattached = true;
-               return false;
-            } else {
-               return true;
-            }
-
-         }
-      };
-      server.getRemotingService().addIncomingInterceptor(reattachInterceptor);
-
-      final long retryInterval = 50;
-      final double retryMultiplier = 1d;
-      final int reconnectAttempts = 1;
-      ServerLocator locator = createFactory(true).setCallTimeout(200).setRetryInterval(retryInterval).setRetryIntervalMultiplier(retryMultiplier).setReconnectAttempts(reconnectAttempts).setConfirmationWindowSize(-1);
-      ClientSessionFactoryInternal sf = (ClientSessionFactoryInternal) createSessionFactory(locator);
-      final CountDownLatch latch = new CountDownLatch(1);
-      sf.addFailoverListener(eventType -> {
-         if (eventType == FailoverEventType.FAILOVER_FAILED) {
-            latch.countDown();
-         }
-      });
-
-      ClientSession session = sf.createSession(false, true, true);
-      RemotingConnection conn = ((ClientSessionInternal) session).getConnection();
-      conn.fail(new ActiveMQNotConnectedException());
-
-      assertTrue(latch.await(1000, TimeUnit.MILLISECONDS));
-      assertTrue(session.isClosed());
-
-      session.close();
-      sf.close();
-      server.stop();
-   }
-
    @Test
    public void testClosingConsumerTimeout() throws Exception {
       ActiveMQServer server = createServer(true, true);


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


(artemis) 04/11: Disallow JsonUtil from performing deserialization

Posted by cl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 5a5c1385ffa5ada274c4e02aae8a8b7a785a6f23
Author: Clebert Suconic <cl...@apache.org>
AuthorDate: Thu May 21 10:58:54 2026 -0400

    Disallow JsonUtil from performing deserialization
    
    This serialization operation should only be used in a particular usage.
    We should also apply a filter more strict into this
    
    (cherry picked from commit 3e682aeb42d5c97e459513a34695cbc708fa577a)
    
    ARTEMIS-6246
---
 .../apache/activemq/artemis/api/core/JsonUtil.java |  75 ++++++-
 .../api/core/management/ManagementHelper.java      |   3 +-
 .../core/client/ActiveMQClientMessageBundle.java   |   5 +
 .../management/impl/ManagementServiceImpl.java     |  10 +-
 .../tests/integration/json/JsonSerialTest.java     | 225 +++++++++++++++++++++
 .../management/ManagementServiceImplTest.java      |  68 +++++++
 6 files changed, 377 insertions(+), 9 deletions(-)

diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/JsonUtil.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/JsonUtil.java
index 4aad12eeea..3e1434d82f 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/JsonUtil.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/JsonUtil.java
@@ -20,6 +20,7 @@ import javax.management.openmbean.CompositeData;
 import javax.management.openmbean.CompositeDataSupport;
 import java.io.ByteArrayInputStream;
 import java.io.StringReader;
+import java.lang.invoke.MethodHandles;
 import java.lang.reflect.Array;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -28,6 +29,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
+import org.apache.activemq.artemis.core.client.ActiveMQClientMessageBundle;
 import org.apache.activemq.artemis.json.JsonArray;
 import org.apache.activemq.artemis.json.JsonArrayBuilder;
 import org.apache.activemq.artemis.json.JsonNumber;
@@ -39,9 +41,58 @@ import org.apache.activemq.artemis.utils.Base64;
 import org.apache.activemq.artemis.utils.JsonLoader;
 import org.apache.activemq.artemis.utils.ObjectInputStreamWithClassLoader;
 import org.apache.activemq.artemis.utils.StringEscapeUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ObjectInputFilter;
 
 public final class JsonUtil {
 
+   private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+   private static final String SERIAL_FILTER;
+
+   static {
+      String filter = System.getProperty("artemis.json.composite.data.serial.filter");
+      if (filter == null) {
+         filter = System.getenv("ARTEMIS_JSON_COMPOSITE_DATA_SERIAL_FILTER");
+      }
+      if (filter == null) {
+         // this is the minimal necessary to deserialize a CompositeData
+         filter = "maxdepth=10;" +
+            "javax.management.openmbean.CompositeType;" +
+            "javax.management.openmbean.CompositeDataSupport;" +
+            "javax.management.openmbean.OpenType;" +
+            "javax.management.openmbean.ArrayType;" +
+            "javax.management.openmbean.SimpleType;" +
+            "javax.management.openmbean.TabularType;" +
+            "javax.management.openmbean.TabularDataSupport;" +
+            "java.util.TreeMap;" +
+            "java.util.Collections;" +
+            "java.util.Collections$UnmodifiableList;" +
+            "java.util.Collections$UnmodifiableCollection;" +
+            "java.util.Collections$UnmodifiableRandomAccessList;" +
+            "java.util.ArrayList;" +
+            "java.util.LinkedHashMap;" +
+            "java.util.HashMap;" +
+            "java.util.Map$Entry;" +
+            "java.util.Arrays;" +
+            "java.util.Arrays$ArrayList;" +
+            "java.lang.Object;" +
+            "java.lang.String;" +
+            "java.lang.Boolean;" +
+            "java.lang.Long;" +
+            "java.lang.Number;" +
+            "java.lang.Byte;" +
+            "java.lang.Double;" +
+            "java.lang.Float;" +
+            "java.lang.Integer;" +
+            "java.lang.Short;" +
+            "!*";
+      }
+      SERIAL_FILTER = filter;
+   }
+
    public static JsonArray toJSONArray(final Object[] array) throws Exception {
       JsonArrayBuilder jsonArray = JsonLoader.createArrayBuilder();
 
@@ -52,13 +103,17 @@ public final class JsonUtil {
    }
 
    public static Object[] fromJsonArray(final JsonArray jsonArray) throws Exception {
+      return fromJsonArray(jsonArray, true);
+   }
+
+   public static Object[] fromJsonArray(final JsonArray jsonArray, boolean allowCompositeDataSerialization) throws Exception {
       Object[] array = new Object[jsonArray.size()];
 
       for (int i = 0; i < jsonArray.size(); i++) {
          Object val = jsonArray.get(i);
 
          if (val instanceof JsonArray jsonArrayValue) {
-            Object[] inner = fromJsonArray(jsonArrayValue);
+            Object[] inner = fromJsonArray(jsonArrayValue, allowCompositeDataSerialization);
 
             array[i] = inner;
          } else if (val instanceof JsonObject jsonObject) {
@@ -71,7 +126,7 @@ public final class JsonUtil {
                Object innerVal = jsonObject.get(key);
 
                if (innerVal instanceof JsonArray jsonArrayValue) {
-                  innerVal = fromJsonArray(jsonArrayValue);
+                  innerVal = fromJsonArray(jsonArrayValue, allowCompositeDataSerialization);
                } else if (innerVal instanceof JsonString jsonString) {
                   innerVal = jsonString.getString();
                } else if (innerVal == JsonValue.FALSE) {
@@ -93,12 +148,26 @@ public final class JsonUtil {
                   innerVal = innerMap;
                }
                if (CompositeData.class.getName().equals(key)) {
+                  if (!allowCompositeDataSerialization) {
+                     throw ActiveMQClientMessageBundle.BUNDLE.serializationNotAllowedOnManagement();
+                  }
                   Object[] data = (Object[]) innerVal;
                   CompositeData[] cds = new CompositeData[data.length];
                   for (int i1 = 0; i1 < data.length; i1++) {
                      String dataConverted = convertJsonValue(data[i1], String.class).toString();
                      try (ObjectInputStreamWithClassLoader ois = new ObjectInputStreamWithClassLoader(new ByteArrayInputStream(Base64.decode(dataConverted)))) {
-                        ois.setAllowList("java.util,java.lang,javax.management");
+                        ObjectInputFilter serialFilter = ObjectInputFilter.Config.createFilter(SERIAL_FILTER);
+                        if (logger.isDebugEnabled()) {
+                           ois.setObjectInputFilter(info -> {
+                              ObjectInputFilter.Status status = serialFilter.checkInput(info);
+                              if (status == ObjectInputFilter.Status.REJECTED && info.serialClass() != null) {
+                                 logger.debug("Serialization class rejected: {} (depth={}, refs={})", info.serialClass().getName(), info.depth(), info.references());
+                              }
+                              return status;
+                           });
+                        } else {
+                           ois.setObjectInputFilter(serialFilter);
+                        }
                         cds[i1] = (CompositeDataSupport) ois.readObject();
                      }
                   }
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ManagementHelper.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ManagementHelper.java
index 76af6a7504..a35bf41440 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ManagementHelper.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ManagementHelper.java
@@ -207,7 +207,8 @@ public final class ManagementHelper {
       if (jsonString != null) {
          JsonArray jsonArray = JsonUtil.readJsonArray(jsonString);
 
-         return JsonUtil.fromJsonArray(jsonArray);
+         // this is used on client 2 server communication and serialization is never needed at this point
+         return JsonUtil.fromJsonArray(jsonArray, false);
       } else {
          return null;
       }
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/ActiveMQClientMessageBundle.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/ActiveMQClientMessageBundle.java
index 671de7c909..36c06b9bee 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/ActiveMQClientMessageBundle.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/ActiveMQClientMessageBundle.java
@@ -19,6 +19,7 @@ package org.apache.activemq.artemis.core.client;
 import org.apache.activemq.artemis.api.core.ActiveMQAddressFullException;
 import org.apache.activemq.artemis.api.core.ActiveMQConnectionTimedOutException;
 import org.apache.activemq.artemis.api.core.ActiveMQDisconnectedException;
+import org.apache.activemq.artemis.api.core.ActiveMQException;
 import org.apache.activemq.artemis.api.core.ActiveMQIllegalStateException;
 import org.apache.activemq.artemis.api.core.ActiveMQInterceptorRejectedPacketException;
 import org.apache.activemq.artemis.api.core.ActiveMQInternalErrorException;
@@ -234,4 +235,8 @@ public interface ActiveMQClientMessageBundle {
 
    @Message(id = 219070, value = "The server discovery is disabled by default. See the Server Discovery section in the user manual for security requirements and enablement.")
    IllegalStateException serverDiscoveryDisabled();
+
+   @Message(id = 219071, value = "Serialization is not allowed for management operations")
+   ActiveMQException serializationNotAllowedOnManagement();
+
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/impl/ManagementServiceImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/impl/ManagementServiceImpl.java
index fbc00f0ca0..af6222480f 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/impl/ManagementServiceImpl.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/impl/ManagementServiceImpl.java
@@ -667,13 +667,13 @@ public class ManagementServiceImpl implements ManagementService {
       String operation = message.getStringProperty(ManagementHelper.HDR_OPERATION_NAME);
 
       if (operation != null) {
-         Object[] params = ManagementHelper.retrieveOperationParameters(message);
+         try {
+            Object[] params = ManagementHelper.retrieveOperationParameters(message);
 
-         if (params == null) {
-            params = new Object[0];
-         }
+            if (params == null) {
+               params = new Object[0];
+            }
 
-         try {
             Object result = invokeOperation(resourceName, operation, params, auth);
 
             ManagementHelper.storeResult(reply, result);
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/json/JsonSerialTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/json/JsonSerialTest.java
new file mode 100644
index 0000000000..a9494a72e2
--- /dev/null
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/json/JsonSerialTest.java
@@ -0,0 +1,225 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.tests.integration.json;
+
+import javax.management.openmbean.CompositeData;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InvalidClassException;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.activemq.artemis.api.core.ActiveMQException;
+import org.apache.activemq.artemis.api.core.JsonUtil;
+import org.apache.activemq.artemis.api.core.Message;
+import org.apache.activemq.artemis.api.core.SimpleString;
+import org.apache.activemq.artemis.core.message.impl.CoreMessage;
+import org.apache.activemq.artemis.json.JsonArray;
+import org.apache.activemq.artemis.json.JsonArrayBuilder;
+import org.apache.activemq.artemis.protocol.amqp.broker.AMQPStandardMessage;
+import org.apache.activemq.artemis.reader.TextMessageUtil;
+import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
+import org.apache.activemq.artemis.utils.Base64;
+import org.apache.activemq.artemis.utils.JsonLoader;
+import org.apache.activemq.artemis.utils.RandomUtil;
+import org.apache.activemq.artemis.utils.SpawnedVMSupport;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import org.apache.activemq.artemis.protocol.amqp.util.NettyWritable;
+import org.apache.qpid.proton.Proton;
+import org.apache.qpid.proton.amqp.messaging.AmqpValue;
+import org.apache.qpid.proton.amqp.messaging.ApplicationProperties;
+import org.apache.qpid.proton.amqp.messaging.Header;
+import org.apache.qpid.proton.amqp.messaging.Properties;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class JsonSerialTest extends ActiveMQTestBase {
+
+   private static final int NON_RELATED_FAILURE = -1;
+   private static final int DESERIALIZED = 1;
+   private static final int DESERIALIZED_CCE = 2;
+   private static final int REJECTED_SERIALIZATION = 3;
+
+   @Test
+   public void testCoreCompositeData() throws Exception {
+      CoreMessage message = new CoreMessage();
+      message.initBuffer(1024);
+      message.putStringProperty("propKey", "propValue");
+      message.putIntProperty("propInt", 1);
+      message.putCharProperty("propChar", 'a');
+      message.putBytesProperty("propBytes", RandomUtil.randomBytes());
+      message.putByteProperty("propByte", RandomUtil.randomByte());
+      message.putShortProperty("propShort", RandomUtil.randomShort());
+      message.putDoubleProperty("propDouble", RandomUtil.randomDouble());
+      message.putFloatProperty("propFloat", RandomUtil.randomFloat());
+      message.putBooleanProperty("propBoolean", RandomUtil.randomBoolean());
+      message.putLongProperty("propLong", RandomUtil.randomLong());
+      message.putStringProperty(SimpleString.of("propSimpleString"), RandomUtil.randomUUIDSimpleString());
+
+      TextMessageUtil.writeBodyText(message.getBodyBuffer(), SimpleString.of("hello"));
+
+      internalMessageTest(message);
+   }
+
+   @Test
+   public void testAMQPCompositeData() throws Exception {
+      org.apache.qpid.proton.message.impl.MessageImpl protonMessage = (org.apache.qpid.proton.message.impl.MessageImpl) Proton.message();
+
+      Header header = new Header();
+      header.setDurable(true);
+      protonMessage.setHeader(header);
+
+      Properties properties = new Properties();
+      properties.setMessageId(UUID.randomUUID());
+      properties.setTo("testAddress");
+      properties.setCreationTime(new Date(System.currentTimeMillis()));
+      protonMessage.setProperties(properties);
+
+      ApplicationProperties applicationProperties = new ApplicationProperties(new LinkedHashMap<>());
+      applicationProperties.getValue().put("propKey", "propValue");
+      applicationProperties.getValue().put("propInt", 1);
+      applicationProperties.getValue().put("propByte", RandomUtil.randomByte());
+      applicationProperties.getValue().put("propShort", RandomUtil.randomShort());
+      applicationProperties.getValue().put("propDouble", RandomUtil.randomDouble());
+      applicationProperties.getValue().put("propFloat", RandomUtil.randomFloat());
+      applicationProperties.getValue().put("propBoolean", RandomUtil.randomBoolean());
+      applicationProperties.getValue().put("propLong", RandomUtil.randomLong());
+      protonMessage.setApplicationProperties(applicationProperties);
+
+      protonMessage.setBody(new AmqpValue("hello"));
+
+      ByteBuf nettyBuffer = Unpooled.buffer(10 * 1024);
+      protonMessage.encode(new NettyWritable(nettyBuffer));
+      byte[] encodedMessage = new byte[nettyBuffer.writerIndex()];
+      nettyBuffer.readBytes(encodedMessage);
+
+      AMQPStandardMessage message = new AMQPStandardMessage(0, encodedMessage, null);
+
+      internalMessageTest(message);
+   }
+
+   private void internalMessageTest(Message message) throws Exception {
+      CompositeData data = message.toCompositeData(10, 1);
+      JsonArrayBuilder builder = JsonLoader.createArrayBuilder();
+      JsonUtil.addToArray(new CompositeData[] {data}, builder);
+
+      String json = builder.build().toString();
+
+      JsonArray arrayOutput = JsonUtil.readJsonArray(json);
+      Object[] output = JsonUtil.fromJsonArray(arrayOutput);
+      HashMap<String, Object> objectHashMap = (HashMap<String, Object>) output[0];
+      CompositeData[] compositeDataOutput = (CompositeData[]) objectHashMap.get("javax.management.openmbean.CompositeData");
+      assertEquals(1, compositeDataOutput.length);
+      assertEquals(data, compositeDataOutput[0]);
+      assertThrows(ActiveMQException.class, () -> JsonUtil.fromJsonArray(arrayOutput, false));
+
+      {
+         // this portion is to validate a very restrictive filter would REJECT everything
+         Process process = SpawnedVMSupport.spawnVM(JsonSerialTest.class.getName(), new String[]{"-Dartemis.json.composite.data.serial.filter=!*"}, true, json);
+         assertTrue(process.waitFor(10, TimeUnit.SECONDS));
+         assertEquals(REJECTED_SERIALIZATION, process.exitValue(), "Expected to reject deserialization");
+      }
+
+      {
+         Process process = SpawnedVMSupport.spawnVM(JsonSerialTest.class.getName(), json);
+         assertTrue(process.waitFor(10, TimeUnit.SECONDS));
+         assertEquals(DESERIALIZED, process.exitValue(), "Expected to allow deserialization");
+      }
+   }
+
+   @Test
+   public void testInvalidDataByDepth() throws Exception {
+      int n = 10;
+      final ArrayList<Object> top = new ArrayList<>();
+      ArrayList<Object> current = top;
+      for (int i = 0; i < n; i++) {
+         ArrayList<Object> sub = new ArrayList<>();
+         current.add(sub);
+         current.add("top" + i);
+         sub.add("a" + i);
+         current = sub;
+      }
+
+      // passing in invalidData to exploit serialization
+      // if serialization is successful this will cause ClassCastException on JsonUtil
+      internalInvalidDataTest(createCompositeData(top));
+   }
+
+   @Test
+   public void testFilteredOutClass() throws Exception {
+      // passing in invalidData to exploit serialization
+      // if serialization is successful this will cause ClassCastException on JsonUtil
+      internalInvalidDataTest(createCompositeData(new HashSet<>()));
+   }
+
+   private static String createCompositeData(Object payload) throws Exception {
+      // Serialize the payload to base64
+      ByteArrayOutputStream baos = new ByteArrayOutputStream();
+      ObjectOutputStream oos = new ObjectOutputStream(baos);
+      oos.writeObject(payload);
+      oos.flush();
+      String serializedPayload = Base64.encodeBytes(baos.toByteArray());
+
+      return "[{\"javax.management.openmbean.CompositeData\":[\"" + serializedPayload + "\"]}]";
+   }
+
+   private static void internalInvalidDataTest(String json) throws Exception {
+      {
+         Process process = SpawnedVMSupport.spawnVM(JsonSerialTest.class.getName(), true, json);
+         assertTrue(process.waitFor(10, TimeUnit.SECONDS));
+         assertEquals(REJECTED_SERIALIZATION, process.exitValue(), "Expected to reject deserialization");
+      }
+
+      {
+         // Positive test allowing everything, to validate the test infrastructure itself.
+         Process process = SpawnedVMSupport.spawnVM(JsonSerialTest.class.getName(), new String[]{"-Dartemis.json.composite.data.serial.filter=*"}, true, json);
+         assertTrue(process.waitFor(10, TimeUnit.SECONDS));
+         assertEquals(DESERIALIZED_CCE, process.exitValue(), "Expected to allow deserialization");
+      }
+   }
+
+   public static void main(String[] arg) {
+      try {
+         String json = arg[0];
+         JsonArray arrayOutput = JsonUtil.readJsonArray(json);
+         Object[] output = JsonUtil.fromJsonArray(arrayOutput);
+         assertNotNull(output);
+         System.exit(DESERIALIZED);
+      } catch (InvalidClassException e) {
+         e.printStackTrace(System.out);
+         System.exit(REJECTED_SERIALIZATION);
+      } catch (ClassCastException e) {
+         // if I got ClassCastException, it means deserialization was allowed to happen
+         e.printStackTrace(System.out);
+         System.exit(DESERIALIZED_CCE);
+      } catch (Throwable e) {
+         e.printStackTrace(System.out);
+         System.exit(NON_RELATED_FAILURE);
+      }
+   }
+}
\ No newline at end of file
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ManagementServiceImplTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ManagementServiceImplTest.java
index 970dcdeab7..df1c90dbc4 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ManagementServiceImplTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ManagementServiceImplTest.java
@@ -16,6 +16,8 @@
  */
 package org.apache.activemq.artemis.tests.integration.management;
 
+import javax.management.openmbean.CompositeData;
+
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
@@ -23,13 +25,22 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import org.apache.activemq.artemis.api.core.ICoreMessage;
+import org.apache.activemq.artemis.api.core.JsonUtil;
 import org.apache.activemq.artemis.api.core.Message;
+import org.apache.activemq.artemis.api.core.QueueConfiguration;
 import org.apache.activemq.artemis.api.core.SimpleString;
+import org.apache.activemq.artemis.api.core.client.ClientConsumer;
+import org.apache.activemq.artemis.api.core.client.ClientMessage;
+import org.apache.activemq.artemis.api.core.client.ClientProducer;
+import org.apache.activemq.artemis.api.core.client.ClientSession;
+import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
+import org.apache.activemq.artemis.api.core.client.ServerLocator;
 import org.apache.activemq.artemis.api.core.management.AddressControl;
 import org.apache.activemq.artemis.api.core.management.ManagementHelper;
 import org.apache.activemq.artemis.api.core.management.QueueControl;
 import org.apache.activemq.artemis.api.core.management.ResourceNames;
 import org.apache.activemq.artemis.core.config.Configuration;
+import org.apache.activemq.artemis.core.client.impl.ClientMessageImpl;
 import org.apache.activemq.artemis.core.message.impl.CoreMessage;
 import org.apache.activemq.artemis.core.persistence.impl.nullpm.NullStorageManager;
 import org.apache.activemq.artemis.core.server.ActiveMQServer;
@@ -37,10 +48,13 @@ import org.apache.activemq.artemis.core.server.ActiveMQServers;
 import org.apache.activemq.artemis.core.server.Queue;
 import org.apache.activemq.artemis.core.server.impl.AddressInfo;
 import org.apache.activemq.artemis.core.server.management.impl.ManagementServiceImpl;
+import org.apache.activemq.artemis.json.JsonArrayBuilder;
 import org.apache.activemq.artemis.reader.MessageUtil;
+import org.apache.activemq.artemis.reader.TextMessageUtil;
 import org.apache.activemq.artemis.tests.integration.server.FakeStorageManager;
 import org.apache.activemq.artemis.tests.unit.core.postoffice.impl.fakes.FakeQueue;
 import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
+import org.apache.activemq.artemis.utils.JsonLoader;
 import org.apache.activemq.artemis.utils.RandomUtil;
 import org.apache.activemq.artemis.utils.UUID;
 import org.apache.activemq.artemis.utils.UUIDGenerator;
@@ -69,6 +83,60 @@ public class ManagementServiceImplTest extends ActiveMQTestBase {
       assertTrue(ManagementHelper.hasOperationSucceeded(reply));
    }
 
+   @Test
+   public void testHandleInvalidSerialization() throws Exception {
+
+      CoreMessage messageCD = new CoreMessage().initBuffer(1024);
+      TextMessageUtil.writeBodyText(messageCD.getBodyBuffer(), RandomUtil.randomUUIDSimpleString());
+      messageCD.putStringProperty("hello", "its me");
+
+      CompositeData compositeData = messageCD.toCompositeData(10, 10);
+
+      ActiveMQServer server = createServer(false, false);
+      server.start();
+
+      SimpleString replyQueue = RandomUtil.randomUUIDSimpleString();
+
+      try (
+         ServerLocator locator = createInVMNonHALocator();
+         ClientSessionFactory sf = createSessionFactory(locator);
+         ClientSession session = sf.createSession(false, true, true);
+      ) {
+         session.createQueue(QueueConfiguration.of(replyQueue).setAddress(replyQueue).setDurable(false).setTemporary(true));
+
+         ClientProducer producer = session.createProducer(server.getConfiguration().getManagementAddress());
+         ClientConsumer consumer = session.createConsumer(replyQueue);
+
+         session.start();
+
+         ClientMessage message = session.createMessage(false);
+
+         message.putStringProperty(ManagementHelper.HDR_RESOURCE_NAME, SimpleString.of(ResourceNames.BROKER));
+         // we don't need a valid operation name to trigger JSONUtil deserialization
+         message.putStringProperty(ManagementHelper.HDR_OPERATION_NAME, SimpleString.of("idontcare"));
+
+         message.putStringProperty(ClientMessageImpl.REPLYTO_HEADER_NAME, replyQueue);
+
+         JsonArrayBuilder arrayBuilder = JsonLoader.createArrayBuilder();
+         JsonUtil.addToArray(new CompositeData[]{compositeData}, arrayBuilder);
+         String json = arrayBuilder.build().toString();
+
+         message.getBodyBuffer().writeNullableSimpleString(SimpleString.of(json));
+         producer.send(message);
+
+         ClientMessage reply = consumer.receive(5000);
+         assertNotNull(reply);
+         assertFalse(ManagementHelper.hasOperationSucceeded(reply));
+         SimpleString resultString = reply.getReadOnlyBodyBuffer().readNullableSimpleString();
+         assertNotNull(resultString);
+         // verify the operation fails due to "Serialization not allowed"
+         assertTrue(String.valueOf(resultString).contains("AMQ219071"), () -> "Expected to fail because of AMQ219070 (serialization disallowed), invalidResult=" + resultString);
+
+         producer.close();
+         consumer.close();
+      }
+   }
+
    @Test
    public void testHandleManagementMessageWithOperationWhichFails() throws Exception {
       Configuration config = createBasicConfig().setJMXManagementEnabled(false);


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


(artemis) 03/11: Disable discovery by default

Posted by cl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit ac6041c3914d3fb911d81519c52086f7bf920619
Author: Domenico Francesco Bruscino <br...@apache.org>
AuthorDate: Wed Jun 24 12:25:39 2026 +0200

    Disable discovery by default
    
    Discovery is now disabled unless explicitly enabled.
    Broker startup fails fast when broadcast or discovery group
    definitions are present but the property is unset or false.
    Client locators fail at construction when discovery is
    configured but disabled.
    
    Assisted-by: Cursor
    
    (cherry picked from commit cb012acf6e9111b21b19d0bfb6f9b0d62faf0118)
    
    ARTEMIS-6246
---
 .../activemq/artemis/cli/commands/Create.java      |  14 +++
 .../activemq/artemis/cli/commands/Upgrade.java     |   2 +-
 .../config/ServerLocatorConfigTestAccessor.java    |  24 ++++
 .../cli/commands/CreateDiscoveryPropertyTest.java  |  91 +++++++++++++++
 .../activemq/artemis/util/JVMArgumentTest.java     |  13 ++-
 .../org/apache/activemq/cli/test/ArtemisTest.java  |   2 +-
 .../org/apache/activemq/cli/test/CliTestBase.java  |   9 ++
 .../artemis/api/config/ServerLocatorConfig.java    |  27 +++++
 .../core/client/ActiveMQClientMessageBundle.java   |   3 +
 .../core/client/impl/ServerLocatorImpl.java        |  14 +++
 .../ServerLocatorConfigDiscoveryPropertyTest.java  | 126 +++++++++++++++++++++
 .../config/ServerLocatorConfigTestAccessor.java    |  24 ++++
 .../artemis/uri/ConnectionFactoryURITest.java      |  18 ++-
 .../artemis/core/config/ConfigurationUtils.java    |  12 ++
 .../artemis/core/server/ActiveMQMessageBundle.java |   6 +
 .../core/server/impl/ActiveMQServerImpl.java       |   2 +
 .../config/ServerLocatorConfigTestAccessor.java    |  24 ++++
 ...adcastDiscoveryConfigurationValidationTest.java | 126 +++++++++++++++++++++
 .../config/impl/HAPolicyConfigurationTest.java     |   6 +
 .../artemis/tests/util/ServerTestBase.java         |   9 ++
 docs/user-manual/clusters.adoc                     |  26 +++++
 docs/user-manual/threat-model.adoc                 |   5 +
 docs/user-manual/versions.adoc                     |   1 +
 .../config/ServerLocatorConfigTestAccessor.java    |  24 ++++
 .../artemis/tests/util/ActiveMQTestBase.java       |   9 ++
 tests/db-tests/pom.xml                             |   1 +
 .../integration/client/SessionFactoryTest.java     |   1 +
 .../bridge/BridgeWithDiscoveryGroupStartTest.java  |   1 +
 .../MessageRedistributionWithDiscoveryTest.java    |   1 +
 .../SymmetricClusterWithDiscoveryTest.java         |   8 ++
 .../TwoWayTwoNodeClusterWithDiscoveryTest.java     |  11 +-
 .../DiscoveryClusterWithBackupFailoverTest.java    |   8 ++
 .../HAClientTopologyWithDiscoveryTest.java         |  10 +-
 .../jms/ActiveMQConnectionFactoryTest.java         |   1 +
 .../integration/jms/SimpleJNDIClientTest.java      |   5 +-
 .../ConnectionFactorySerializationTest.java        |  10 +-
 ...nectionFactoryWithJGroupsSerializationTest.java |   1 +
 .../jms/server/JMSServerDeployerTest.java          |   1 +
 .../management/BroadcastGroupControlTest.java      |   1 +
 .../management/ClusterConnectionControl2Test.java  |   8 +-
 .../management/ClusterConnectionControlTest.java   |   8 +-
 .../management/ConnectionRouterControlTest.java    |   9 +-
 .../JGroupsChannelBroadcastGroupControlTest.java   |   8 +-
 .../JGroupsFileBroadcastGroupControlTest.java      |   1 +
 .../tests/integration/ra/ResourceAdapterTest.java  |   7 +-
 .../integration/routing/AmqpRedirectTest.java      |   8 ++
 .../tests/integration/routing/KeyTypeTest.java     |   7 ++
 .../integration/routing/MQTTRedirectTest.java      |   8 ++
 .../tests/integration/routing/RedirectTest.java    |   8 ++
 .../artemis/tests/unit/ra/ResourceAdapterTest.java |   2 +
 50 files changed, 701 insertions(+), 50 deletions(-)

diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java
index f84e18647d..e533ac739f 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java
@@ -34,6 +34,7 @@ import org.apache.activemq.artemis.core.server.JournalType;
 import org.apache.activemq.artemis.core.server.cluster.impl.MessageLoadBalancingType;
 import org.apache.activemq.artemis.nativo.jlibaio.LibaioContext;
 import org.apache.activemq.artemis.nativo.jlibaio.LibaioFile;
+import org.apache.activemq.artemis.api.config.ServerLocatorConfig;
 import org.apache.activemq.artemis.utils.FileUtil;
 import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
 import picocli.CommandLine.Command;
@@ -165,6 +166,9 @@ public class Create extends InstallAbstract {
    @Option(names = "--clustered", description = "Enable clustering.")
    private boolean clustered = false;
 
+   @Option(names = "--discovery-enabled", description = "Enable server discovery.", hidden = true)
+   private boolean discoveryEnabled;
+
    @Option(names = "--max-hops", description = "Number of hops on the cluster configuration.")
    private int maxHops = DEFAULT_MAX_HOPS;
 
@@ -721,6 +725,10 @@ public class Create extends InstallAbstract {
 
 
       if (clustered) {
+         if (staticNode == null && !discoveryEnabled) {
+            throw new IllegalArgumentException("Clustered instances without --static-cluster use discovery groups, which are disabled by default. Supply static configuration, or see the Server Discovery section in the user manual for security requirements and enablement.");
+         }
+
          filters.put("${host}", getHostForClustered());
          if (name == null) {
             name = getHostForClustered();
@@ -778,6 +786,12 @@ public class Create extends InstallAbstract {
       String processedJavaOptions = getJavaOptions();
       String processedJavaUtilityOptions = getJavaUtilityOptions();
 
+      if (discoveryEnabled) {
+         String discoveryJvmArg = "-D" + ServerLocatorConfig.DISCOVERY_ENABLED_PROPERTY + "=true ";
+         processedJavaOptions = discoveryJvmArg + processedJavaOptions;
+         processedJavaUtilityOptions = discoveryJvmArg + processedJavaUtilityOptions;
+      }
+
       addScriptFilters(filters, getHome(), getInstance(), etcFolder, dataFolder, oomeDumpFile, javaMemory, processedJavaOptions, processedJavaUtilityOptions, role);
 
       boolean allowAnonymous = isAllowAnonymous();
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Upgrade.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Upgrade.java
index 072ac20740..8d355f2e58 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Upgrade.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Upgrade.java
@@ -41,7 +41,7 @@ import picocli.CommandLine.Command;
 public class Upgrade extends InstallAbstract {
 
    // These are the JVM arguments we must keep between upgrades
-   private static final String[] KEEPING_JVM_ARGUMENTS = new String[]{"-Xmx", "-Djava.security.auth.login.config", "-Dhawtio.roles="};
+   private static final String[] KEEPING_JVM_ARGUMENTS = new String[]{"-Xmx", "-Djava.security.auth.login.config", "-Dhawtio.roles=", "-Dartemis.discovery.enabled="};
 
    // These are the renamed JVM arguments we must keep between upgrades
    // The keys MUST be one of the current KEEPING_JVM_ARGUMENTS args above, the values then being its old variant to be renamed.
diff --git a/artemis-cli/src/test/java/org/apache/activemq/artemis/api/config/ServerLocatorConfigTestAccessor.java b/artemis-cli/src/test/java/org/apache/activemq/artemis/api/config/ServerLocatorConfigTestAccessor.java
new file mode 100644
index 0000000000..5754527b04
--- /dev/null
+++ b/artemis-cli/src/test/java/org/apache/activemq/artemis/api/config/ServerLocatorConfigTestAccessor.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.api.config;
+
+public class ServerLocatorConfigTestAccessor {
+
+   public static void setDiscoveryEnabled(Boolean enabled) {
+      ServerLocatorConfig.setDiscoveryEnabled(enabled);
+   }
+}
diff --git a/artemis-cli/src/test/java/org/apache/activemq/artemis/cli/commands/CreateDiscoveryPropertyTest.java b/artemis-cli/src/test/java/org/apache/activemq/artemis/cli/commands/CreateDiscoveryPropertyTest.java
new file mode 100644
index 0000000000..97a2367d9e
--- /dev/null
+++ b/artemis-cli/src/test/java/org/apache/activemq/artemis/cli/commands/CreateDiscoveryPropertyTest.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.cli.commands;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+
+import org.apache.activemq.artemis.cli.Artemis;
+import org.apache.activemq.artemis.api.config.ServerLocatorConfig;
+import org.apache.activemq.cli.test.CliTestBase;
+import org.junit.jupiter.api.Test;
+
+public class CreateDiscoveryPropertyTest extends CliTestBase {
+
+   private static final String DISCOVERY_JVM_ARG = "-D" + ServerLocatorConfig.DISCOVERY_ENABLED_PROPERTY + "=true";
+
+   @Test
+   public void testClusteredCreateFailsWhenDiscoveryDisabled() {
+      File testInstance = new File(temporaryFolder, "clustered-discovery-disabled");
+
+      assertThrows(IllegalArgumentException.class,
+         () -> Artemis.internalExecute(clusteredCreateArgs(testInstance)));
+   }
+
+   @Test
+   public void testClusteredCreateSucceedsWithDiscoveryEnabledFlag() throws Exception {
+      File testInstance = new File(temporaryFolder, "clustered-discovery-enabled");
+
+      Artemis.internalExecute(clusteredCreateArgsWithDiscoveryFlag(testInstance));
+
+      assertTrue(new File(testInstance, "etc/broker.xml").exists());
+
+      String brokerProfile = Files.readString(new File(testInstance, "etc/artemis.profile").toPath(), StandardCharsets.UTF_8);
+      String utilityProfile = Files.readString(new File(testInstance, "etc/artemis-utility.profile").toPath(), StandardCharsets.UTF_8);
+
+      assertTrue(brokerProfile.contains(DISCOVERY_JVM_ARG));
+      assertTrue(utilityProfile.contains(DISCOVERY_JVM_ARG));
+   }
+
+   @Test
+   public void testStaticClusterCreateSucceedsWhenDiscoveryDisabled() throws Exception {
+      File testInstance = new File(temporaryFolder, "static-cluster");
+
+      Artemis.internalExecute(staticClusterCreateArgs(testInstance));
+
+      assertTrue(new File(testInstance, "etc/broker.xml").exists());
+   }
+
+   private static String[] clusteredCreateArgs(File instance) {
+      return new String[] {
+         "create", instance.getAbsolutePath(), "--silent", "--no-autotune", "--force",
+         "--user", "admin", "--password", "admin",
+         "--cluster-user", "admin", "--cluster-password", "admin", "--clustered"
+      };
+   }
+
+   private static String[] clusteredCreateArgsWithDiscoveryFlag(File instance) {
+      return new String[] {
+         "create", instance.getAbsolutePath(), "--silent", "--no-autotune", "--force",
+         "--user", "admin", "--password", "admin",
+         "--cluster-user", "admin", "--cluster-password", "admin", "--clustered", "--discovery-enabled"
+      };
+   }
+
+   private static String[] staticClusterCreateArgs(File instance) {
+      return new String[] {
+         "create", instance.getAbsolutePath(), "--silent", "--no-autotune", "--force",
+         "--user", "admin", "--password", "admin",
+         "--cluster-user", "admin", "--cluster-password", "admin",
+         "--static-cluster", "tcp://localhost:61616,tcp://localhost:61617"
+      };
+   }
+}
diff --git a/artemis-cli/src/test/java/org/apache/activemq/artemis/util/JVMArgumentTest.java b/artemis-cli/src/test/java/org/apache/activemq/artemis/util/JVMArgumentTest.java
index 3505e12288..8f1b29b234 100644
--- a/artemis-cli/src/test/java/org/apache/activemq/artemis/util/JVMArgumentTest.java
+++ b/artemis-cli/src/test/java/org/apache/activemq/artemis/util/JVMArgumentTest.java
@@ -48,25 +48,26 @@ public class JVMArgumentTest {
       final String arguments;
       if (useNewArgPropName) {
          // Uses new args prop -Dhawtio.roles=
-         arguments = "IF \"%JAVA_ARGS%\"==\"\" (set JAVA_ARGS= -must-go -XX:AutoBoxCacheMax=20000 -XX:+PrintClassHistogram  -XX:+UseG1GC -XX:+UseStringDeduplication -Xms333M -Xmx77G -Dhawtio.disableProxy=true -Dhawtio.offline=true -Dhawtio.realm=activemq -Dhawtio.roles=amq -Dhawtio.rolePrincipalClasses=org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal -Dhawtio.http.strictTransportSecurity=max-age=31536000;includeSubDomains;preload -Djolokia.policyLocation=classpath:jolokia [...]
+         arguments = "IF \"%JAVA_ARGS%\"==\"\" (set JAVA_ARGS= -must-go -XX:AutoBoxCacheMax=20000 -XX:+PrintClassHistogram  -XX:+UseG1GC -XX:+UseStringDeduplication -Xms333M -Xmx77G -Dartemis.discovery.enabled=true -Dhawtio.disableProxy=true -Dhawtio.offline=true -Dhawtio.realm=activemq -Dhawtio.roles=amq -Dhawtio.rolePrincipalClasses=org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal -Dhawtio.http.strictTransportSecurity=max-age=31536000;includeSubDomains;preload -Djolokia [...]
       } else {
          // Uses old args prop -Dhawtio.role=
-         arguments = "IF \"%JAVA_ARGS%\"==\"\" (set JAVA_ARGS= -must-go -XX:AutoBoxCacheMax=20000 -XX:+PrintClassHistogram  -XX:+UseG1GC -XX:+UseStringDeduplication -Xms333M -Xmx77G -Dhawtio.disableProxy=true -Dhawtio.offline=true -Dhawtio.realm=activemq -Dhawtio.role=amq -Dhawtio.rolePrincipalClasses=org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal -Dhawtio.http.strictTransportSecurity=max-age=31536000;includeSubDomains;preload -Djolokia.policyLocation=classpath:jolokia- [...]
+         arguments = "IF \"%JAVA_ARGS%\"==\"\" (set JAVA_ARGS= -must-go -XX:AutoBoxCacheMax=20000 -XX:+PrintClassHistogram  -XX:+UseG1GC -XX:+UseStringDeduplication -Xms333M -Xmx77G -Dartemis.discovery.enabled=true -Dhawtio.disableProxy=true -Dhawtio.offline=true -Dhawtio.realm=activemq -Dhawtio.role=amq -Dhawtio.rolePrincipalClasses=org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal -Dhawtio.http.strictTransportSecurity=max-age=31536000;includeSubDomains;preload -Djolokia. [...]
       }
 
       String prefix = "IF \"%JAVA_ARGS%\"==\"\" (set JAVA_ARGS= ";
 
-      String[] fixedArguments = new String[]{"-Xmx", "-Xms", "-Dhawtio.roles="};
+      String[] fixedArguments = new String[]{"-Xmx", "-Xms", "-Dhawtio.roles=", "-Dartemis.discovery.enabled="};
       Map<String, String> keepArgumentsAlternates = Map.of("-Dhawtio.roles=", "-Dhawtio.role=");
 
       Map<String, String> usedArgs = new HashMap<>();
       JVMArgumentParser.parseOriginalArgs(prefix, "\"", arguments, keepArgumentsAlternates, fixedArguments, usedArgs);
-      assertEquals(3, usedArgs.size());
+      assertEquals(4, usedArgs.size());
       assertEquals("-Xmx77G", usedArgs.get("-Xmx"));
       assertEquals("-Xms333M", usedArgs.get("-Xms"));
       assertEquals("-Dhawtio.roles=amq", usedArgs.get("-Dhawtio.roles="));
+      assertEquals("-Dartemis.discovery.enabled=true", usedArgs.get("-Dartemis.discovery.enabled="));
 
-      String newLine = "IF \"%JAVA_ARGS%\"==\"\" (set JAVA_ARGS= -XX:AutoBoxCacheMax=20000 -XX:+PrintClassHistogram  -XX:+UseG1GC -XX:+UseStringDeduplication -Xms512M -Xmx1G -Dhawtio.disableProxy=true -Dhawtio.offline=true -Dhawtio.realm=activemq -Dhawtio.roles=replaceThisRole -Dhawtio.rolePrincipalClasses=org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal -Djolokia.policyLocation=classpath:jolokia-access.xml)";
+      String newLine = "IF \"%JAVA_ARGS%\"==\"\" (set JAVA_ARGS= -XX:AutoBoxCacheMax=20000 -XX:+PrintClassHistogram  -XX:+UseG1GC -XX:+UseStringDeduplication -Xms512M -Xmx1G -Dartemis.discovery.enabled=false -Dhawtio.disableProxy=true -Dhawtio.offline=true -Dhawtio.realm=activemq -Dhawtio.roles=replaceThisRole -Dhawtio.rolePrincipalClasses=org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal -Djolokia.policyLocation=classpath:jolokia-access.xml)";
 
       String resultLine = JVMArgumentParser.parseNewLine(prefix, "\"", newLine, fixedArguments, usedArgs);
 
@@ -76,9 +77,11 @@ public class JVMArgumentTest {
       assertTrue(resultLine.contains("-Xmx77G"));
       assertTrue(resultLine.contains("-Xms333M"));
       assertTrue(resultLine.contains("-Dhawtio.roles=amq"));
+      assertTrue(resultLine.contains("-Dartemis.discovery.enabled=true"));
       assertFalse(resultLine.contains("-Xmx1G"));
       assertFalse(resultLine.contains("-Xmx512M"));
       assertFalse(resultLine.contains("replaceThisRole"));
+      assertFalse(resultLine.contains("-Dartemis.discovery.enabled=false"));
       assertFalse(resultLine.contains("-Dhawtio.role="));
    }
 
diff --git a/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java b/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java
index ae943f685e..bdcaa40aff 100644
--- a/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java
+++ b/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java
@@ -232,7 +232,7 @@ public class ArtemisTest extends CliTestBase {
    }
 
    private void testMaxHops(int maxHops) throws Exception {
-      List<String> args = new ArrayList<>(List.of("--silent", "--no-autotune", "--clustered", "--user", "admin", "--password", "admin", "--cluster-user", "admin", "--cluster-password", "admin"));
+      List<String> args = new ArrayList<>(List.of("--silent", "--no-autotune", "--static-cluster", "tcp://localhost:61616,tcp://localhost:61617", "--user", "admin", "--password", "admin", "--cluster-user", "admin", "--cluster-password", "admin"));
       if (maxHops != Create.DEFAULT_MAX_HOPS) {
          args.add("--max-hops");
          args.add(String.valueOf(maxHops));
diff --git a/artemis-cli/src/test/java/org/apache/activemq/cli/test/CliTestBase.java b/artemis-cli/src/test/java/org/apache/activemq/cli/test/CliTestBase.java
index 3216c6b03e..5bb8984e73 100644
--- a/artemis-cli/src/test/java/org/apache/activemq/cli/test/CliTestBase.java
+++ b/artemis-cli/src/test/java/org/apache/activemq/cli/test/CliTestBase.java
@@ -20,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import org.apache.activemq.artemis.api.config.ServerLocatorConfigTestAccessor;
 import org.apache.activemq.artemis.api.core.RoutingType;
 import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
 import org.apache.activemq.artemis.cli.Artemis;
@@ -50,6 +51,14 @@ import java.util.concurrent.TimeUnit;
 
 public class CliTestBase extends ArtemisTestCase {
 
+   /**
+    * Enables discovery for the current test and resets it after the test completes.
+    */
+   protected void enableDiscoveryForTest() {
+      ServerLocatorConfigTestAccessor.setDiscoveryEnabled(true);
+      runAfter(() -> ServerLocatorConfigTestAccessor.setDiscoveryEnabled(null));
+   }
+
    // Temp folder at ./target/tmp/<TestClassName>/<generated>
    @TempDir(factory = TargetTempDirFactory.class)
    public File temporaryFolder;
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ServerLocatorConfig.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ServerLocatorConfig.java
index 96a146c696..3132d44f74 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ServerLocatorConfig.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ServerLocatorConfig.java
@@ -19,6 +19,33 @@ package org.apache.activemq.artemis.api.config;
 import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
 
 public class ServerLocatorConfig {
+
+   public static final String DISCOVERY_ENABLED_PROPERTY = "artemis.discovery.enabled";
+
+   public static final String DISCOVERY_ENABLED_ENV_VAR = "ARTEMIS_DISCOVERY_ENABLED";
+
+   private static volatile Boolean discoveryEnabled;
+
+   public static boolean isDiscoveryEnabled() {
+      Boolean enabled = discoveryEnabled;
+      if (enabled == null) {
+         String value = System.getProperty(DISCOVERY_ENABLED_PROPERTY);
+         if (value == null) {
+            value = System.getenv(DISCOVERY_ENABLED_ENV_VAR);
+         }
+         enabled = Boolean.parseBoolean(value);
+         discoveryEnabled = enabled;
+      }
+      return enabled;
+   }
+
+   /**
+    * Overrides the cached discovery-enabled flag. Intended for tests only.
+    */
+   static void setDiscoveryEnabled(Boolean enabled) {
+      discoveryEnabled = enabled;
+   }
+
    public long clientFailureCheckPeriod = ActiveMQClient.DEFAULT_CLIENT_FAILURE_CHECK_PERIOD;
    public long connectionTTL = ActiveMQClient.DEFAULT_CONNECTION_TTL;
    public long callTimeout = ActiveMQClient.DEFAULT_CALL_TIMEOUT;
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/ActiveMQClientMessageBundle.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/ActiveMQClientMessageBundle.java
index 8b154ac80e..671de7c909 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/ActiveMQClientMessageBundle.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/ActiveMQClientMessageBundle.java
@@ -231,4 +231,7 @@ public interface ActiveMQClientMessageBundle {
 
    @Message(id = 219069, value = "Unable to create Session. Either the ClientSessionFactory is closed or the ClientProtocolManager is dead.")
    IllegalStateException unableToCreateSession();
+
+   @Message(id = 219070, value = "The server discovery is disabled by default. See the Server Discovery section in the user manual for security requirements and enablement.")
+   IllegalStateException serverDiscoveryDisabled();
 }
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ServerLocatorImpl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ServerLocatorImpl.java
index 6a53e704f4..d4b7eb098f 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ServerLocatorImpl.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ServerLocatorImpl.java
@@ -344,6 +344,10 @@ public final class ServerLocatorImpl implements ServerLocatorInternal, Discovery
                              final boolean useHA,
                              final DiscoveryGroupConfiguration discoveryGroupConfiguration,
                              final TransportConfiguration[] transportConfigs) {
+      if (discoveryGroupConfiguration != null) {
+         ensureDiscoveryEnabled();
+      }
+
       this.topology = Objects.requireNonNullElseGet(topology, () -> new Topology(this));
 
       this.ha = useHA;
@@ -438,6 +442,10 @@ public final class ServerLocatorImpl implements ServerLocatorInternal, Discovery
    }
 
    private ServerLocatorImpl(ServerLocatorImpl locator) {
+      if (locator.discoveryGroupConfiguration != null) {
+         ensureDiscoveryEnabled();
+      }
+
       ha = locator.ha;
       clusterConnection = locator.clusterConnection;
       initialConnectors = locator.initialConnectors;
@@ -454,6 +462,12 @@ public final class ServerLocatorImpl implements ServerLocatorInternal, Discovery
       discoveryListener = locator.discoveryListener;
    }
 
+   private static void ensureDiscoveryEnabled() {
+      if (!ServerLocatorConfig.isDiscoveryEnabled()) {
+         throw ActiveMQClientMessageBundle.BUNDLE.serverDiscoveryDisabled();
+      }
+   }
+
    private boolean useInitConnector() {
       return !config.useTopologyForLoadBalancing || !receivedTopology || topologyArray == null || topologyArray.length == 0;
    }
diff --git a/artemis-core-client/src/test/java/org/apache/activemq/artemis/api/config/ServerLocatorConfigDiscoveryPropertyTest.java b/artemis-core-client/src/test/java/org/apache/activemq/artemis/api/config/ServerLocatorConfigDiscoveryPropertyTest.java
new file mode 100644
index 0000000000..7653122e5a
--- /dev/null
+++ b/artemis-core-client/src/test/java/org/apache/activemq/artemis/api/config/ServerLocatorConfigDiscoveryPropertyTest.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.api.config;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.activemq.artemis.api.core.DiscoveryGroupConfiguration;
+import org.apache.activemq.artemis.api.core.UDPBroadcastEndpointFactory;
+import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
+import org.apache.activemq.artemis.api.core.client.ServerLocator;
+import org.apache.activemq.artemis.tests.util.ArtemisTestCase;
+import org.junit.jupiter.api.Test;
+
+public class ServerLocatorConfigDiscoveryPropertyTest extends ArtemisTestCase {
+
+   @Test
+   public void testDiscoveryEnabledEnvVarName() {
+      assertEquals("ARTEMIS_DISCOVERY_ENABLED", ServerLocatorConfig.DISCOVERY_ENABLED_ENV_VAR);
+   }
+
+   @Test
+   public void testDiscoveryDisabledByDefault() {
+      assertFalse(ServerLocatorConfig.isDiscoveryEnabled());
+   }
+
+   @Test
+   public void testDiscoveryEnabledFromSystemProperty() {
+      ServerLocatorConfig.setDiscoveryEnabled(null);
+      System.setProperty(ServerLocatorConfig.DISCOVERY_ENABLED_PROPERTY, "true");
+      runAfter(() -> {
+         System.clearProperty(ServerLocatorConfig.DISCOVERY_ENABLED_PROPERTY);
+         ServerLocatorConfig.setDiscoveryEnabled(null);
+      });
+
+      assertTrue(ServerLocatorConfig.isDiscoveryEnabled());
+   }
+
+   @Test
+   public void testDiscoveryDisabledFromSystemProperty() {
+      ServerLocatorConfig.setDiscoveryEnabled(null);
+      System.setProperty(ServerLocatorConfig.DISCOVERY_ENABLED_PROPERTY, "false");
+      runAfter(() -> {
+         System.clearProperty(ServerLocatorConfig.DISCOVERY_ENABLED_PROPERTY);
+         ServerLocatorConfig.setDiscoveryEnabled(null);
+      });
+
+      assertFalse(ServerLocatorConfig.isDiscoveryEnabled());
+   }
+
+   @Test
+   public void testSetDiscoveryEnabledOverridesCachedValue() {
+      runAfter(() -> {
+         ServerLocatorConfig.setDiscoveryEnabled(null);
+         System.clearProperty(ServerLocatorConfig.DISCOVERY_ENABLED_PROPERTY);
+      });
+
+      assertFalse(ServerLocatorConfig.isDiscoveryEnabled());
+      ServerLocatorConfig.setDiscoveryEnabled(true);
+      assertTrue(ServerLocatorConfig.isDiscoveryEnabled());
+      ServerLocatorConfig.setDiscoveryEnabled(false);
+      assertFalse(ServerLocatorConfig.isDiscoveryEnabled());
+      ServerLocatorConfig.setDiscoveryEnabled(null);
+      assertFalse(ServerLocatorConfig.isDiscoveryEnabled());
+   }
+
+   @Test
+   public void testCreateServerLocatorFailsWhenDisabled() {
+      DiscoveryGroupConfiguration groupConfiguration = new DiscoveryGroupConfiguration()
+         .setBroadcastEndpointFactory(new UDPBroadcastEndpointFactory().setGroupAddress("231.7.7.7").setGroupPort(9876));
+
+      IllegalStateException exception = assertThrows(IllegalStateException.class,
+         () -> ActiveMQClient.createServerLocatorWithoutHA(groupConfiguration));
+      assertTrue(exception.getMessage().contains("server discovery is disabled by default"));
+   }
+
+   @Test
+   public void testCreateServerLocatorSucceedsWhenEnabledViaSystemProperty() {
+      ServerLocatorConfig.setDiscoveryEnabled(null);
+      System.setProperty(ServerLocatorConfig.DISCOVERY_ENABLED_PROPERTY, "true");
+      runAfter(() -> {
+         System.clearProperty(ServerLocatorConfig.DISCOVERY_ENABLED_PROPERTY);
+         ServerLocatorConfig.setDiscoveryEnabled(null);
+      });
+
+      testCreateServerLocatorSucceeds();
+   }
+
+   @Test
+   public void testCreateServerLocatorSucceedsWhenEnabled() {
+      enableDiscoveryForTest();
+
+      testCreateServerLocatorSucceeds();
+   }
+
+   private void enableDiscoveryForTest() {
+      ServerLocatorConfig.setDiscoveryEnabled(true);
+      runAfter(() -> ServerLocatorConfig.setDiscoveryEnabled(null));
+   }
+
+   private void testCreateServerLocatorSucceeds() {
+      DiscoveryGroupConfiguration groupConfiguration = new DiscoveryGroupConfiguration()
+         .setBroadcastEndpointFactory(new UDPBroadcastEndpointFactory().setGroupAddress("231.7.7.7").setGroupPort(9876));
+
+      ServerLocator locator = ActiveMQClient.createServerLocatorWithoutHA(groupConfiguration);
+      assertNotNull(locator);
+      locator.close();
+   }
+}
diff --git a/artemis-jms-client/src/test/java/org/apache/activemq/artemis/api/config/ServerLocatorConfigTestAccessor.java b/artemis-jms-client/src/test/java/org/apache/activemq/artemis/api/config/ServerLocatorConfigTestAccessor.java
new file mode 100644
index 0000000000..5754527b04
--- /dev/null
+++ b/artemis-jms-client/src/test/java/org/apache/activemq/artemis/api/config/ServerLocatorConfigTestAccessor.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.api.config;
+
+public class ServerLocatorConfigTestAccessor {
+
+   public static void setDiscoveryEnabled(Boolean enabled) {
+      ServerLocatorConfig.setDiscoveryEnabled(enabled);
+   }
+}
diff --git a/artemis-jms-client/src/test/java/org/apache/activemq/artemis/uri/ConnectionFactoryURITest.java b/artemis-jms-client/src/test/java/org/apache/activemq/artemis/uri/ConnectionFactoryURITest.java
index 4792803a9a..0e014d8286 100644
--- a/artemis-jms-client/src/test/java/org/apache/activemq/artemis/uri/ConnectionFactoryURITest.java
+++ b/artemis-jms-client/src/test/java/org/apache/activemq/artemis/uri/ConnectionFactoryURITest.java
@@ -54,6 +54,8 @@ import org.apache.activemq.artemis.jms.client.ActiveMQQueueConnectionFactory;
 import org.apache.activemq.artemis.jms.client.ActiveMQTopicConnectionFactory;
 import org.apache.activemq.artemis.jms.client.ActiveMQXAQueueConnectionFactory;
 import org.apache.activemq.artemis.jms.client.ActiveMQXATopicConnectionFactory;
+import org.apache.activemq.artemis.api.config.ServerLocatorConfigTestAccessor;
+import org.apache.activemq.artemis.tests.util.ArtemisTestCase;
 import org.apache.activemq.artemis.utils.RandomUtil;
 import org.apache.commons.beanutils.BeanUtilsBean;
 import org.junit.jupiter.api.Test;
@@ -61,7 +63,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import java.lang.invoke.MethodHandles;
 
-public class ConnectionFactoryURITest {
+public class ConnectionFactoryURITest extends ArtemisTestCase {
 
    private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
@@ -77,6 +79,11 @@ public class ConnectionFactoryURITest {
       ignoreList.add("outgoingInterceptorList");
    }
 
+   protected void enableDiscoveryForTest() {
+      ServerLocatorConfigTestAccessor.setDiscoveryEnabled(true);
+      runAfter(() -> ServerLocatorConfigTestAccessor.setDiscoveryEnabled(null));
+   }
+
    @Test
    public void testIPv6() throws Exception {
       for (String IPV6 : V6IPs) {
@@ -319,6 +326,7 @@ public class ConnectionFactoryURITest {
 
    @Test
    public void testUDP() throws Exception {
+      enableDiscoveryForTest();
       ActiveMQConnectionFactory factory = parser.newObject(new URI("udp://localhost:3030?ha=true&type=QUEUE_XA_CF"), null);
 
       assertEquals(ActiveMQXAQueueConnectionFactory.class.getName(), factory.getClass().getName());
@@ -326,6 +334,7 @@ public class ConnectionFactoryURITest {
 
    @Test
    public void testUDPAllProperties() throws Exception {
+      enableDiscoveryForTest();
       ignoreList.add("deserializationBlackList");
       ignoreList.add("deserializationWhiteList");
       try {
@@ -344,6 +353,7 @@ public class ConnectionFactoryURITest {
 
    @Test
    public void testUDPAllPropertiesWithDeprecatedListProps() throws Exception {
+      enableDiscoveryForTest();
       ignoreList.add("deserializationDenyList");
       ignoreList.add("deserializationAllowList");
       try {
@@ -362,6 +372,7 @@ public class ConnectionFactoryURITest {
 
    @Test
    public void testUDPURI() throws Exception {
+      enableDiscoveryForTest();
       DiscoveryGroupConfiguration discoveryGroupConfiguration = new DiscoveryGroupConfiguration();
       UDPBroadcastEndpointFactory endpoint = new UDPBroadcastEndpointFactory();
       endpoint.setGroupPort(3333).setGroupAddress("wahey").setLocalBindPort(555).setLocalBindAddress("uhuh");
@@ -395,6 +406,7 @@ public class ConnectionFactoryURITest {
 
    @Test
    public void testInvalidCFType() throws Exception {
+      enableDiscoveryForTest();
       ActiveMQConnectionFactory factory = parser.newObject(new URI("udp://localhost:3030?ha=true&type=QUEUE_XA_CFInvalid"), null);
 
       assertEquals(ActiveMQJMSConnectionFactory.class.getName(), factory.getClass().getName());
@@ -402,6 +414,7 @@ public class ConnectionFactoryURITest {
 
    @Test
    public void testJGroupsFile() throws Exception {
+      enableDiscoveryForTest();
       ActiveMQConnectionFactory factory = parser.newObject(new URI("jgroups://channel-name?file=/path/to/some/file/channel-file.xml&test=33"), null);
 
       assertEquals(ActiveMQJMSConnectionFactory.class.getName(), factory.getClass().getName());
@@ -412,6 +425,7 @@ public class ConnectionFactoryURITest {
 
    @Test
    public void testJGroupsAllProperties() throws Exception {
+      enableDiscoveryForTest();
       ignoreList.add("deserializationBlackList");
       ignoreList.add("deserializationWhiteList");
       try {
@@ -430,6 +444,7 @@ public class ConnectionFactoryURITest {
 
    @Test
    public void testJGroupsAllPropertiesWithDeprecatedListProps() throws Exception {
+      enableDiscoveryForTest();
       ignoreList.add("deserializationDenyList");
       ignoreList.add("deserializationAllowList");
       try {
@@ -448,6 +463,7 @@ public class ConnectionFactoryURITest {
 
    @Test
    public void testJGroupsFileURI() throws Exception {
+      enableDiscoveryForTest();
       DiscoveryGroupConfiguration discoveryGroupConfiguration = new DiscoveryGroupConfiguration();
       JGroupsFileBroadcastEndpointFactory endpointFactory = new JGroupsFileBroadcastEndpointFactory().setChannelName("channel-name").setFile("channel-file.xml");
       discoveryGroupConfiguration.setName("foo").setRefreshTimeout(12345).setDiscoveryInitialWaitTimeout(5678).setBroadcastEndpointFactory(endpointFactory);
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/ConfigurationUtils.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/ConfigurationUtils.java
index 40dd0a92cc..6c849613ea 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/ConfigurationUtils.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/ConfigurationUtils.java
@@ -46,6 +46,7 @@ import org.apache.activemq.artemis.core.server.cluster.ha.SharedStorePrimaryPoli
 import org.apache.activemq.artemis.core.server.cluster.ha.SharedStoreBackupPolicy;
 import org.apache.activemq.artemis.uri.AcceptorTransportConfigurationParser;
 import org.apache.activemq.artemis.uri.ConnectorTransportConfigurationParser;
+import org.apache.activemq.artemis.api.config.ServerLocatorConfig;
 
 public final class ConfigurationUtils {
 
@@ -151,6 +152,17 @@ public final class ConfigurationUtils {
       compareTTLWithCheckPeriod(configuration);
    }
 
+   public static void validateStartupConfiguration(Configuration configuration) throws ActiveMQIllegalStateException {
+      if (!ServerLocatorConfig.isDiscoveryEnabled()) {
+         if (!configuration.getBroadcastGroupConfigurations().isEmpty()) {
+            throw ActiveMQMessageBundle.BUNDLE.broadcastGroupsConfiguredButDisabled();
+         }
+         if (!configuration.getDiscoveryGroupConfigurations().isEmpty()) {
+            throw ActiveMQMessageBundle.BUNDLE.discoveryGroupsConfiguredButDisabled();
+         }
+      }
+   }
+
    public static List<TransportConfiguration> parseAcceptorURI(String name, String uri) {
       try {
          // remove all whitespace
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQMessageBundle.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQMessageBundle.java
index 3a1027dc01..96f3247d2f 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQMessageBundle.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQMessageBundle.java
@@ -547,4 +547,10 @@ public interface ActiveMQMessageBundle {
 
    @Message(id = 229261, value = "Authentication mechanism not supported: {}")
    ActiveMQSecurityException authenticationMechanismNotSupported(String authMechanism);
+
+   @Message(id = 229262, value = "Broadcast groups are disabled by default. See the Server Discovery section in the user manual for security requirements and enablement.")
+   ActiveMQIllegalStateException broadcastGroupsConfiguredButDisabled();
+
+   @Message(id = 229263, value = "Discovery groups are disabled by default. See the Server Discovery section in the user manual for security requirements and enablement.")
+   ActiveMQIllegalStateException discoveryGroupsConfiguredButDisabled();
 }
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 b301395668..6d95de2fb8 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
@@ -719,6 +719,8 @@ public class ActiveMQServerImpl implements ActiveMQServer {
       configuration.parseProperties(propertiesFileUrl);
       updateStatus(ServerStatus.CONFIGURATION_COMPONENT, configuration.getStatus());
 
+      ConfigurationUtils.validateStartupConfiguration(configuration);
+
       initializeExecutorServices();
 
       initializeCriticalAnalyzer();
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/api/config/ServerLocatorConfigTestAccessor.java b/artemis-server/src/test/java/org/apache/activemq/artemis/api/config/ServerLocatorConfigTestAccessor.java
new file mode 100644
index 0000000000..5754527b04
--- /dev/null
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/api/config/ServerLocatorConfigTestAccessor.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.api.config;
+
+public class ServerLocatorConfigTestAccessor {
+
+   public static void setDiscoveryEnabled(Boolean enabled) {
+      ServerLocatorConfig.setDiscoveryEnabled(enabled);
+   }
+}
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/BroadcastDiscoveryConfigurationValidationTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/BroadcastDiscoveryConfigurationValidationTest.java
new file mode 100644
index 0000000000..d418b30203
--- /dev/null
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/BroadcastDiscoveryConfigurationValidationTest.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.core.config;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.activemq.artemis.api.config.ServerLocatorConfig;
+import org.apache.activemq.artemis.api.config.ServerLocatorConfigTestAccessor;
+import org.apache.activemq.artemis.api.core.ActiveMQIllegalStateException;
+import org.apache.activemq.artemis.api.core.BroadcastGroupConfiguration;
+import org.apache.activemq.artemis.api.core.DiscoveryGroupConfiguration;
+import org.apache.activemq.artemis.api.core.TransportConfiguration;
+import org.apache.activemq.artemis.api.core.UDPBroadcastEndpointFactory;
+import org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnectorFactory;
+import org.apache.activemq.artemis.core.server.ActiveMQServer;
+import org.apache.activemq.artemis.core.server.ActiveMQServers;
+import org.apache.activemq.artemis.tests.util.ServerTestBase;
+import org.junit.jupiter.api.Test;
+
+public class BroadcastDiscoveryConfigurationValidationTest extends ServerTestBase {
+
+   @Test
+   public void testConstructionSucceedsWhenDisabled() throws Exception {
+      Configuration configuration = createDefaultInVMConfig()
+         .addConnectorConfiguration("invm", new TransportConfiguration(InVMConnectorFactory.class.getName(), generateInVMParams(0), "invm"))
+         .addBroadcastGroupConfiguration(createBroadcastGroupConfiguration());
+
+      assertDoesNotThrow(() -> ActiveMQServers.newActiveMQServer(configuration, false));
+   }
+
+   @Test
+   public void testStartFailsWithBroadcastGroupsWhenDisabled() throws Exception {
+      Configuration configuration = createDefaultInVMConfig()
+         .addConnectorConfiguration("invm", new TransportConfiguration(InVMConnectorFactory.class.getName(), generateInVMParams(0), "invm"))
+         .addBroadcastGroupConfiguration(createBroadcastGroupConfiguration());
+
+      ActiveMQServer server = ActiveMQServers.newActiveMQServer(configuration, false);
+
+      ActiveMQIllegalStateException exception = assertThrows(ActiveMQIllegalStateException.class, server::start);
+      assertTrue(exception.getMessage().contains("AMQ229262"));
+   }
+
+   @Test
+   public void testStartFailsWithDiscoveryGroupsWhenDisabled() throws Exception {
+      Configuration configuration = createDefaultInVMConfig()
+         .addDiscoveryGroupConfiguration("dg1", createDiscoveryGroupConfiguration());
+
+      ActiveMQServer server = ActiveMQServers.newActiveMQServer(configuration, false);
+
+      ActiveMQIllegalStateException exception = assertThrows(ActiveMQIllegalStateException.class, server::start);
+      assertTrue(exception.getMessage().contains("AMQ229263"));
+   }
+
+   @Test
+   public void testStartSucceedsWhenEnabledViaSystemProperty() throws Exception {
+      ServerLocatorConfigTestAccessor.setDiscoveryEnabled(null);
+      System.setProperty(ServerLocatorConfig.DISCOVERY_ENABLED_PROPERTY, "true");
+      runAfter(() -> {
+         System.clearProperty(ServerLocatorConfig.DISCOVERY_ENABLED_PROPERTY);
+         ServerLocatorConfigTestAccessor.setDiscoveryEnabled(null);
+      });
+
+      testStartSucceeds();
+   }
+
+   @Test
+   public void testStartSucceedsWhenEnabled() throws Exception {
+      enableDiscoveryForTest();
+
+      testStartSucceeds();
+   }
+
+   private void testStartSucceeds() throws Exception {
+      Configuration configuration = configurationWithBroadcastAndDiscovery();
+
+      ActiveMQServer server = ActiveMQServers.newActiveMQServer(configuration, false);
+
+      server.start();
+      server.stop();
+   }
+
+   private BroadcastGroupConfiguration createBroadcastGroupConfiguration() {
+      List<String> connectorNames = new ArrayList<>();
+      connectorNames.add("invm");
+
+      return new BroadcastGroupConfiguration()
+         .setName("bg1")
+         .setBroadcastPeriod(1000)
+         .setConnectorInfos(connectorNames)
+         .setEndpointFactory(new UDPBroadcastEndpointFactory().setGroupAddress("231.7.7.7").setGroupPort(9876));
+   }
+
+   private DiscoveryGroupConfiguration createDiscoveryGroupConfiguration() {
+      return new DiscoveryGroupConfiguration()
+         .setName("dg1")
+         .setRefreshTimeout(5000)
+         .setBroadcastEndpointFactory(new UDPBroadcastEndpointFactory().setGroupAddress("231.7.7.7").setGroupPort(9876));
+   }
+
+   private Configuration configurationWithBroadcastAndDiscovery() throws Exception {
+      TransportConfiguration connector = new TransportConfiguration(InVMConnectorFactory.class.getName(), generateInVMParams(0), "invm");
+      return createDefaultInVMConfig()
+         .addConnectorConfiguration("invm", connector)
+         .addBroadcastGroupConfiguration(createBroadcastGroupConfiguration())
+         .addDiscoveryGroupConfiguration("dg1", createDiscoveryGroupConfiguration());
+   }
+}
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/HAPolicyConfigurationTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/HAPolicyConfigurationTest.java
index 16a82a64c6..f6c51c7ef2 100644
--- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/HAPolicyConfigurationTest.java
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/HAPolicyConfigurationTest.java
@@ -91,6 +91,7 @@ public class HAPolicyConfigurationTest extends ServerTestBase {
    public void primaryOnlyTest() throws Exception {
       Configuration configuration = createConfiguration("primary-only-hapolicy-config.xml");
       ActiveMQServerImpl server = new ActiveMQServerImpl(configuration);
+      enableDiscoveryForTest();
       try {
          server.start();
          Activation activation = server.getActivation();
@@ -373,6 +374,7 @@ public class HAPolicyConfigurationTest extends ServerTestBase {
    public void BackupReplicationTest() throws Exception {
       Configuration configuration = createConfiguration("backup-hapolicy-config.xml");
       ActiveMQServerImpl server = new ActiveMQServerImpl(configuration);
+      enableDiscoveryForTest();
       try {
          server.start();
          Activation activation = server.getActivation();
@@ -451,6 +453,7 @@ public class HAPolicyConfigurationTest extends ServerTestBase {
    public void ReplicaTest() throws Exception {
       Configuration configuration = createConfiguration("replica-hapolicy-config.xml");
       ActiveMQServerImpl server = new ActiveMQServerImpl(configuration);
+      enableDiscoveryForTest();
       try {
          server.start();
          Activation activation = server.getActivation();
@@ -549,6 +552,7 @@ public class HAPolicyConfigurationTest extends ServerTestBase {
    public void SharedStoreBackupTest() throws Exception {
       Configuration configuration = createConfiguration("shared-store-backup-hapolicy-config.xml");
       ActiveMQServerImpl server = new ActiveMQServerImpl(configuration);
+      enableDiscoveryForTest();
       try {
          server.start();
          Activation activation = server.getActivation();
@@ -621,6 +625,7 @@ public class HAPolicyConfigurationTest extends ServerTestBase {
    public void colocatedTest() throws Exception {
       Configuration configuration = createConfiguration("colocated-hapolicy-config.xml");
       ActiveMQServerImpl server = new ActiveMQServerImpl(configuration);
+      enableDiscoveryForTest();
       try {
          server.start();
          Activation activation = server.getActivation();
@@ -649,6 +654,7 @@ public class HAPolicyConfigurationTest extends ServerTestBase {
    public void colocatedTestNullBackup() throws Exception {
       Configuration configuration = createConfiguration("colocated-hapolicy-config-null-backup.xml");
       ActiveMQServerImpl server = new ActiveMQServerImpl(configuration);
+      enableDiscoveryForTest();
       try {
          server.start();
          Activation activation = server.getActivation();
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/tests/util/ServerTestBase.java b/artemis-server/src/test/java/org/apache/activemq/artemis/tests/util/ServerTestBase.java
index adb21eb429..0fcb87d9c8 100644
--- a/artemis-server/src/test/java/org/apache/activemq/artemis/tests/util/ServerTestBase.java
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/tests/util/ServerTestBase.java
@@ -38,6 +38,7 @@ import org.apache.activemq.artemis.api.core.TransportConfiguration;
 import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
 import org.apache.activemq.artemis.core.client.impl.ClientSessionFactoryImpl;
 import org.apache.activemq.artemis.core.client.impl.ServerLocatorImpl;
+import org.apache.activemq.artemis.api.config.ServerLocatorConfigTestAccessor;
 import org.apache.activemq.artemis.core.config.Configuration;
 import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl;
 import org.apache.activemq.artemis.core.config.storage.DatabaseStorageConfiguration;
@@ -94,6 +95,14 @@ public abstract class ServerTestBase extends ArtemisTestCase {
    public static final String NETTY_ACCEPTOR_FACTORY = NettyAcceptorFactory.class.getCanonicalName();
    public static final String CLUSTER_PASSWORD = "UnitTestsClusterPassword";
 
+   /**
+    * Enables discovery for the current test and resets it after the test completes.
+    */
+   protected void enableDiscoveryForTest() {
+      ServerLocatorConfigTestAccessor.setDiscoveryEnabled(true);
+      runAfter(() -> ServerLocatorConfigTestAccessor.setDiscoveryEnabled(null));
+   }
+
    // There is a verification about thread leakages. We only fail a single thread when this happens
    private static Set<Thread> alreadyFailedThread = new HashSet<>();
 
diff --git a/docs/user-manual/clusters.adoc b/docs/user-manual/clusters.adoc
index cffb2e2a6c..fcaed5276f 100644
--- a/docs/user-manual/clusters.adoc
+++ b/docs/user-manual/clusters.adoc
@@ -50,6 +50,7 @@ If the answer to that question is "no" then you're likely to get better performa
 
 Also keep in mind that a xref:connection-routers.adoc#connection-routers[connection router] may improve performance of your cluster by grouping related consumers and producers together on the same node.
 
+[[server-discovery]]
 == Server discovery
 
 Server discovery is a mechanism by which servers can propagate their connection details to:
@@ -63,6 +64,31 @@ This information, let's call it the Cluster Topology, is actually sent around no
 This being the case, we need a way of establishing the initial first connection.
 This can be done using dynamic discovery techniques like https://en.wikipedia.org/wiki/User_Datagram_Protocol[UDP] and http://www.jgroups.org/[JGroups], or by providing a list of initial connectors.
 
+[WARNING]
+====
+Server discovery is *disabled by default* for security reasons.
+
+Before enabling server discovery, check that the broadcast endpoint authenticates the senders:
+
+* *JGroups*: configure the JGroups `AUTH` protocol and/or TLS in the stack referenced by `jgroups-file` so only authorized members can send broadcasts.
+* *UDP*: has no built-in sender authentication; use only on trusted networks.
+
+Set the JVM system property `artemis.discovery.enabled=true` or the environment
+variable `ARTEMIS_DISCOVERY_ENABLED=true` to enable discovery on clients and servers.
+
+You can set the JVM system property for the broker by including
+`-Dartemis.discovery.enabled=true` within the `JAVA_ARGS` within `etc/artemis.profile` and
+`etc/artemis-utility.profile` for your broker instance.`
+
+For clustered instances created with the CLI that require server discovery, you must pass
+the hidden `--discovery-enabled` flag to `artemis create`.
+The `--discovery-enabled` flag adds `-Dartemis.discovery.enabled=true` to `JAVA_ARGS` in
+both profile files.
+Use this only after reviewing the security requirements above.
+
+See xref:threat-model.adoc#threat-model[threat model] for trust boundary implications.
+====
+
 === Dynamic Discovery
 
 Server discovery uses https://en.wikipedia.org/wiki/User_Datagram_Protocol[UDP] multicast or http://www.jgroups.org/[JGroups] to broadcast server connection settings.
diff --git a/docs/user-manual/threat-model.adoc b/docs/user-manual/threat-model.adoc
index 8ebf9b12ed..43e2261972 100644
--- a/docs/user-manual/threat-model.adoc
+++ b/docs/user-manual/threat-model.adoc
@@ -205,6 +205,9 @@ Reports about admin capabilities (e.g., "admin can read messages") are triaged a
 . *When `security-enabled=false`:* Can send arbitrary protocol messages and perform messaging or management operations without authentication.
 This configuration is not supported in production.
 Reports against such deployments with are triaged as `OUT-OF-SCOPE`.
+. *When `artemis.discovery.enabled=true` and insecure server discovery broadcast endpoint: *If unauthenticated senders send discovery packets, the broker does not defend against malicious discovery packets.
+This configuration is not supported in production.
+Reports against such deployments with are triaged as `OUT-OF-SCOPE`.
 
 == Security Properties Provided
 
@@ -222,6 +225,8 @@ If message payloads must be kept confidential from the broker operator, the appl
 . *Authentication of message content origin:* The broker authenticates the _connection_ that sends a message, not the message itself.
 A compromised client or a client authorized to send to an address can claim to be any logical sender.
 Applications requiring non-repudiation must implement message signing.
+. *Authentication of server discovery packets:* The broker and the client don't authenticate the server discovery packets.
+Server discovery broadcast endpoints must implement authentication.
 . *Comprehensive per-client message rate limiting across all protocols:* Message rate limiting is available for Core and MQTT 5 clients, but *not* for AMQP, STOMP, OpenWire, or MQTT 3.x clients.
 Clients using protocols without rate limiting can send messages at line rate until paging or disk space limits are enforced.
 Operators must use network-level controls for protocols that lack broker-side rate limiting.
diff --git a/docs/user-manual/versions.adoc b/docs/user-manual/versions.adoc
index 94a52598bd..a92856d318 100644
--- a/docs/user-manual/versions.adoc
+++ b/docs/user-manual/versions.adoc
@@ -57,6 +57,7 @@ Due to the additional persistent state there will be a performance impact.
 Of course, this is a traditional trade-off for increased reliability.
 The exact impact will depend on your specific use-case.
 Generally speaking, the recommendation is to use the lowest QoS possible to avoid unnecessary overhead.
+* Server discovery is disabled by default, see the xref:clusters.adoc#server-discovery[Server Discovery] section in the user manual for security requirements and enablement.
 
 == Version 2.55.0
 
diff --git a/tests/artemis-test-support/src/main/java/org/apache/activemq/artemis/api/config/ServerLocatorConfigTestAccessor.java b/tests/artemis-test-support/src/main/java/org/apache/activemq/artemis/api/config/ServerLocatorConfigTestAccessor.java
new file mode 100644
index 0000000000..5754527b04
--- /dev/null
+++ b/tests/artemis-test-support/src/main/java/org/apache/activemq/artemis/api/config/ServerLocatorConfigTestAccessor.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.api.config;
+
+public class ServerLocatorConfigTestAccessor {
+
+   public static void setDiscoveryEnabled(Boolean enabled) {
+      ServerLocatorConfig.setDiscoveryEnabled(enabled);
+   }
+}
diff --git a/tests/artemis-test-support/src/main/java/org/apache/activemq/artemis/tests/util/ActiveMQTestBase.java b/tests/artemis-test-support/src/main/java/org/apache/activemq/artemis/tests/util/ActiveMQTestBase.java
index 3a65d746f6..b4a0fb5797 100644
--- a/tests/artemis-test-support/src/main/java/org/apache/activemq/artemis/tests/util/ActiveMQTestBase.java
+++ b/tests/artemis-test-support/src/main/java/org/apache/activemq/artemis/tests/util/ActiveMQTestBase.java
@@ -67,6 +67,7 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
+import org.apache.activemq.artemis.api.config.ServerLocatorConfigTestAccessor;
 import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
 import org.apache.activemq.artemis.api.core.ActiveMQException;
 import org.apache.activemq.artemis.api.core.ActiveMQExceptionType;
@@ -191,6 +192,14 @@ public abstract class ActiveMQTestBase extends ArtemisTestCase {
    public static final String NETTY_CONNECTOR_FACTORY = NettyConnectorFactory.class.getCanonicalName();
    public static final String CLUSTER_PASSWORD = "UnitTestsClusterPassword";
 
+   /**
+    * Enables discovery for the current test and resets it after the test completes.
+    */
+   protected void enableDiscoveryForTest() {
+      ServerLocatorConfigTestAccessor.setDiscoveryEnabled(true);
+      runAfter(() -> ServerLocatorConfigTestAccessor.setDiscoveryEnabled(null));
+   }
+
    /**
     * Add a "sendCallNumber" property to messages sent using helper classes. Meant to help in debugging.
     */
diff --git a/tests/db-tests/pom.xml b/tests/db-tests/pom.xml
index 7705f531e9..56cb8d1eda 100644
--- a/tests/db-tests/pom.xml
+++ b/tests/db-tests/pom.xml
@@ -215,6 +215,7 @@
                      <allowAnonymous>false</allowAnonymous>
                      <noWeb>true</noWeb>
                      <instance>${basedir}/target/jdbc-bad-driver</instance>
+                     <staticCluster>tcp://localhost:61616</staticCluster>
                      <args>
                         <arg>--shared-store</arg>
                         <arg>--jdbc</arg>
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/client/SessionFactoryTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/client/SessionFactoryTest.java
index 7730ed6f58..a9391cdfa2 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/client/SessionFactoryTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/client/SessionFactoryTest.java
@@ -50,6 +50,7 @@ public class SessionFactoryTest extends ActiveMQTestBase {
    @Override
    @BeforeEach
    public void setUp() throws Exception {
+      enableDiscoveryForTest();
       super.setUp();
 
       startServer();
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/bridge/BridgeWithDiscoveryGroupStartTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/bridge/BridgeWithDiscoveryGroupStartTest.java
index 62e1a9fb96..84d0c8c4d1 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/bridge/BridgeWithDiscoveryGroupStartTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/bridge/BridgeWithDiscoveryGroupStartTest.java
@@ -74,6 +74,7 @@ public class BridgeWithDiscoveryGroupStartTest extends ActiveMQTestBase {
 
    @TestTemplate
    public void testStartStop() throws Exception {
+      enableDiscoveryForTest();
       Map<String, Object> server0Params = new HashMap<>();
       ActiveMQServer server0 = createClusteredServerWithParams(isNetty(), 0, true, server0Params);
 
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/distribution/MessageRedistributionWithDiscoveryTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/distribution/MessageRedistributionWithDiscoveryTest.java
index aec60236cd..0690f73284 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/distribution/MessageRedistributionWithDiscoveryTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/distribution/MessageRedistributionWithDiscoveryTest.java
@@ -46,6 +46,7 @@ public class MessageRedistributionWithDiscoveryTest extends ClusterTestBase {
    @Override
    @BeforeEach
    public void setUp() throws Exception {
+      enableDiscoveryForTest();
       super.setUp();
       setupCluster();
    }
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/distribution/SymmetricClusterWithDiscoveryTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/distribution/SymmetricClusterWithDiscoveryTest.java
index bd5919d5e4..8976e61894 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/distribution/SymmetricClusterWithDiscoveryTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/distribution/SymmetricClusterWithDiscoveryTest.java
@@ -31,6 +31,7 @@ import org.apache.activemq.artemis.core.server.cluster.impl.MessageLoadBalancing
 import org.apache.activemq.artemis.logs.AssertionLoggerHandler;
 import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
 import org.apache.activemq.artemis.tests.util.Wait;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 public class SymmetricClusterWithDiscoveryTest extends SymmetricClusterTest {
@@ -39,6 +40,13 @@ public class SymmetricClusterWithDiscoveryTest extends SymmetricClusterTest {
 
    protected final int groupPort = ActiveMQTestBase.getUDPDiscoveryPort();
 
+   @Override
+   @BeforeEach
+   public void setUp() throws Exception {
+      enableDiscoveryForTest();
+      super.setUp();
+   }
+
    @Override
    protected boolean isNetty() {
       return false;
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/distribution/TwoWayTwoNodeClusterWithDiscoveryTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/distribution/TwoWayTwoNodeClusterWithDiscoveryTest.java
index fd215ef795..6fe2b0e541 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/distribution/TwoWayTwoNodeClusterWithDiscoveryTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/distribution/TwoWayTwoNodeClusterWithDiscoveryTest.java
@@ -17,17 +17,20 @@
 package org.apache.activemq.artemis.tests.integration.cluster.distribution;
 
 import org.apache.activemq.artemis.core.server.cluster.impl.MessageLoadBalancingType;
+import org.junit.jupiter.api.BeforeEach;
 
 public class TwoWayTwoNodeClusterWithDiscoveryTest extends TwoWayTwoNodeClusterTest {
 
-
    protected static final String groupAddress = getUDPDiscoveryAddress();
 
    protected static final int groupPort = getUDPDiscoveryPort();
 
-
-
-
+   @Override
+   @BeforeEach
+   public void setUp() throws Exception {
+      enableDiscoveryForTest();
+      super.setUp();
+   }
 
    @Override
    protected void setupClusters() {
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/failover/DiscoveryClusterWithBackupFailoverTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/failover/DiscoveryClusterWithBackupFailoverTest.java
index 6820f24446..6c8daa9875 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/failover/DiscoveryClusterWithBackupFailoverTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/failover/DiscoveryClusterWithBackupFailoverTest.java
@@ -17,6 +17,7 @@
 package org.apache.activemq.artemis.tests.integration.cluster.failover;
 
 import org.apache.activemq.artemis.core.server.cluster.impl.MessageLoadBalancingType;
+import org.junit.jupiter.api.BeforeEach;
 
 public class DiscoveryClusterWithBackupFailoverTest extends ClusterWithBackupFailoverTest {
 
@@ -24,6 +25,13 @@ public class DiscoveryClusterWithBackupFailoverTest extends ClusterWithBackupFai
 
    protected final int groupPort = getUDPDiscoveryPort();
 
+   @Override
+   @BeforeEach
+   public void setUp() throws Exception {
+      enableDiscoveryForTest();
+      super.setUp();
+   }
+
    @Override
    protected void setupCluster(final MessageLoadBalancingType messageLoadBalancingType) throws Exception {
       // The lives
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/topology/HAClientTopologyWithDiscoveryTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/topology/HAClientTopologyWithDiscoveryTest.java
index a18f7bf9b3..99d6265dc5 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/topology/HAClientTopologyWithDiscoveryTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/topology/HAClientTopologyWithDiscoveryTest.java
@@ -19,8 +19,9 @@ package org.apache.activemq.artemis.tests.integration.cluster.topology;
 import org.apache.activemq.artemis.api.core.DiscoveryGroupConfiguration;
 import org.apache.activemq.artemis.api.core.UDPBroadcastEndpointFactory;
 import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
-import org.apache.activemq.artemis.api.core.client.ServerLocator;
 import org.apache.activemq.artemis.core.server.cluster.impl.MessageLoadBalancingType;
+import org.apache.activemq.artemis.api.core.client.ServerLocator;
+import org.junit.jupiter.api.BeforeEach;
 
 public class HAClientTopologyWithDiscoveryTest extends TopologyClusterTestBase {
 
@@ -28,6 +29,13 @@ public class HAClientTopologyWithDiscoveryTest extends TopologyClusterTestBase {
 
    protected final int groupPort = getUDPDiscoveryPort();
 
+   @Override
+   @BeforeEach
+   public void setUp() throws Exception {
+      enableDiscoveryForTest();
+      super.setUp();
+   }
+
    @Override
    protected boolean isNetty() {
       return false;
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/ActiveMQConnectionFactoryTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/ActiveMQConnectionFactoryTest.java
index b8ff1eb882..86e90e4ee8 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/ActiveMQConnectionFactoryTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/ActiveMQConnectionFactoryTest.java
@@ -704,6 +704,7 @@ public class ActiveMQConnectionFactoryTest extends ActiveMQTestBase {
    @Override
    @BeforeEach
    public void setUp() throws Exception {
+      enableDiscoveryForTest();
       super.setUp();
 
       startServer();
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/SimpleJNDIClientTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/SimpleJNDIClientTest.java
index 9303ce5b06..3c09ef676d 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/SimpleJNDIClientTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/SimpleJNDIClientTest.java
@@ -91,11 +91,13 @@ public class SimpleJNDIClientTest extends ActiveMQTestBase {
       }
    }
 
+   @Override
    @AfterEach
-   public void closeCFs() {
+   public void tearDown() throws Exception {
       factories.forEach(cf -> {
          cf.close();
       });
+      super.tearDown();
    }
 
    @Test
@@ -399,6 +401,7 @@ public class SimpleJNDIClientTest extends ActiveMQTestBase {
    @Override
    @BeforeEach
    public void setUp() throws Exception {
+      enableDiscoveryForTest();
       super.setUp();
 
       startServer();
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/connection/ConnectionFactorySerializationTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/connection/ConnectionFactorySerializationTest.java
index d0dbb5175f..420f2bde03 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/connection/ConnectionFactorySerializationTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/connection/ConnectionFactorySerializationTest.java
@@ -46,7 +46,6 @@ import org.apache.activemq.artemis.jms.server.config.impl.ConnectionFactoryConfi
 import org.apache.activemq.artemis.tests.util.JMSTestBase;
 import org.apache.activemq.artemis.utils.RandomUtil;
 import org.apache.commons.beanutils.BeanUtilsBean;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -56,15 +55,9 @@ public class ConnectionFactorySerializationTest extends JMSTestBase {
 
    protected static ActiveMQConnectionFactory cf;
 
-   @Override
-   @BeforeEach
-   public void setUp() throws Exception {
-      super.setUp();
-   }
-
-
    @Test
    public void testConnectionFactoryUDP() throws Exception {
+      enableDiscoveryForTest();
       createDiscoveryFactoryUDP();
       cf = (ActiveMQConnectionFactory) namingContext.lookup("/MyConnectionFactory");
 
@@ -87,6 +80,7 @@ public class ConnectionFactorySerializationTest extends JMSTestBase {
 
    @Test
    public void testConnectionFactoryJgroupsFile() throws Exception {
+      enableDiscoveryForTest();
       createDiscoveryFactoryJGroupsFile();
       cf = (ActiveMQConnectionFactory) namingContext.lookup("/MyConnectionFactory");
 
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/connection/ConnectionFactoryWithJGroupsSerializationTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/connection/ConnectionFactoryWithJGroupsSerializationTest.java
index 160444cd99..494467ccae 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/connection/ConnectionFactoryWithJGroupsSerializationTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/connection/ConnectionFactoryWithJGroupsSerializationTest.java
@@ -55,6 +55,7 @@ public class ConnectionFactoryWithJGroupsSerializationTest extends JMSTestBase {
    @BeforeEach
    public void setUp() throws Exception {
       try {
+         enableDiscoveryForTest();
          super.setUp();
 
          channel = new JChannel("udp.xml");
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/server/JMSServerDeployerTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/server/JMSServerDeployerTest.java
index ff5b75d043..77771dbfb2 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/server/JMSServerDeployerTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/server/JMSServerDeployerTest.java
@@ -103,6 +103,7 @@ public class JMSServerDeployerTest extends ActiveMQTestBase {
    @Override
    @BeforeEach
    public void setUp() throws Exception {
+      enableDiscoveryForTest();
       super.setUp();
 
       DiscoveryGroupConfiguration dcg = new DiscoveryGroupConfiguration().setName("mygroup").setRefreshTimeout(5432).setDiscoveryInitialWaitTimeout(5432).setBroadcastEndpointFactory(new UDPBroadcastEndpointFactory().setGroupAddress(getUDPDiscoveryAddress()).setGroupPort(getUDPDiscoveryPort()).setLocalBindAddress("172.16.8.10"));
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/BroadcastGroupControlTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/BroadcastGroupControlTest.java
index c28f96e690..cd721bedec 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/BroadcastGroupControlTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/BroadcastGroupControlTest.java
@@ -85,6 +85,7 @@ public class BroadcastGroupControlTest extends ManagementTestBase {
    @Override
    @BeforeEach
    public void setUp() throws Exception {
+      enableDiscoveryForTest();
       super.setUp();
 
       TransportConfiguration connectorConfiguration = new TransportConfiguration(NETTY_CONNECTOR_FACTORY);
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ClusterConnectionControl2Test.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ClusterConnectionControl2Test.java
index ee8fce736e..0234efe396 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ClusterConnectionControl2Test.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ClusterConnectionControl2Test.java
@@ -39,7 +39,6 @@ import org.apache.activemq.artemis.core.server.ActiveMQServers;
 import org.apache.activemq.artemis.core.server.cluster.impl.MessageLoadBalancingType;
 import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
 import org.apache.activemq.artemis.utils.RandomUtil;
-import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
@@ -90,6 +89,7 @@ public class ClusterConnectionControl2Test extends ManagementTestBase {
    @Override
    @BeforeEach
    public void setUp() throws Exception {
+      enableDiscoveryForTest();
       super.setUp();
 
       String discoveryName = RandomUtil.randomUUIDString();
@@ -127,12 +127,6 @@ public class ClusterConnectionControl2Test extends ManagementTestBase {
       waitForServerToStart(server0);
    }
 
-   @Override
-   @AfterEach
-   public void tearDown() throws Exception {
-      super.tearDown();
-   }
-
    protected ClusterConnectionControl createManagementControl(final String name) throws Exception {
       return ManagementControlHelper.createClusterConnectionControl(name, mbeanServer);
    }
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ClusterConnectionControlTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ClusterConnectionControlTest.java
index bf6f836144..3d7b9370a6 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ClusterConnectionControlTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ClusterConnectionControlTest.java
@@ -51,7 +51,6 @@ import org.apache.activemq.artemis.core.server.management.Notification;
 import org.apache.activemq.artemis.tests.integration.SimpleNotificationService;
 import org.apache.activemq.artemis.utils.RandomUtil;
 import org.apache.activemq.artemis.utils.Wait;
-import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
@@ -170,6 +169,7 @@ public class ClusterConnectionControlTest extends ManagementTestBase {
    @Override
    @BeforeEach
    public void setUp() throws Exception {
+      enableDiscoveryForTest();
       super.setUp();
 
       Map<String, Object> acceptorParams = new HashMap<>();
@@ -201,12 +201,6 @@ public class ClusterConnectionControlTest extends ManagementTestBase {
       server_0.start();
    }
 
-   @Override
-   @AfterEach
-   public void tearDown() throws Exception {
-      super.tearDown();
-   }
-
    protected ClusterConnectionControl createManagementControl(final String name) throws Exception {
       return ManagementControlHelper.createClusterConnectionControl(name, mbeanServer);
    }
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ConnectionRouterControlTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ConnectionRouterControlTest.java
index c31218314d..b09b998a5d 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ConnectionRouterControlTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ConnectionRouterControlTest.java
@@ -28,7 +28,6 @@ import org.apache.activemq.artemis.api.core.management.ConnectionRouterControl;
 import org.apache.activemq.artemis.core.server.routing.policies.FirstElementPolicy;
 import org.apache.activemq.artemis.core.server.routing.KeyType;
 import org.apache.activemq.artemis.tests.integration.routing.RoutingTestBase;
-import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.apache.activemq.artemis.json.JsonObject;
@@ -51,12 +50,6 @@ public class ConnectionRouterControlTest extends RoutingTestBase {
       mbeanServer = createMBeanServer();
    }
 
-   @Override
-   @AfterEach
-   public void tearDown() throws Exception {
-      super.tearDown();
-   }
-
 
    @Test
    public void testGetTarget() throws Exception {
@@ -193,6 +186,7 @@ public class ConnectionRouterControlTest extends RoutingTestBase {
    }
 
    private ConnectionRouterControl getConnectionRouterControlForTarget() throws Exception {
+      enableDiscoveryForTest();
       setupPrimaryServerWithDiscovery(0, GROUP_ADDRESS, GROUP_PORT, true, true, false);
       setupRouterServerWithDiscovery(0, KeyType.USER_NAME, FirstElementPolicy.NAME, null, false, null, 1);
       getServer(0).setMBeanServer(mbeanServer);
@@ -205,6 +199,7 @@ public class ConnectionRouterControlTest extends RoutingTestBase {
    }
 
    private ConnectionRouterControl getConnectionRouterControlForLocalTarget() throws Exception {
+      enableDiscoveryForTest();
       setupPrimaryServerWithDiscovery(0, GROUP_ADDRESS, GROUP_PORT, true, true, false);
       setupRouterServerWithDiscovery(0, KeyType.USER_NAME, FirstElementPolicy.NAME, null, true, null, 1);
       getServer(0).setMBeanServer(mbeanServer);
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/JGroupsChannelBroadcastGroupControlTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/JGroupsChannelBroadcastGroupControlTest.java
index 55ba3cc040..fdd71f6e29 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/JGroupsChannelBroadcastGroupControlTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/JGroupsChannelBroadcastGroupControlTest.java
@@ -31,7 +31,6 @@ import org.apache.activemq.artemis.core.server.ActiveMQServer;
 import org.apache.activemq.artemis.core.server.ActiveMQServers;
 import org.apache.activemq.artemis.utils.RandomUtil;
 import org.jgroups.JChannel;
-import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.apache.activemq.artemis.json.JsonArray;
@@ -74,6 +73,7 @@ public class JGroupsChannelBroadcastGroupControlTest extends ManagementTestBase
    @Override
    @BeforeEach
    public void setUp() throws Exception {
+      enableDiscoveryForTest();
       super.setUp();
 
       prepareJChannel();
@@ -82,6 +82,7 @@ public class JGroupsChannelBroadcastGroupControlTest extends ManagementTestBase
       List<String> connectorInfos = new ArrayList<>();
       connectorInfos.add(connectorConfiguration.getName());
       JChannel channel = new JChannel("udp.xml");
+      runAfter(() -> channel.close());
 
       String channelName1 = "channel1";
       ChannelBroadcastEndpointFactory endpointFactory = new ChannelBroadcastEndpointFactory(channel, channelName1);
@@ -97,9 +98,4 @@ public class JGroupsChannelBroadcastGroupControlTest extends ManagementTestBase
    public void prepareJChannel() {
       JChannelManager.getInstance().setLoopbackMessages(true);
    }
-
-   @AfterEach
-   public void cleanupJChannel() {
-      JChannelManager.getInstance().clear();
-   }
 }
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/JGroupsFileBroadcastGroupControlTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/JGroupsFileBroadcastGroupControlTest.java
index eecc36bfd9..65f2c14f17 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/JGroupsFileBroadcastGroupControlTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/JGroupsFileBroadcastGroupControlTest.java
@@ -70,6 +70,7 @@ public class JGroupsFileBroadcastGroupControlTest extends ManagementTestBase {
    @Override
    @BeforeEach
    public void setUp() throws Exception {
+      enableDiscoveryForTest();
       super.setUp();
 
       TransportConfiguration connectorConfiguration = new TransportConfiguration(NETTY_CONNECTOR_FACTORY);
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ra/ResourceAdapterTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ra/ResourceAdapterTest.java
index 7b65240bdc..ed8cbf894d 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ra/ResourceAdapterTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ra/ResourceAdapterTest.java
@@ -372,6 +372,8 @@ public class ResourceAdapterTest extends ActiveMQRATestBase {
 
    @Test
    public void testResourceAdapterSetup() throws Exception {
+      enableDiscoveryForTest();
+
       ActiveMQResourceAdapter adapter = new ActiveMQResourceAdapter();
       adapter.setDiscoveryAddress("231.1.1.1");
       ActiveMQConnectionFactory factory = adapter.getDefaultActiveMQConnectionFactory();
@@ -419,7 +421,6 @@ public class ResourceAdapterTest extends ActiveMQRATestBase {
       // override initial wait
       assertEquals(10000L, refresh);
       assertEquals(9999L, initWait);
-
    }
 
    @Test
@@ -499,6 +500,8 @@ public class ResourceAdapterTest extends ActiveMQRATestBase {
 
    @Test
    public void testResourceAdapterSetupNoOverrideDiscovery() throws Exception {
+      enableDiscoveryForTest();
+
       ActiveMQResourceAdapter qResourceAdapter = new ActiveMQResourceAdapter();
       qResourceAdapter.setDiscoveryAddress("231.6.6.6");
       qResourceAdapter.setDiscoveryPort(1234);
@@ -524,6 +527,8 @@ public class ResourceAdapterTest extends ActiveMQRATestBase {
 
    @Test
    public void testResourceAdapterSetupOverrideDiscovery() throws Exception {
+      enableDiscoveryForTest();
+
       ActiveMQResourceAdapter qResourceAdapter = new ActiveMQResourceAdapter();
       qResourceAdapter.setDiscoveryAddress("231.7.7.7");
 
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/routing/AmqpRedirectTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/routing/AmqpRedirectTest.java
index 4048e34a3a..64e6e05ac9 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/routing/AmqpRedirectTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/routing/AmqpRedirectTest.java
@@ -31,6 +31,7 @@ import org.apache.activemq.transport.amqp.client.AmqpValidator;
 import org.apache.qpid.proton.amqp.transport.ConnectionError;
 import org.apache.qpid.proton.amqp.transport.ErrorCondition;
 import org.apache.qpid.proton.engine.Connection;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 /**
@@ -40,6 +41,13 @@ import org.junit.jupiter.api.Test;
  */
 public class AmqpRedirectTest extends RoutingTestBase {
 
+   @Override
+   @BeforeEach
+   public void setUp() throws Exception {
+      enableDiscoveryForTest();
+      super.setUp();
+   }
+
    @Test
    public void testRouterRejectionDueToOfflineTargetPool() throws Exception {
       setupPrimaryServerWithDiscovery(0, GROUP_ADDRESS, GROUP_PORT, true, true, false);
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/routing/KeyTypeTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/routing/KeyTypeTest.java
index 1cfda9e1a2..872c7b8230 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/routing/KeyTypeTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/routing/KeyTypeTest.java
@@ -101,6 +101,13 @@ public class KeyTypeTest extends RoutingTestBase {
       this.protocol = protocol;
    }
 
+   @Override
+   @BeforeEach
+   public void setUp() throws Exception {
+      enableDiscoveryForTest();
+      super.setUp();
+   }
+
    @BeforeEach
    public void setup() throws Exception {
       PolicyFactoryResolver.getInstance().registerPolicyFactory(MOCK_POLICY_NAME, () -> new FirstElementPolicy(MOCK_POLICY_NAME) {
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/routing/MQTTRedirectTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/routing/MQTTRedirectTest.java
index 8a234c20ce..bb4186c5b2 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/routing/MQTTRedirectTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/routing/MQTTRedirectTest.java
@@ -42,6 +42,7 @@ import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
 import org.eclipse.paho.client.mqttv3.MqttException;
 import org.eclipse.paho.client.mqttv3.MqttMessage;
 import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -50,6 +51,13 @@ import static org.junit.jupiter.api.Assertions.fail;
 
 public class MQTTRedirectTest extends RoutingTestBase {
 
+   @Override
+   @BeforeEach
+   public void setUp() throws Exception {
+      enableDiscoveryForTest();
+      super.setUp();
+   }
+
    static {
       String path = System.getProperty("java.security.auth.login.config");
       if (path == null) {
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/routing/RedirectTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/routing/RedirectTest.java
index 966bcaf6f0..b1561b6156 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/routing/RedirectTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/routing/RedirectTest.java
@@ -39,6 +39,7 @@ import org.apache.activemq.artemis.core.server.routing.policies.LeastConnections
 import org.apache.activemq.artemis.core.server.routing.policies.RoundRobinPolicy;
 import org.apache.activemq.artemis.tests.extensions.parameterized.ParameterizedTestExtension;
 import org.apache.activemq.artemis.tests.extensions.parameterized.Parameters;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.TestTemplate;
 import org.junit.jupiter.api.extension.ExtendWith;
 
@@ -63,6 +64,13 @@ public class RedirectTest extends RoutingTestBase {
       this.pool = pool;
    }
 
+   @Override
+   @BeforeEach
+   public void setUp() throws Exception {
+      enableDiscoveryForTest();
+      super.setUp();
+   }
+
    @TestTemplate
    public void testSimpleRedirect() throws Exception {
       final String queueName = "RedirectTestQueue";
diff --git a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/ra/ResourceAdapterTest.java b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/ra/ResourceAdapterTest.java
index dbd384dfe3..8691f90bf4 100644
--- a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/ra/ResourceAdapterTest.java
+++ b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/ra/ResourceAdapterTest.java
@@ -250,6 +250,8 @@ public class ResourceAdapterTest extends ActiveMQTestBase {
 
    @Test
    public void testCreateConnectionFactoryOverrideDiscovery() throws Exception {
+      enableDiscoveryForTest();
+
       ActiveMQResourceAdapter ra = new ActiveMQResourceAdapter();
       ra.setConnectorClassName(InVMConnectorFactory.class.getName());
       ConnectionFactoryProperties connectionFactoryProperties = new ConnectionFactoryProperties();


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


(artemis) 09/11: Applying SystemPropertyHelper to previous commits

Posted by cl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit acdec7a902f7dbd2d84fff8c51d4cd5b8910a27b
Author: Clebert Suconic <cl...@gmail.com>
AuthorDate: Mon Aug 17 18:18:55 2026 -0400

    Applying SystemPropertyHelper to previous commits
    
    (cherry picked from commit b15518e00af885a84a87164abe5271e98917a8f3)
---
 .../artemis/api/config/ServerLocatorConfig.java    |  7 +--
 .../apache/activemq/artemis/api/core/JsonUtil.java | 72 ++++++++++------------
 2 files changed, 36 insertions(+), 43 deletions(-)

diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ServerLocatorConfig.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ServerLocatorConfig.java
index 3132d44f74..f27a6870e9 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ServerLocatorConfig.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ServerLocatorConfig.java
@@ -17,6 +17,7 @@
 package org.apache.activemq.artemis.api.config;
 
 import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
+import org.apache.activemq.artemis.utils.SystemPropertyHelper;
 
 public class ServerLocatorConfig {
 
@@ -29,11 +30,7 @@ public class ServerLocatorConfig {
    public static boolean isDiscoveryEnabled() {
       Boolean enabled = discoveryEnabled;
       if (enabled == null) {
-         String value = System.getProperty(DISCOVERY_ENABLED_PROPERTY);
-         if (value == null) {
-            value = System.getenv(DISCOVERY_ENABLED_ENV_VAR);
-         }
-         enabled = Boolean.parseBoolean(value);
+         enabled = Boolean.parseBoolean(SystemPropertyHelper.getProperty(DISCOVERY_ENABLED_PROPERTY, DISCOVERY_ENABLED_ENV_VAR, "false"));
          discoveryEnabled = enabled;
       }
       return enabled;
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/JsonUtil.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/JsonUtil.java
index 3e1434d82f..5382965bd9 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/JsonUtil.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/JsonUtil.java
@@ -41,6 +41,7 @@ import org.apache.activemq.artemis.utils.Base64;
 import org.apache.activemq.artemis.utils.JsonLoader;
 import org.apache.activemq.artemis.utils.ObjectInputStreamWithClassLoader;
 import org.apache.activemq.artemis.utils.StringEscapeUtils;
+import org.apache.activemq.artemis.utils.SystemPropertyHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -52,45 +53,40 @@ public final class JsonUtil {
 
    private static final String SERIAL_FILTER;
 
+   // this is the minimal necessary to deserialize a CompositeData
+   private static final String DEFAULT_SERIAL_FILTER = "maxdepth=10;" +
+      "javax.management.openmbean.CompositeType;" +
+      "javax.management.openmbean.CompositeDataSupport;" +
+      "javax.management.openmbean.OpenType;" +
+      "javax.management.openmbean.ArrayType;" +
+      "javax.management.openmbean.SimpleType;" +
+      "javax.management.openmbean.TabularType;" +
+      "javax.management.openmbean.TabularDataSupport;" +
+      "java.util.TreeMap;" +
+      "java.util.Collections;" +
+      "java.util.Collections$UnmodifiableList;" +
+      "java.util.Collections$UnmodifiableCollection;" +
+      "java.util.Collections$UnmodifiableRandomAccessList;" +
+      "java.util.ArrayList;" +
+      "java.util.LinkedHashMap;" +
+      "java.util.HashMap;" +
+      "java.util.Map$Entry;" +
+      "java.util.Arrays;" +
+      "java.util.Arrays$ArrayList;" +
+      "java.lang.Object;" +
+      "java.lang.String;" +
+      "java.lang.Boolean;" +
+      "java.lang.Long;" +
+      "java.lang.Number;" +
+      "java.lang.Byte;" +
+      "java.lang.Double;" +
+      "java.lang.Float;" +
+      "java.lang.Integer;" +
+      "java.lang.Short;" +
+      "!*";
+
    static {
-      String filter = System.getProperty("artemis.json.composite.data.serial.filter");
-      if (filter == null) {
-         filter = System.getenv("ARTEMIS_JSON_COMPOSITE_DATA_SERIAL_FILTER");
-      }
-      if (filter == null) {
-         // this is the minimal necessary to deserialize a CompositeData
-         filter = "maxdepth=10;" +
-            "javax.management.openmbean.CompositeType;" +
-            "javax.management.openmbean.CompositeDataSupport;" +
-            "javax.management.openmbean.OpenType;" +
-            "javax.management.openmbean.ArrayType;" +
-            "javax.management.openmbean.SimpleType;" +
-            "javax.management.openmbean.TabularType;" +
-            "javax.management.openmbean.TabularDataSupport;" +
-            "java.util.TreeMap;" +
-            "java.util.Collections;" +
-            "java.util.Collections$UnmodifiableList;" +
-            "java.util.Collections$UnmodifiableCollection;" +
-            "java.util.Collections$UnmodifiableRandomAccessList;" +
-            "java.util.ArrayList;" +
-            "java.util.LinkedHashMap;" +
-            "java.util.HashMap;" +
-            "java.util.Map$Entry;" +
-            "java.util.Arrays;" +
-            "java.util.Arrays$ArrayList;" +
-            "java.lang.Object;" +
-            "java.lang.String;" +
-            "java.lang.Boolean;" +
-            "java.lang.Long;" +
-            "java.lang.Number;" +
-            "java.lang.Byte;" +
-            "java.lang.Double;" +
-            "java.lang.Float;" +
-            "java.lang.Integer;" +
-            "java.lang.Short;" +
-            "!*";
-      }
-      SERIAL_FILTER = filter;
+      SERIAL_FILTER = SystemPropertyHelper.getProperty("artemis.json.composite.data.serial.filter", "ARTEMIS_JSON_COMPOSITE_DATA_SERIAL_FILTER", DEFAULT_SERIAL_FILTER);
    }
 
    public static JsonArray toJSONArray(final Object[] array) throws Exception {


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


(artemis) 07/11: Fix compatibility with older clients during pre-authentication topology exchange.

Posted by cl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit ffb79bb98704284ed8cc2ab0cec1f35637c4c858
Author: Clebert Suconic <cl...@gmail.com>
AuthorDate: Fri Aug 7 13:07:37 2026 -0400

    Fix compatibility with older clients during pre-authentication topology exchange.
    
    Before authentication, the server sends a placeholder topology entry so clients don't
    block waiting for topology data. Previously, this placeholder used null connectors with
    a dummy name (PRE_AUTH_CONNECTOR), which older clients couldn't handle.
    
    The fix now sends the actual connector for the node the client is already connected to.
    Once the client authenticates, the placeholder is replaced with the real topology.
    
    (cherry picked from commit 96eae4cee2de363af8dc381a5c28772437af050c)
    
    ARTEMIS-6246
---
 .../core/impl/BackwardsCompatibilityUtils.java     |  8 ++
 .../protocol/core/impl/CoreProtocolManager.java    | 22 +++++-
 .../legacyclient/artemisTopologyTest.groovy        | 89 +++++++++++++++-------
 .../resources/legacyclient/hqTopologyTest.groovy   | 78 ++++++++++++-------
 .../compatibility/LegacyClientTopologyTest.java    | 46 +++++++++--
 5 files changed, 178 insertions(+), 65 deletions(-)

diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/BackwardsCompatibilityUtils.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/BackwardsCompatibilityUtils.java
index 2bcdf13e77..0fbc7d3c14 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/BackwardsCompatibilityUtils.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/BackwardsCompatibilityUtils.java
@@ -168,6 +168,14 @@ public class BackwardsCompatibilityUtils {
       return new Pair<>(member.getPrimary(), member.getBackup());
    }
 
+   public static Pair<TransportConfiguration, TransportConfiguration> checkTCPPairConversion(int clientIncrementingVersion,
+                                                                                             Pair<TransportConfiguration, TransportConfiguration> pair) {
+      if (clientIncrementingVersion < INITIAL_ACTIVEMQ_INCREMENTING_VERSION) {
+         return new Pair<>(convertTransport(pair.getA()), convertTransport(pair.getB()));
+      }
+      return pair;
+   }
+
    /**
     * Replaces class name and parameter names to HornetQ values.
     */
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/CoreProtocolManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/CoreProtocolManager.java
index ce8cad8d2f..807cc3cdcf 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/CoreProtocolManager.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/CoreProtocolManager.java
@@ -381,12 +381,28 @@ public class CoreProtocolManager implements ProtocolManager<Interceptor, ActiveM
 
       // Send a single dummy topology response to unblock the client's isLast=true wait (the client blocks
       // until it receives a topology message before proceeding to CREATE_SESSION). The dummy response
-      // uses PRE_AUTH_NODE_ID and an empty connector pair so no real broker identity or connectivity
+      // uses PRE_AUTH_NODE_ID. The current connector is sent so older clients wouldn't fail and would have something they can connect to.
       // information is disclosed to unauthenticated callers.
+      // Notice that this PRE_AUTH node is removed once the node is connected
       private void sendPreAuthTopology() {
          preAuthTopologySent = true;
-         Pair<TransportConfiguration, TransportConfiguration> preAuthConfig = new Pair<>(null, new TransportConfiguration(
-            rc.getTransportConnection().getConnectorConfig().getFactoryClassName(), null, Topology.PRE_AUTH_CONNECTOR_NAME));
+         TransportConfiguration connectorConfig = rc.getTransportConnection().getConnectorConfig();
+         Map<String, Object> connectorParams = new HashMap<>();
+         if (connectorConfig != null && connectorConfig.getParams() != null) {
+            Object host = connectorConfig.getParams().get(org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants.HOST_PROP_NAME);
+            Object port = connectorConfig.getParams().get(org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants.PORT_PROP_NAME);
+            if (host != null) {
+               connectorParams.put(org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants.HOST_PROP_NAME, host);
+            }
+            if (port != null) {
+               connectorParams.put(org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants.PORT_PROP_NAME, port);
+            }
+         }
+         String factoryClassName = connectorConfig != null ? connectorConfig.getFactoryClassName() : org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory.class.getName();
+         TransportConfiguration liveConnector = new TransportConfiguration(factoryClassName, connectorParams);
+         Pair<TransportConfiguration, TransportConfiguration> preAuthConfig = BackwardsCompatibilityUtils.checkTCPPairConversion(
+            channel0.getConnection().getChannelVersion(),
+            new Pair<>(liveConnector, new TransportConfiguration(factoryClassName, null, Topology.PRE_AUTH_CONNECTOR_NAME)));
 
          logger.debug("Sending pre-authentication topology to unauthenticated connection from {}.", rc.getRemoteAddress());
 
diff --git a/tests/compatibility-tests/src/main/resources/legacyclient/artemisTopologyTest.groovy b/tests/compatibility-tests/src/main/resources/legacyclient/artemisTopologyTest.groovy
index 1525b0d628..cd746d9fc0 100644
--- a/tests/compatibility-tests/src/main/resources/legacyclient/artemisTopologyTest.groovy
+++ b/tests/compatibility-tests/src/main/resources/legacyclient/artemisTopologyTest.groovy
@@ -19,27 +19,48 @@ package legacyclient
 
 import org.apache.activemq.artemis.api.core.TransportConfiguration
 import org.apache.activemq.artemis.api.core.client.ActiveMQClient
+import org.apache.activemq.artemis.api.core.client.ClientSession
 import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory
 import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants
 
 import java.lang.reflect.Field
 
-def printTopology(locator, String label) {
+def getLiveConnector(member) {
    try {
-      Field topologyField = locator.getClass().getDeclaredField("topologyArray")
-      topologyField.setAccessible(true)
-      def topologyArray = topologyField.get(locator)
-      if (topologyArray != null) {
-         println(label + " topologyArray length: " + topologyArray.length)
-         for (int t = 0; t < topologyArray.length; t++) {
-            def pair = topologyArray[t]
-            println("  [" + t + "] A=" + pair.getA() + " B=" + pair.getB())
+      return member.getPrimary()
+   } catch (Exception e) {
+      return member.getLive()
+   }
+}
+
+def printTopology(locator, String label, boolean failIfPreAuth) {
+   String preAuthError = null
+   try {
+      def topology = locator.getTopology()
+      def members = topology.getMembers()
+      println(label + " topology members: " + members.size())
+      for (def member : members) {
+         def live = getLiveConnector(member)
+         println("  nodeID=" + member.getNodeId() + " live=" + live + " backup=" + member.getBackup())
+         if (failIfPreAuth) {
+            if (live != null && "PRE_AUTH_CONNECTOR".equals(live.getName())) {
+               preAuthError = "PRE_AUTH_CONNECTOR found in live for nodeID=" + member.getNodeId()
+            }
+            if (member.getBackup() != null && "PRE_AUTH_CONNECTOR".equals(member.getBackup().getName())) {
+               preAuthError = "PRE_AUTH_CONNECTOR found in backup for nodeID=" + member.getNodeId()
+            }
          }
-      } else {
-         println(label + " topologyArray is null")
       }
+      if (preAuthError != null) {
+         throw new Exception(preAuthError)
+      }
+      return members.size()
    } catch (Exception e) {
+      if (preAuthError != null) {
+         throw e
+      }
       println(label + " Could not inspect topology: " + e.getMessage())
+      throw e
    }
 }
 
@@ -50,29 +71,39 @@ def tc = new TransportConfiguration(NettyConnectorFactory.class.getName(), param
 
 def locator = ActiveMQClient.createServerLocatorWithHA(tc)
 
-printTopology(locator, "Before first createSessionFactory:")
+printTopology(locator, "Before first createSessionFactory:", false)
 
 println("=== Attempting first createSessionFactory ===")
 
-try {
-   def sf = locator.createSessionFactory()
-   println("first createSessionFactory succeeded")
+def sf = locator.createSessionFactory()
+println("first createSessionFactory succeeded")
 
-   printTopology(locator, "After first createSessionFactory:")
 
-   println("=== Attempting second createSessionFactory ===")
-   def sf2 = locator.createSessionFactory()
-   println("second createSessionFactory succeeded")
 
-   printTopology(locator, "After second createSessionFactory:")
+def count = printTopology(locator, "After first createSessionFactory:", false)
+if (count > 1) {
+   throw new Exception("Topology after first createSessionFactory has " + count1 + " elements, expected at most 1")
+}
+
+println("=== Attempting second createSessionFactory ===")
+def sf2 = locator.createSessionFactory()
+println("second createSessionFactory succeeded")
+
+count = printTopology(locator, "After second createSessionFactory:", false)
+if (count > 1) {
+   throw new Exception("Topology after second createSessionFactory has " + count2 + " elements, expected at most 1")
+}
+
+ClientSession session = sf.createSession("guest", "guest", false, true, true, false, 0)
+
+Thread.sleep(1000);
 
-   sf2.close()
-   sf.close()
-   locator.close()
-   return true
-} catch (Exception e) {
-   println("Exception: " + e.getClass().getName() + " - " + e.getMessage())
-   e.printStackTrace()
-   locator.close()
-   return false
+count = printTopology(locator, "After authorization:", true)
+if (count != 2) {
+   throw new Exception("Cluster topology is not correctly informed to the client")
 }
+session.close();
+sf2.close()
+sf.close()
+locator.close()
+return true
diff --git a/tests/compatibility-tests/src/main/resources/legacyclient/hqTopologyTest.groovy b/tests/compatibility-tests/src/main/resources/legacyclient/hqTopologyTest.groovy
index 5af8159bf9..52d8d6efd1 100644
--- a/tests/compatibility-tests/src/main/resources/legacyclient/hqTopologyTest.groovy
+++ b/tests/compatibility-tests/src/main/resources/legacyclient/hqTopologyTest.groovy
@@ -18,28 +18,40 @@ package legacyclient
  */
 
 import org.hornetq.api.core.TransportConfiguration
+import org.hornetq.api.core.client.ClientSession
 import org.hornetq.api.core.client.HornetQClient
 import org.hornetq.core.remoting.impl.netty.NettyConnectorFactory
 import org.hornetq.core.remoting.impl.netty.TransportConstants
 
 import java.lang.reflect.Field
 
-def printTopology(locator, String label) {
+def printTopology(locator, String label, boolean failIfPreAuth) {
+   String preAuthError = null
    try {
-      Field topologyField = locator.getClass().getDeclaredField("topologyArray")
-      topologyField.setAccessible(true)
-      def topologyArray = topologyField.get(locator)
-      if (topologyArray != null) {
-         println(label + " topologyArray length: " + topologyArray.length)
-         for (int t = 0; t < topologyArray.length; t++) {
-            def pair = topologyArray[t]
-            println("  [" + t + "] A=" + pair.getA() + " B=" + pair.getB())
+      def topology = locator.getTopology()
+      def members = topology.getMembers()
+      println(label + " topology members: " + members.size())
+      for (def member : members) {
+         println("  nodeID=" + member.getNodeId() + " live=" + member.getLive() + " backup=" + member.getBackup())
+         if (failIfPreAuth) {
+            if (member.getLive() != null && "PRE_AUTH_CONNECTOR".equals(member.getLive().getName())) {
+               preAuthError = "PRE_AUTH_CONNECTOR found in live for nodeID=" + member.getNodeId()
+            }
+            if (member.getBackup() != null && "PRE_AUTH_CONNECTOR".equals(member.getBackup().getName())) {
+               preAuthError = "PRE_AUTH_CONNECTOR found in backup for nodeID=" + member.getNodeId()
+            }
          }
-      } else {
-         println(label + " topologyArray is null")
       }
+      if (preAuthError != null) {
+         throw new Exception(preAuthError)
+      }
+      return members.size()
    } catch (Exception e) {
+      if (preAuthError != null) {
+         throw e
+      }
       println(label + " Could not inspect topology: " + e.getMessage())
+      return 0
    }
 }
 
@@ -50,29 +62,39 @@ def tc = new TransportConfiguration(NettyConnectorFactory.class.getName(), param
 
 def locator = HornetQClient.createServerLocatorWithHA(tc)
 
-printTopology(locator, "Before first createSessionFactory:")
+printTopology(locator, "Before first createSessionFactory:", false)
 
 println("=== Attempting createSessionFactory (subscribes to topology before auth) ===")
 
-try {
-   def sf = locator.createSessionFactory()
-   println("createSessionFactory succeeded")
+def sf = locator.createSessionFactory()
+println("createSessionFactory succeeded")
+
+def count = printTopology(locator, "After first createSessionFactory:", false)
+if (count > 1) {
+   throw new Exception("Topology after first createSessionFactory has " + count1 + " elements, expected at most 1")
+}
+
+println("=== Attempting second createSessionFactory (should use topologyArray) ===")
+def sf2 = locator.createSessionFactory()
+println("second createSessionFactory succeeded")
 
-   printTopology(locator, "After first createSessionFactory:")
+count = printTopology(locator, "After second createSessionFactory:", false)
+if (count > 1) {
+   throw new Exception("Topology after second createSessionFactory has " + count2 + " elements, expected at most 1")
+}
 
-   println("=== Attempting second createSessionFactory (should use topologyArray) ===")
-   def sf2 = locator.createSessionFactory()
-   println("second createSessionFactory succeeded")
+ClientSession session = sf.createSession("guest", "guest", false, true, true, false, 0)
 
-   printTopology(locator, "After second createSessionFactory:")
 
-   sf2.close()
+Thread.sleep(1000);
 
-   locator.close()
-   return true
-} catch (Exception e) {
-   println("Exception: " + e.getClass().getName() + " - " + e.getMessage())
-   e.printStackTrace()
-   locator.close()
-   return false
+count = printTopology(locator, "After authorization:", true)
+if (count != 2) {
+   throw new Exception("Cluster topology is not correctly informed to the client")
 }
+session.close();
+System.out.println("count : " + count)
+sf2.close()
+sf.close()
+locator.close()
+return true
diff --git a/tests/compatibility-tests/src/test/java/org/apache/activemq/artemis/tests/compatibility/LegacyClientTopologyTest.java b/tests/compatibility-tests/src/test/java/org/apache/activemq/artemis/tests/compatibility/LegacyClientTopologyTest.java
index ec6701f390..aec7083d80 100644
--- a/tests/compatibility-tests/src/test/java/org/apache/activemq/artemis/tests/compatibility/LegacyClientTopologyTest.java
+++ b/tests/compatibility-tests/src/test/java/org/apache/activemq/artemis/tests/compatibility/LegacyClientTopologyTest.java
@@ -30,6 +30,7 @@ import static org.apache.activemq.artemis.tests.compatibility.GroovyRun.HORNETQ_
 import static org.apache.activemq.artemis.tests.compatibility.GroovyRun.SNAPSHOT;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -43,6 +44,7 @@ import org.apache.activemq.artemis.core.server.ActiveMQServers;
 import org.apache.activemq.artemis.core.server.JournalType;
 import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
 import org.apache.activemq.artemis.tests.compatibility.base.ClasspathBase;
+import org.apache.activemq.artemis.utils.Wait;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -50,6 +52,7 @@ import org.junit.jupiter.api.Test;
 public class LegacyClientTopologyTest extends ClasspathBase {
 
    private ActiveMQServer server;
+   private ActiveMQServer server2;
 
    @BeforeEach
    public void setUp() throws Exception {
@@ -58,25 +61,58 @@ public class LegacyClientTopologyTest extends ClasspathBase {
       configuration.addAcceptorConfiguration("artemis", "tcp://0.0.0.0:61616?anycastPrefix=jms.queue.&multicastPrefix=jms.topic.");
       configuration.setSecurityEnabled(true);
       configuration.setPersistenceEnabled(false);
+      configuration.setClusterPassword("guest");
       configuration.addConnectorConfiguration("netty-connector", "tcp://localhost:61616");
-      configuration.addClusterConfiguration(new ClusterConnectionConfiguration().setName("my-cluster").setConnectorName("netty-connector"));
+      configuration.addConnectorConfiguration("netty-connector-2", "tcp://localhost:61617");
+      configuration.addClusterConfiguration(new ClusterConnectionConfiguration().setName("my-cluster").setConnectorName("netty-connector").setStaticConnectors(Arrays.asList("netty-connector-2")));
 
       server = ActiveMQServers.newActiveMQServer(configuration, false);
       server.start();
 
-      ActiveMQJAASSecurityManager securityManager = (ActiveMQJAASSecurityManager) server.getSecurityManager();
+      ConfigurationImpl configuration2 = new ConfigurationImpl();
+      configuration2.setJournalType(JournalType.NIO);
+      configuration2.addAcceptorConfiguration("artemis", "tcp://0.0.0.0:61617?anycastPrefix=jms.queue.&multicastPrefix=jms.topic.");
+      configuration2.setSecurityEnabled(true);
+      configuration2.setPersistenceEnabled(false);
+      configuration2.setClusterPassword("guest");
+      configuration2.addConnectorConfiguration("netty-connector", "tcp://localhost:61616");
+      configuration2.addConnectorConfiguration("netty-connector-2", "tcp://localhost:61617");
+      configuration2.addClusterConfiguration(new ClusterConnectionConfiguration().setName("my-cluster").setConnectorName("netty-connector-2").setStaticConnectors(Arrays.asList("netty-connector")));
+
+      server2 = ActiveMQServers.newActiveMQServer(configuration2, false);
+      server2.start();
+
+      setupSecurity(server);
+      setupSecurity(server2);
+
+
+      Wait.assertTrue(() -> {
+         for (org.apache.activemq.artemis.core.server.cluster.ClusterConnection cc : server.getClusterManager().getClusterConnections()) {
+            if (cc.getTopology().getMembers().size() == 2) {
+               return true;
+            }
+         }
+         return false;
+      }, 10000, 100);
+
+      server.createQueue(QueueConfiguration.of("jms.queue.testQueue").setRoutingType(RoutingType.ANYCAST).setDurable(false));
+   }
+
+   private void setupSecurity(ActiveMQServer srv) {
+      ActiveMQJAASSecurityManager securityManager = (ActiveMQJAASSecurityManager) srv.getSecurityManager();
       securityManager.getConfiguration().addUser("guest", "guest");
       securityManager.getConfiguration().addRole("guest", "guest");
 
       Set<Role> roles = new HashSet<>();
       roles.add(new Role("guest", true, true, true, true, true, true, true, true, true, true, false, false));
-      server.getSecurityRepository().addMatch("#", roles);
-
-      server.createQueue(QueueConfiguration.of("jms.queue.testQueue").setRoutingType(RoutingType.ANYCAST).setDurable(false));
+      srv.getSecurityRepository().addMatch("#", roles);
    }
 
    @AfterEach
    public void tearDown() throws Exception {
+      if (server2 != null) {
+         server2.stop();
+      }
       if (server != null) {
          server.stop();
       }


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


(artemis) 06/11: Enforce auth checks for RemoveSubscriptionInfo and other openwire commands that arrive before the ConnectionInfo is received.

Posted by cl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit d94fc56b81361e934605c88593f62fbcea30dc03
Author: Timothy Bish <ta...@gmail.com>
AuthorDate: Mon Aug 10 15:21:49 2026 -0400

    Enforce auth checks for RemoveSubscriptionInfo and other openwire
    commands that arrive before the ConnectionInfo is received.
    
    (cherry picked from commit 5b541819a274f1fff1e3db8aa1bea2598d1a018b)
    
    ARTEMIS-6248
---
 .../core/protocol/openwire/OpenWireConnection.java |  34 +-
 .../openwire/amq/OpenWireConnectionTest.java       |   2 -
 .../OpenWireRemoveSubscriptionInfoFrameTest.java   | 570 +++++++++++++++++++++
 3 files changed, 598 insertions(+), 8 deletions(-)

diff --git a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireConnection.java b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireConnection.java
index 8f7105ae11..58b41dc05c 100644
--- a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireConnection.java
+++ b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireConnection.java
@@ -139,6 +139,11 @@ import org.slf4j.LoggerFactory;
  */
 public class OpenWireConnection extends AbstractRemotingConnection implements SecurityAuth, TempResourceObserver {
 
+   private static final Set<Class<? extends Command>> PRE_AUTHENTICATION_ALLOWED_SET;
+
+   static {
+      PRE_AUTHENTICATION_ALLOWED_SET = Set.of(WireFormatInfo.class, ConnectionControl.class, ConnectionInfo.class);
+   }
 
    private final Object lockSend = new Object();
 
@@ -339,7 +344,6 @@ public class OpenWireConnection extends AbstractRemotingConnection implements Se
       }
    }
 
-
    private void act(Command command) {
       try {
          recoverOperationContext();
@@ -354,6 +358,15 @@ public class OpenWireConnection extends AbstractRemotingConnection implements Se
             return;
          }
 
+         // Any command that isn't expected to arrive before a connection create event from
+         // the ConnectionInfo should just fail the connection as we only support well behaved
+         // client connections, not other bridged ActiveMQ brokers.
+         if (state == null && !PRE_AUTHENTICATION_ALLOWED_SET.contains(command.getClass())) {
+            logger.debug("Rejecting OpenWire connection that sent unexpected command before authentication: {}", command);
+            shutdown(true);
+            return;
+         }
+
          boolean responseRequired = command.isResponseRequired();
          int commandId = command.getCommandId();
 
@@ -379,9 +392,10 @@ public class OpenWireConnection extends AbstractRemotingConnection implements Se
                   serviceException(cause);
                   response = null;
                }
-               // If there was an exception when processing ConnectionInfo we should
-               // stop the connection to prevent dangling sockets
-               if (command instanceof ConnectionInfo) {
+               // If there was an exception when processing ConnectionInfo or any other command
+               // that arrives before a connection info exchange triggers an error we should stop
+               // the connection to prevent dangling sockets
+               if (command instanceof ConnectionInfo || state == null) {
                   delayedStop(2000, cause.getMessage(), cause);
                }
             }
@@ -1339,8 +1353,16 @@ public class OpenWireConnection extends AbstractRemotingConnection implements Se
 
       @Override
       public Response processRemoveSubscription(RemoveSubscriptionInfo subInfo) throws Exception {
-         SimpleString subQueueName = org.apache.activemq.artemis.jms.client.ActiveMQDestination.createQueueNameForSubscription(true, subInfo.getClientId(), subInfo.getSubscriptionName());
-         server.destroyQueue(subQueueName);
+         final ConnectionState cs = getState();
+         final ServerSession ss = internalSession;
+
+         if (cs == null || ss == null) {
+            throw new IllegalStateException("Cannot remove a subscription on a connection that had not been registered");
+         }
+
+         final SimpleString subQueueName = org.apache.activemq.artemis.jms.client.ActiveMQDestination.createQueueNameForSubscription(true, subInfo.getClientId(), subInfo.getSubscriptionName());
+
+         ss.deleteQueue(subQueueName);
 
          return null;
       }
diff --git a/artemis-protocols/artemis-openwire-protocol/src/test/java/org/apache/activemq/artemis/core/protocol/openwire/amq/OpenWireConnectionTest.java b/artemis-protocols/artemis-openwire-protocol/src/test/java/org/apache/activemq/artemis/core/protocol/openwire/amq/OpenWireConnectionTest.java
index 6868fca377..893579b1c1 100644
--- a/artemis-protocols/artemis-openwire-protocol/src/test/java/org/apache/activemq/artemis/core/protocol/openwire/amq/OpenWireConnectionTest.java
+++ b/artemis-protocols/artemis-openwire-protocol/src/test/java/org/apache/activemq/artemis/core/protocol/openwire/amq/OpenWireConnectionTest.java
@@ -159,8 +159,6 @@ public class OpenWireConnectionTest {
          sessionInfoBuffer.resetReaderIndex();
          producerInfoBuffer.resetReaderIndex();
          removeInfoBuffer.resetReaderIndex();
-
       }
    }
-
 }
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/openwire/OpenWireRemoveSubscriptionInfoFrameTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/openwire/OpenWireRemoveSubscriptionInfoFrameTest.java
new file mode 100644
index 0000000000..1e96b55a50
--- /dev/null
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/openwire/OpenWireRemoveSubscriptionInfoFrameTest.java
@@ -0,0 +1,570 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.tests.integration.openwire;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
+
+import javax.jms.Connection;
+import javax.jms.DeliveryMode;
+import javax.jms.JMSSecurityException;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+import javax.jms.Topic;
+import javax.resource.spi.IllegalStateException;
+
+import org.apache.activemq.artemis.api.core.QueueConfiguration;
+import org.apache.activemq.artemis.api.core.SimpleString;
+import org.apache.activemq.artemis.core.config.Configuration;
+import org.apache.activemq.artemis.core.security.Role;
+import org.apache.activemq.artemis.core.server.Queue;
+import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
+import org.apache.activemq.artemis.utils.RandomUtil;
+import org.apache.activemq.artemis.utils.Wait;
+import org.apache.activemq.command.Command;
+import org.apache.activemq.command.ConnectionId;
+import org.apache.activemq.command.ConnectionInfo;
+import org.apache.activemq.command.ExceptionResponse;
+import org.apache.activemq.command.RemoveSubscriptionInfo;
+import org.apache.activemq.command.Response;
+import org.apache.activemq.command.WireFormatInfo;
+import org.apache.activemq.openwire.OpenWireFormat;
+import org.apache.activemq.openwire.OpenWireFormatFactory;
+import org.apache.activemq.transport.netty.NettyTransport;
+import org.apache.activemq.transport.netty.NettyTransportFactory;
+import org.apache.activemq.transport.netty.NettyTransportListener;
+import org.apache.activemq.util.ByteSequence;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufInputStream;
+import io.netty.buffer.Unpooled;
+
+@Timeout(20)
+public class OpenWireRemoveSubscriptionInfoFrameTest extends BasicOpenWireTest {
+
+   private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+   private final String ALLOWED_USER = "allowedUser";
+   private final String ALLOWED_ROLE = "allowedRole";
+   private final String PASS = RandomUtil.randomUUIDString();
+   private final String ADDRESS = "myAddress";
+
+   private final String DENIED_USER = "deniedUser";
+   private final String DENIED_ROLE = "deniedRole";
+
+   @Override
+   @BeforeEach
+   public void setUp() throws Exception {
+      realStore = false;
+      enableSecurity = true;
+
+      super.setUp();
+   }
+
+   @Override
+   protected void extraServerConfig(Configuration configuration) {
+      super.extraServerConfig(configuration);
+
+      final Role allowed = new Role(ALLOWED_ROLE, true, true, true, true, false, false, false, false, true, false, false, false);
+      final Role denied = new Role(DENIED_ROLE, false, false, false, false, false, false, false, false, false, false, false, false);
+
+      final ActiveMQJAASSecurityManager securityManager = (ActiveMQJAASSecurityManager) server.getSecurityManager();
+
+      securityManager.getConfiguration().addUser(ALLOWED_USER, PASS);
+      securityManager.getConfiguration().addRole(ALLOWED_USER, ALLOWED_ROLE);
+      securityManager.getConfiguration().addRole(ALLOWED_USER, "advisoryReceiver");
+      securityManager.getConfiguration().addUser(DENIED_USER, PASS);
+      securityManager.getConfiguration().addRole(DENIED_USER, DENIED_ROLE);
+      securityManager.getConfiguration().addRole(DENIED_USER, "advisoryReceiver");
+
+      configuration.putSecurityRoles(ADDRESS, Set.of(allowed, denied));
+   }
+
+   @Test
+   public void testUnauthenticatedConnectionCannotDeleteSubscriptionQueue() throws Exception {
+      final String CLIENT_ID = "test-id";
+      final String SUBSCRIPTION_NAME = getTestMethodName();
+      final SimpleString SUBSCRIPTION_QUEUE = org.apache.activemq.artemis.jms.client.ActiveMQDestination.createQueueNameForSubscription(true, CLIENT_ID,
+         SUBSCRIPTION_NAME);
+
+      try (Connection connection = factory.createConnection(ALLOWED_USER, PASS)) {
+
+         connection.setClientID(CLIENT_ID);
+
+         final Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+         final Topic destination = session.createTopic(ADDRESS);
+
+         final MessageProducer producer = session.createProducer(destination);
+         final MessageConsumer consumer = session.createDurableConsumer(destination, SUBSCRIPTION_NAME);
+
+         consumer.close();
+
+         producer.setDeliveryMode(DeliveryMode.PERSISTENT); // ALLOWED USER
+         producer.send(session.createTextMessage("durable-message"));
+
+         Wait.assertTrue(() -> server.queueQuery(SUBSCRIPTION_QUEUE).isExists(), 1_000, 50);
+
+         final Queue subscriptionQueue = server.locateQueue(SUBSCRIPTION_QUEUE);
+
+         assertEquals(1, subscriptionQueue.getMessageCount());
+
+         connection.close();
+
+         final RemoveSubscriptionInfo removeSubscription = new RemoveSubscriptionInfo();
+         removeSubscription.setClientId(CLIENT_ID);
+         removeSubscription.setConnectionId(new ConnectionId("test:1"));
+         removeSubscription.setResponseRequired(true);
+         removeSubscription.setSubcriptionName(SUBSCRIPTION_NAME);
+
+         try (OpenwireTestClient client = new OpenwireTestClient(OWHOST, OWPORT)) {
+            client.connect().get(2, TimeUnit.SECONDS);
+
+            try {
+               client.send(removeSubscription).get(10, TimeUnit.MILLISECONDS);
+
+               fail("Should have timed out waiting for the requested response");
+            } catch (TimeoutException e) {
+               // expected
+            }
+
+            client.awaitRemoteClose(2, TimeUnit.SECONDS);
+         }
+
+         Wait.assertTrue(() -> server.queueQuery(SUBSCRIPTION_QUEUE).isExists(), 1_000, 50);
+      }
+   }
+
+   @Test
+   public void testConnectionWithoutAuthorizationCannotDeleteSubscriptionQueue() throws Exception {
+      final String CLIENT_ID = "test-id";
+      final String SUBSCRIPTION_NAME = getTestMethodName();
+      final SimpleString SUBSCRIPTION_QUEUE = org.apache.activemq.artemis.jms.client.ActiveMQDestination.createQueueNameForSubscription(true, CLIENT_ID,
+         SUBSCRIPTION_NAME);
+
+      try (Connection connection = factory.createConnection(ALLOWED_USER, PASS)) {
+
+         connection.setClientID(CLIENT_ID);
+
+         final Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+         final Topic destination = session.createTopic(ADDRESS);
+
+         final MessageProducer producer = session.createProducer(destination);
+         final MessageConsumer consumer = session.createDurableConsumer(destination, SUBSCRIPTION_NAME);
+
+         consumer.close();
+
+         producer.setDeliveryMode(DeliveryMode.PERSISTENT); // ALLOWED USER
+         producer.send(session.createTextMessage("durable-message"));
+
+         connection.close();
+
+         Wait.assertTrue(() -> server.queueQuery(SUBSCRIPTION_QUEUE).isExists(), 1_000, 50);
+
+         final Queue subscriptionQueue = server.locateQueue(SUBSCRIPTION_QUEUE);
+
+         assertEquals(1, subscriptionQueue.getMessageCount());
+
+         final ConnectionInfo connectionInfo = new ConnectionInfo();
+         connectionInfo.setConnectionId(new ConnectionId("test:1"));
+         connectionInfo.setClientId(UUID.randomUUID().toString());
+         connectionInfo.setUserName(DENIED_USER);
+         connectionInfo.setPassword(PASS);
+         connectionInfo.setResponseRequired(true);
+
+         final RemoveSubscriptionInfo removeSubscription = new RemoveSubscriptionInfo();
+         removeSubscription.setClientId(CLIENT_ID);
+         removeSubscription.setConnectionId(new ConnectionId("test:1"));
+         removeSubscription.setResponseRequired(true);
+         removeSubscription.setSubcriptionName(SUBSCRIPTION_NAME);
+
+         try (OpenwireTestClient client = new OpenwireTestClient(OWHOST, OWPORT)) {
+            client.connect().get(2, TimeUnit.SECONDS);
+            client.send(connectionInfo).get(20, TimeUnit.SECONDS);
+
+            final Response response = client.send(removeSubscription).get(10, TimeUnit.SECONDS);
+
+            assertTrue(response.isException());
+            assertNull(client.getLastError());
+
+            final ExceptionResponse exResponse = (ExceptionResponse) response;
+
+            assertTrue(exResponse.getException() instanceof JMSSecurityException);
+         }
+
+         Thread.sleep(50); // Bit of buffer to allow the command to be handled.
+
+         Wait.assertTrue(() -> server.queueQuery(SUBSCRIPTION_QUEUE).isExists(), 1_000, 50);
+      }
+   }
+
+   @Test
+   public void testCannotDeleteCreatedQueueWithRemoveSubscriptionInfoResponse() throws Exception {
+      testCannotDeleteCreatedQueueWithRemoveSubscriptionInfo(true);
+   }
+
+   @Test
+   public void testCannotDeleteCreatedQueueWithRemoveSubscriptionInfoNoResponse() throws Exception {
+      testCannotDeleteCreatedQueueWithRemoveSubscriptionInfo(false);
+   }
+
+   private void testCannotDeleteCreatedQueueWithRemoveSubscriptionInfo(boolean requireResponse) throws Exception {
+      final String QUEUE_NAME = getTestMethodName() + "deleteMe";
+
+      server.createQueue(QueueConfiguration.of(QUEUE_NAME));
+
+      Wait.assertTrue(() -> server.queueQuery(SimpleString.of(QUEUE_NAME)).isExists(), 1_000, 50);
+
+      final RemoveSubscriptionInfo removeSubscription = new RemoveSubscriptionInfo();
+      removeSubscription.setClientId(null);
+      removeSubscription.setConnectionId(new ConnectionId("test:1"));
+      removeSubscription.setResponseRequired(requireResponse);
+      removeSubscription.setSubcriptionName(QUEUE_NAME);
+
+      try (OpenwireTestClient client = new OpenwireTestClient(OWHOST, OWPORT)) {
+         client.connect().get(2, TimeUnit.SECONDS);
+
+         try {
+            client.send(removeSubscription).get(10, TimeUnit.MILLISECONDS);
+
+            if (removeSubscription.isResponseRequired()) {
+               fail("Should have timed out waiting for the requested response");
+            }
+         } catch (TimeoutException e) {
+            assertTrue(requireResponse);
+         }
+
+         client.awaitRemoteClose(2, TimeUnit.SECONDS);
+      }
+
+      Wait.assertTrue(() -> server.queueQuery(SimpleString.of(QUEUE_NAME)).isExists(), 500, 50);
+   }
+
+   protected class OpenwireTestClient implements AutoCloseable {
+
+      private final NettyTransportListener listener = new InternalNettyTransportListener();
+      private final CountDownLatch remoteClosed = new CountDownLatch(1);
+      private final OpenWireFormatFactory wfFactory = new OpenWireFormatFactory();
+      {
+         wfFactory.setVersion(OpenWireFormat.DEFAULT_WIRE_VERSION);
+         wfFactory.setMaxInactivityDuration(30);
+         wfFactory.setTightEncodingEnabled(false);
+         wfFactory.setCacheEnabled(false);
+         wfFactory.setMaxFrameSize(8192);
+      }
+      private final List<Command> received = new CopyOnWriteArrayList<>();
+      private final Map<Integer, CompletableFuture<Response>> requests = new ConcurrentHashMap<>();
+      private final OpenWireFormat wf;
+      private final CompletableFuture<Response> wireFormatInfoResponse = new CompletableFuture<>();
+
+      private final String host;
+      private final int port;
+
+      private NettyTransport tcpClient;
+      private Throwable lastError;
+      private int commandId = 0;
+
+      public OpenwireTestClient(String host, int port) {
+         Objects.requireNonNull(host);
+
+         if (port <= 0) {
+            throw new IllegalArgumentException("Port value given is out of range.");
+         }
+
+         this.host = host;
+         this.port = port;
+         this.wf = (OpenWireFormat) wfFactory.createWireFormat();
+      }
+
+      public Future<Response> connect() throws Exception {
+         if (tcpClient != null) {
+            throw new IllegalStateException("This client can only be connected once");
+         }
+
+         final URI openwireURI = new URI("tcp://" + host + ":" + port);
+
+         tcpClient = NettyTransportFactory.createTransport(openwireURI);
+         tcpClient.setTransportListener(listener);
+         tcpClient.connect();
+
+         send(wf.getPreferedWireFormatInfo());
+
+         return wireFormatInfoResponse;
+      }
+
+      public Future<Response> send(Command command) throws Exception {
+         command.setCommandId(commandId++);
+
+         final CompletableFuture<Response> pending;
+
+         if (command.isWireFormatInfo()) {
+            pending = wireFormatInfoResponse;
+         } else if (command.isResponseRequired()) {
+            pending = new CompletableFuture<Response>();
+            requests.put(command.getCommandId(), pending);
+         } else {
+            pending = CompletableFuture.completedFuture(null);
+         }
+
+         final ByteSequence commandBytes = wf.marshal(command);
+         final ByteBuf wrapper = Unpooled.wrappedBuffer(commandBytes.getData(), commandBytes.getOffset(), commandBytes.getLength());
+
+         wrapper.writerIndex(commandBytes.getLength());
+
+         tcpClient.send(wrapper).await();
+
+         return pending;
+      }
+
+      public boolean isConnected() {
+         return tcpClient.isConnected();
+      }
+
+      public void awaitRemoteClose(int timeout, TimeUnit units) throws Exception {
+         assertTrue(remoteClosed.await(timeout, units));
+      }
+
+      public Throwable getLastError() {
+         return lastError;
+      }
+
+      public int receivedCount() {
+         return received.size();
+      }
+
+      public void foreach(Consumer<? super Command> commandConsumer) {
+         received.forEach(commandConsumer);
+      }
+
+      @Override
+      public void close() throws Exception {
+         if (tcpClient != null) {
+            tcpClient.close();
+         }
+      }
+
+      private class InternalNettyTransportListener implements NettyTransportListener {
+
+         private final FrameSizeParsingStage frameSizeParser = new FrameSizeParsingStage();
+         private final FrameBufferingStage frameBufferingStage = new FrameBufferingStage();
+         private final FrameBodyParsingStage frameBodyParsingStage = new FrameBodyParsingStage();
+
+         private FrameParserStage stage = new FrameSizeParsingStage();
+
+         @Override
+         public void onData(ByteBuf incoming) {
+            LOG.debug("Incoming data packet from server: {}", incoming);
+
+            while (incoming.isReadable()) {
+               try {
+                  stage.parse(incoming);
+               } catch (Exception e) {
+                  LOG.error("Caught error while decoding incoming frame:", e);
+                  lastError = e;
+               }
+            }
+         }
+
+         @Override
+         public void onTransportClosed() {
+            LOG.debug("Transport reports connection closed");
+            remoteClosed.countDown();
+         }
+
+         @Override
+         public void onTransportError(Throwable cause) {
+            LOG.debug("Transport reports error: ", cause);
+            lastError = cause;
+            remoteClosed.countDown();
+         }
+
+         protected void onCommand(Command command) throws IOException {
+            if (command.getDataStructureType() == WireFormatInfo.DATA_STRUCTURE_TYPE) {
+               wf.renegotiateWireFormat((WireFormatInfo) command);
+               wireFormatInfoResponse.complete(null);
+            }
+
+            if (command instanceof Response response) {
+               final int correlationId = response.getCorrelationId();
+               final CompletableFuture<Response> pending = requests.remove(correlationId);
+
+               if (pending != null) {
+                  pending.complete(response);
+               }
+            }
+
+            received.add(command);
+         }
+
+         private FrameParserStage transitionToFrameSizeParsingStage() {
+            return stage = frameSizeParser.reset(0);
+         }
+
+         private FrameParserStage transitionToFrameBufferingStage(int length) {
+            return stage = frameBufferingStage.reset(length);
+         }
+
+         private FrameParserStage initializeFrameUnmarshalStage(int length) {
+            return stage = frameBodyParsingStage.reset(length);
+         }
+
+         private interface FrameParserStage {
+
+            void parse(ByteBuf input) throws IOException;
+
+            FrameParserStage reset(int length);
+
+         }
+
+         private final class FrameSizeParsingStage implements FrameParserStage {
+
+            private int frameSize;
+            private int multiplier = Integer.BYTES;
+
+            @Override
+            public void parse(ByteBuf input) throws IOException {
+               if (multiplier == Integer.BYTES && input.readableBytes() >= Integer.BYTES) {
+                  frameSize = input.getInt(input.readerIndex());
+                  multiplier = 0;
+               } else {
+                  readFrameSizeInChunks(input);
+               }
+
+               if (multiplier == 0) {
+                  int length = frameSize + Integer.BYTES; // Ensure the size prefix is included
+
+                  if (input.readableBytes() < length) {
+                     transitionToFrameBufferingStage(length);
+                  } else {
+                     initializeFrameUnmarshalStage(length);
+                  }
+
+                  stage.parse(input);
+               }
+            }
+
+            private void readFrameSizeInChunks(ByteBuf input) {
+               while (input.isReadable()) {
+                  frameSize |= ((input.getByte(input.readerIndex() + (Integer.MAX_VALUE - multiplier--)) & 0xFF) << multiplier * Byte.SIZE);
+                  if (multiplier == 0) {
+                     break;
+                  }
+               }
+            }
+
+            @Override
+            public FrameSizeParsingStage reset(int frameSize) {
+               multiplier = Integer.BYTES;
+               this.frameSize = frameSize;
+               return this;
+            }
+         }
+
+         private final class FrameBufferingStage implements FrameParserStage {
+
+            private ByteBuf buffer;
+            private int frameBytesRemaining;
+            private int frameSize;
+
+            @Override
+            public void parse(ByteBuf input) throws IOException {
+               if (input.readableBytes() > frameBytesRemaining) {
+                  buffer.writeBytes(input, frameBytesRemaining);
+               } else {
+                  buffer.writeBytes(input);
+               }
+
+               frameBytesRemaining = frameSize - buffer.readableBytes();
+
+               if (frameBytesRemaining == 0) {
+                  // Now we can consume the buffer frame body.
+                  initializeFrameUnmarshalStage(buffer.readableBytes());
+                  try {
+                     stage.parse(buffer);
+                  } finally {
+                     buffer = null;
+                  }
+               }
+            }
+
+            @Override
+            public FrameBufferingStage reset(int length) {
+               buffer = Unpooled.buffer(length);
+               frameBytesRemaining = length;
+               frameSize = length;
+
+               return this;
+            }
+         }
+
+         private final class FrameBodyParsingStage implements FrameParserStage {
+
+            private int length;
+
+            @Override
+            public void parse(ByteBuf input) throws IOException {
+               try (ByteBufInputStream bbis = new ByteBufInputStream(input, length);
+                    DataInputStream dais = new DataInputStream(bbis)) {
+
+                  final Object result = wf.unmarshal(dais);
+
+                  if (result instanceof Command command) {
+                     onCommand(command);
+                  } else {
+                     throw new IOException("Unknown type returned from Openwire frame parser");
+                  }
+               } catch (Exception e) {
+                  LOG.error("Failed while decoding command: ", e);
+               } finally {
+                  transitionToFrameSizeParsingStage();
+               }
+            }
+
+            @Override
+            public FrameBodyParsingStage reset(int length) {
+               this.length = length;
+               return this;
+            }
+         }
+      }
+   }
+}


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


(artemis) 05/11: Remove unused packet handling

Posted by cl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 905ae7b8555178d52b61af262456c3a32c391721
Author: Domenico Francesco Bruscino <br...@apache.org>
AuthorDate: Fri Jul 17 11:50:45 2026 -0400

    Remove unused packet handling
    
    (cherry picked from commit 4e32f8e0d13c3413851f7aa574e83bdab346bf1b)
    
    ARTEMIS-6243
---
 .../protocol/core/impl/ActiveMQPacketHandler.java  | 22 ----------------------
 1 file changed, 22 deletions(-)

diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQPacketHandler.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQPacketHandler.java
index 2f399498f8..b23903f88c 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQPacketHandler.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQPacketHandler.java
@@ -23,7 +23,6 @@ import org.apache.activemq.artemis.api.core.ActiveMQException;
 import org.apache.activemq.artemis.api.core.ActiveMQExceptionType;
 import org.apache.activemq.artemis.api.core.ActiveMQInternalErrorException;
 import org.apache.activemq.artemis.api.core.ActiveMQSecurityException;
-import org.apache.activemq.artemis.api.core.QueueConfiguration;
 import org.apache.activemq.artemis.api.core.RoutingType;
 import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.core.persistence.OperationContext;
@@ -35,7 +34,6 @@ import org.apache.activemq.artemis.core.protocol.core.ServerSessionPacketHandler
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ActiveMQExceptionMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ConnectMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ConnectResponseMessage;
-import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateQueueMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSessionMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSessionMessage_V2;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSessionResponseMessage;
@@ -120,15 +118,6 @@ public class ActiveMQPacketHandler implements ChannelHandler {
             channel1.send(new ReattachSessionResponseMessage(-1, false));
             break;
          }
-         case PacketImpl.CREATE_QUEUE: {
-            // Create queue can also be fielded here in the case of a replicated store and forward queue creation
-
-            CreateQueueMessage request = (CreateQueueMessage) packet;
-
-            handleCreateQueue(request);
-
-            break;
-         }
          default: {
             ActiveMQServerLogger.LOGGER.invalidPacket(packet);
          }
@@ -260,15 +249,4 @@ public class ActiveMQPacketHandler implements ChannelHandler {
       }
    }
 
-   private void handleCreateQueue(final CreateQueueMessage request) {
-      try {
-         server.createQueue(QueueConfiguration.of(request.getQueueName())
-                               .setAddress(request.getAddress())
-                               .setFilterString(request.getFilterString())
-                               .setDurable(request.isDurable())
-                               .setTemporary(request.isTemporary()));
-      } catch (Exception e) {
-         ActiveMQServerLogger.LOGGER.failedToHandleCreateQueue(e);
-      }
-   }
 }
\ No newline at end of file


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