You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@activemq.apache.org by cl...@apache.org on 2021/08/06 12:34:37 UTC

[activemq-artemis] branch main updated (49e3843 -> ce87777)

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/activemq-artemis.git.


    from 49e3843  ARTEMIS-3340 Atomic server restart and clean dirty activation: fix testFailbackTimeout
     new 6d2b96c  ARTEMIS-3275 Lock CORE client communication during failover retries
     new 3555dd7  ARTEMIS-3365 Add broker balancers
     new ce87777  This closes #3634

The 3 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:
 .../artemis/cli/commands/check/CheckAbstract.java  |   6 +-
 .../artemis/cli/commands/check/CheckContext.java   |   6 +-
 .../artemis/cli/commands/check/NodeCheck.java      |  27 +-
 .../artemis/cli/commands/check/QueueCheck.java     |   2 +-
 .../artemis/api/core/ActiveMQExceptionType.java    |   6 +
 .../api/core/ActiveMQRedirectedException.java      |  24 +-
 .../artemis/api/core/DisconnectReason.java         |  73 ++++
 .../artemis/api/core/TransportConfiguration.java   |   2 +-
 .../artemis/api/core/client/ClientSession.java     |   8 +
 .../api/core/client/ClientSessionFactory.java      |  26 ++
 .../core/management/ActiveMQManagementProxy.java   |  73 ++--
 .../api/core/management/BrokerBalancerControl.java |  25 +-
 .../api/core/management/ObjectNameBuilder.java     |   9 +
 .../artemis/api/core/management/ResourceNames.java |   2 +
 .../core/client/ActiveMQClientMessageBundle.java   |   4 +
 .../core/client/impl/ClientSessionFactoryImpl.java | 248 +++++++------
 .../core/client/impl/ClientSessionImpl.java        |  50 ++-
 .../core/client/impl/ClientSessionInternal.java    |   2 +
 .../artemis/core/protocol/core/Channel.java        |   7 +
 .../core/protocol/core/CoreRemotingConnection.java |  10 +
 .../core/impl/ActiveMQClientProtocolManager.java   |  63 ++--
 .../protocol/core/impl/ActiveMQSessionContext.java |   2 +-
 .../core/protocol/core/impl/ChannelImpl.java       |   9 +
 .../core/protocol/core/impl/PacketDecoder.java     |  18 +
 .../core/protocol/core/impl/PacketImpl.java        |   7 +
 .../protocol/core/impl/RemotingConnectionImpl.java |  23 +-
 .../ClusterTopologyChangeMessage_V3.java           |  29 +-
 ...3.java => ClusterTopologyChangeMessage_V4.java} |  68 ++--
 .../core/impl/wireformat/CreateSessionMessage.java |  37 +-
 .../impl/wireformat/CreateSessionMessage_V2.java   | 103 ++++++
 .../core/impl/wireformat/DisconnectMessage_V3.java | 137 +++++++
 .../remoting/impl/netty/TransportConstants.java    |   5 +
 .../spi/core/protocol/RemotingConnection.java      |   8 +
 .../spi/core/remoting/ClientProtocolManager.java   |   3 +-
 .../artemis/spi/core/remoting/Connection.java      |   8 +
 .../spi/core/remoting/TopologyResponseHandler.java |   3 +-
 .../src/main/resources/activemq-version.properties |   2 +-
 .../core/management/OperationAnnotationTest.java   |   3 +-
 .../artemis/jms/client/ActiveMQConnection.java     |  30 +-
 .../amqp/broker/ProtonProtocolManager.java         |  10 +-
 .../amqp/client/ProtonClientProtocolManager.java   |   2 +-
 .../amqp/connect/AMQPBrokerConnectionManager.java  |   3 +-
 .../amqp/proton/AMQPConnectionContext.java         |   4 +-
 .../protocol/amqp/proton/AMQPRedirectContext.java  |  37 ++
 .../protocol/amqp/proton/AMQPRedirectHandler.java  |  64 ++++
 .../amqp/sasl/AnonymousServerSASLFactory.java      |   3 +-
 .../amqp/sasl/ExternalServerSASLFactory.java       |   3 +-
 .../amqp/sasl/GSSAPIServerSASLFactory.java         |   3 +-
 .../protocol/amqp/sasl/PlainServerSASLFactory.java |   3 +-
 .../protocol/amqp/sasl/ServerSASLFactory.java      |   3 +-
 .../amqp/sasl/scram/SCRAMServerSASLFactory.java    |   3 +-
 .../client/HornetQClientProtocolManager.java       |   9 +-
 .../core/protocol/mqtt/MQTTProtocolHandler.java    |  16 +-
 .../core/protocol/mqtt/MQTTProtocolManager.java    |  10 +-
 .../core/protocol/mqtt/MQTTRedirectContext.java    |  33 +-
 .../core/protocol/mqtt/MQTTRedirectHandler.java    |  55 +++
 .../core/protocol/openwire/OpenWireConnection.java |   6 +
 .../protocol/openwire/OpenWireProtocolManager.java |  10 +-
 .../protocol/openwire/OpenWireRedirectContext.java |  33 +-
 .../protocol/openwire/OpenWireRedirectHandler.java |  58 +++
 .../core/protocol/stomp/StompProtocolManager.java  |   8 +-
 .../artemis/ra/inflow/ActiveMQActivation.java      |   3 +-
 .../artemis/core/config/Configuration.java         |  13 +
 .../balancing/BrokerBalancerConfiguration.java     |  95 +++++
 .../balancing/PolicyConfiguration.java}            |  39 +-
 .../core/config/balancing/PoolConfiguration.java   | 123 +++++++
 .../core/config/impl/ConfigurationImpl.java        |  20 ++
 .../artemis/core/config/impl/Validators.java       |  11 +
 .../deployers/impl/FileConfigurationParser.java    | 102 +++++-
 .../management/impl/BrokerBalancerControlImpl.java | 160 +++++++++
 .../core/management/impl/view/ConsumerView.java    |   2 +-
 .../core/management/impl/view/ProducerView.java    |   2 +-
 .../artemis/core/protocol/ProtocolHandler.java     |   5 +
 .../protocol/core/impl/ActiveMQPacketHandler.java  |  14 +-
 .../core/impl/ActiveMQRedirectContext.java}        |  28 +-
 .../core/impl/ActiveMQRedirectHandler.java         |  52 +++
 .../protocol/core/impl/CoreProtocolManager.java    |  25 +-
 .../core/remoting/impl/netty/NettyAcceptor.java    |   7 +-
 ...onnection.java => NettySNIHostnameHandler.java} |  36 +-
 .../remoting/impl/netty/NettyServerConnection.java |  22 +-
 .../artemis/core/server/ActiveMQMessageBundle.java |  11 +
 .../artemis/core/server/ActiveMQServer.java        |   3 +
 .../artemis/core/server/ActiveMQServerLogger.java  |  14 +
 .../core/server/balancing/BrokerBalancer.java      | 193 ++++++++++
 .../server/balancing/BrokerBalancerManager.java    | 191 ++++++++++
 .../core/server/balancing/RedirectContext.java     |  57 +++
 .../core/server/balancing/RedirectHandler.java     |  74 ++++
 .../balancing/policies/AbstractPolicy.java}        |  46 ++-
 .../balancing/policies/ConsistentHashPolicy.java   |  75 ++++
 .../balancing/policies/DefaultPolicyFactory.java   |  49 +++
 .../balancing/policies/FirstElementPolicy.java}    |  39 +-
 .../balancing/policies/LeastConnectionsPolicy.java | 133 +++++++
 .../balancing/policies/Policy.java}                |  32 +-
 .../balancing/policies/PolicyFactory.java}         |  26 +-
 .../balancing/policies/PolicyFactoryResolver.java  |  74 ++++
 .../balancing/policies/RoundRobinPolicy.java       |  47 +++
 .../core/server/balancing/pools/AbstractPool.java  | 243 +++++++++++++
 .../core/server/balancing/pools/ClusterPool.java   |  69 ++++
 .../balancing/pools/DiscoveryGroupService.java     |  87 +++++
 .../core/server/balancing/pools/DiscoveryPool.java |  70 ++++
 .../server/balancing/pools/DiscoveryService.java   |  88 +++++
 .../artemis/core/server/balancing/pools/Pool.java  |  65 ++++
 .../core/server/balancing/pools/StaticPool.java    |  44 +++
 .../server/balancing/targets/AbstractTarget.java   | 112 ++++++
 .../balancing/targets/AbstractTargetFactory.java}  |  41 ++-
 .../server/balancing/targets/ActiveMQTarget.java   | 132 +++++++
 .../balancing/targets/ActiveMQTargetFactory.java}  |  30 +-
 .../core/server/balancing/targets/LocalTarget.java |  69 ++++
 .../core/server/balancing/targets/Target.java      |  59 +++
 .../balancing/targets/TargetFactory.java}          |  30 +-
 .../core/server/balancing/targets/TargetKey.java   |  53 +++
 .../balancing/targets/TargetKeyResolver.java       | 108 ++++++
 .../balancing/targets/TargetListener.java}         |  26 +-
 .../server/balancing/targets/TargetMonitor.java    | 129 +++++++
 .../balancing/targets/TargetProbe.java}            |  28 +-
 .../core/server/impl/ActiveMQServerImpl.java       |  16 +
 .../core/server/management/ManagementService.java  |   9 +
 .../management/impl/ManagementServiceImpl.java     |  26 +-
 .../spi/core/protocol/AbstractProtocolManager.java |   3 +-
 .../artemis/spi/core/protocol/ProtocolManager.java |   5 +-
 .../resources/schema/artemis-configuration.xsd     | 175 +++++++++
 .../core/config/impl/FileConfigurationTest.java    |  37 ++
 .../policies/ConsistentHashPolicyTest.java         |  55 +++
 .../balancing/policies/FirstElementPolicyTest.java |  47 +++
 .../policies/LeastConnectionsPolicyTest.java       |  95 +++++
 .../server/balancing/policies/PolicyTestBase.java  |  52 +++
 .../balancing/policies/RoundRobinPolicyTest.java   |  58 +++
 .../server/balancing/pools/DiscoveryPoolTest.java  | 174 +++++++++
 .../balancing/pools/MockDiscoveryService.java      |  87 +++++
 .../core/server/balancing/pools/PoolTestBase.java  | 190 ++++++++++
 .../server/balancing/pools/StaticPoolTest.java     |  39 ++
 .../core/server/balancing/targets/MockTarget.java  | 156 ++++++++
 .../balancing/targets/MockTargetFactory.java       | 105 ++++++
 .../server/balancing/targets/MockTargetProbe.java  |  61 ++++
 .../balancing/targets/TargetKeyResolverTest.java   | 110 ++++++
 .../server/group/impl/ClusteredResetMockTest.java  |  21 ++
 .../resources/ConfigurationTest-full-config.xml    |  32 ++
 .../ConfigurationTest-xinclude-config.xml          |  32 ++
 docs/user-manual/en/SUMMARY.md                     |   1 +
 docs/user-manual/en/broker-balancers.md            | 191 ++++++++++
 .../en/images/broker_balancer_workflow.png         | Bin 0 -> 96089 bytes
 .../en/images/management_api_redirect_sequence.png | Bin 0 -> 12611 bytes
 .../en/images/native_redirect_sequence.png         | Bin 0 -> 11169 bytes
 .../broker-balancer/evenly-redirect/pom.xml        | 207 +++++++++++
 .../broker-balancer/evenly-redirect/readme.md      |   8 +
 .../artemis/jms/example/EvenlyRedirectExample.java | 106 ++++++
 .../src/main/resources/activemq/server0/broker.xml | 135 +++++++
 .../src/main/resources/activemq/server1/broker.xml | 113 ++++++
 .../src/main/resources/activemq/server2/broker.xml | 113 ++++++
 examples/features/broker-balancer/pom.xml          |  56 +++
 .../broker-balancer/symmetric-redirect/pom.xml     | 164 +++++++++
 .../broker-balancer/symmetric-redirect/readme.md   |   9 +
 .../jms/example/SymmetricRedirectExample.java      | 107 ++++++
 .../src/main/resources/activemq/server0/broker.xml | 150 ++++++++
 .../src/main/resources/activemq/server1/broker.xml | 150 ++++++++
 pom.xml                                            |   2 +-
 .../integration/balancing/BalancingTestBase.java   | 246 +++++++++++++
 .../integration/balancing/MQTTRedirectTest.java    | 125 +++++++
 .../tests/integration/balancing/RedirectTest.java  | 399 +++++++++++++++++++++
 .../tests/integration/balancing/TargetKeyTest.java | 184 ++++++++++
 .../integration/cluster/failover/FailoverTest.java |  49 ++-
 .../integration/cluster/util/BackupSyncDelay.java  |   5 +
 .../client/SessionMetadataAddExceptionTest.java    |   4 +-
 .../management/ActiveMQServerControlTest.java      |   6 +-
 .../management/BrokerBalancerControlTest.java      | 186 ++++++++++
 .../management/ManagementControlHelper.java        |   7 +
 tests/security-resources/build.sh                  |  18 +-
 tests/security-resources/client-ca-keystore.p12    | Bin 2589 -> 2589 bytes
 .../security-resources/client-ca-truststore.jceks  | Bin 950 -> 950 bytes
 tests/security-resources/client-ca-truststore.jks  | Bin 950 -> 950 bytes
 tests/security-resources/client-ca-truststore.p12  | Bin 1186 -> 1186 bytes
 tests/security-resources/client-ca.pem             |  54 +--
 tests/security-resources/client-keystore.jceks     | Bin 4124 -> 4124 bytes
 tests/security-resources/client-keystore.jks       | Bin 4144 -> 4143 bytes
 tests/security-resources/client-keystore.p12       | Bin 4759 -> 4759 bytes
 tests/security-resources/other-client-crl.pem      |  16 +-
 .../security-resources/other-client-keystore.jceks | Bin 4136 -> 4136 bytes
 tests/security-resources/other-client-keystore.jks | Bin 4156 -> 4155 bytes
 tests/security-resources/other-client-keystore.p12 | Bin 4787 -> 4787 bytes
 tests/security-resources/other-server-crl.pem      |  16 +-
 .../security-resources/other-server-keystore.jceks | Bin 4136 -> 4183 bytes
 tests/security-resources/other-server-keystore.jks | Bin 4155 -> 4202 bytes
 tests/security-resources/other-server-keystore.p12 | Bin 4787 -> 4835 bytes
 .../other-server-truststore.jceks                  | Bin 1053 -> 1100 bytes
 .../security-resources/other-server-truststore.jks | Bin 1053 -> 1100 bytes
 .../security-resources/other-server-truststore.p12 | Bin 1290 -> 1338 bytes
 tests/security-resources/server-ca-keystore.p12    | Bin 2589 -> 2589 bytes
 .../security-resources/server-ca-truststore.jceks  | Bin 950 -> 950 bytes
 tests/security-resources/server-ca-truststore.jks  | Bin 950 -> 950 bytes
 tests/security-resources/server-ca-truststore.p12  | Bin 1186 -> 1186 bytes
 tests/security-resources/server-ca.pem             |  54 +--
 tests/security-resources/server-keystore.jceks     | Bin 4103 -> 4150 bytes
 tests/security-resources/server-keystore.jks       | Bin 4122 -> 4169 bytes
 tests/security-resources/server-keystore.p12       | Bin 4735 -> 4783 bytes
 .../unknown-client-keystore.jceks                  | Bin 4112 -> 4112 bytes
 .../security-resources/unknown-client-keystore.jks | Bin 4132 -> 4131 bytes
 .../security-resources/unknown-client-keystore.p12 | Bin 4767 -> 4767 bytes
 .../unknown-server-keystore.jceks                  | Bin 4112 -> 4112 bytes
 .../security-resources/unknown-server-keystore.jks | Bin 4131 -> 4130 bytes
 .../security-resources/unknown-server-keystore.p12 | Bin 4767 -> 4767 bytes
 200 files changed, 8676 insertions(+), 640 deletions(-)
 copy artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java => artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQRedirectedException.java (55%)
 create mode 100644 artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/DisconnectReason.java
 copy artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java => artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/BrokerBalancerControl.java (55%)
 copy artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/{ClusterTopologyChangeMessage_V3.java => ClusterTopologyChangeMessage_V4.java} (53%)
 create mode 100644 artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CreateSessionMessage_V2.java
 create mode 100644 artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/DisconnectMessage_V3.java
 create mode 100644 artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPRedirectContext.java
 create mode 100644 artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPRedirectHandler.java
 copy artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java => artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTRedirectContext.java (51%)
 create mode 100644 artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTRedirectHandler.java
 copy artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java => artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireRedirectContext.java (50%)
 create mode 100644 artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireRedirectHandler.java
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/config/balancing/BrokerBalancerConfiguration.java
 copy artemis-server/src/main/java/org/apache/activemq/artemis/core/{remoting/impl/netty/NettyServerConnection.java => config/balancing/PolicyConfiguration.java} (52%)
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/config/balancing/PoolConfiguration.java
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/BrokerBalancerControlImpl.java
 copy artemis-server/src/main/java/org/apache/activemq/artemis/core/{remoting/impl/netty/NettyServerConnection.java => protocol/core/impl/ActiveMQRedirectContext.java} (51%)
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQRedirectHandler.java
 copy artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/{NettyServerConnection.java => NettySNIHostnameHandler.java} (52%)
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/BrokerBalancer.java
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/BrokerBalancerManager.java
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/RedirectContext.java
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/RedirectHandler.java
 copy artemis-server/src/main/java/org/apache/activemq/artemis/core/{remoting/impl/netty/NettyServerConnection.java => server/balancing/policies/AbstractPolicy.java} (50%)
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/ConsistentHashPolicy.java
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/DefaultPolicyFactory.java
 copy artemis-server/src/main/java/org/apache/activemq/artemis/core/{remoting/impl/netty/NettyServerConnection.java => server/balancing/policies/FirstElementPolicy.java} (51%)
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/LeastConnectionsPolicy.java
 copy artemis-server/src/main/java/org/apache/activemq/artemis/core/{remoting/impl/netty/NettyServerConnection.java => server/balancing/policies/Policy.java} (52%)
 copy artemis-server/src/main/java/org/apache/activemq/artemis/core/{remoting/impl/netty/NettyServerConnection.java => server/balancing/policies/PolicyFactory.java} (50%)
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/PolicyFactoryResolver.java
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/RoundRobinPolicy.java
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/AbstractPool.java
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/ClusterPool.java
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryGroupService.java
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryPool.java
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryService.java
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/Pool.java
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/StaticPool.java
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/AbstractTarget.java
 copy artemis-server/src/main/java/org/apache/activemq/artemis/core/{remoting/impl/netty/NettyServerConnection.java => server/balancing/targets/AbstractTargetFactory.java} (51%)
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/ActiveMQTarget.java
 copy artemis-server/src/main/java/org/apache/activemq/artemis/core/{remoting/impl/netty/NettyServerConnection.java => server/balancing/targets/ActiveMQTargetFactory.java} (51%)
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/LocalTarget.java
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/Target.java
 copy artemis-server/src/main/java/org/apache/activemq/artemis/core/{remoting/impl/netty/NettyServerConnection.java => server/balancing/targets/TargetFactory.java} (51%)
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetKey.java
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetKeyResolver.java
 copy artemis-server/src/main/java/org/apache/activemq/artemis/core/{remoting/impl/netty/NettyServerConnection.java => server/balancing/targets/TargetListener.java} (50%)
 create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetMonitor.java
 copy artemis-server/src/main/java/org/apache/activemq/artemis/core/{remoting/impl/netty/NettyServerConnection.java => server/balancing/targets/TargetProbe.java} (51%)
 create mode 100644 artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/ConsistentHashPolicyTest.java
 create mode 100644 artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/FirstElementPolicyTest.java
 create mode 100644 artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/LeastConnectionsPolicyTest.java
 create mode 100644 artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/PolicyTestBase.java
 create mode 100644 artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/RoundRobinPolicyTest.java
 create mode 100644 artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryPoolTest.java
 create mode 100644 artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/MockDiscoveryService.java
 create mode 100644 artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/PoolTestBase.java
 create mode 100644 artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/StaticPoolTest.java
 create mode 100644 artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/MockTarget.java
 create mode 100644 artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/MockTargetFactory.java
 create mode 100644 artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/MockTargetProbe.java
 create mode 100644 artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetKeyResolverTest.java
 create mode 100644 docs/user-manual/en/broker-balancers.md
 create mode 100644 docs/user-manual/en/images/broker_balancer_workflow.png
 create mode 100644 docs/user-manual/en/images/management_api_redirect_sequence.png
 create mode 100644 docs/user-manual/en/images/native_redirect_sequence.png
 create mode 100644 examples/features/broker-balancer/evenly-redirect/pom.xml
 create mode 100644 examples/features/broker-balancer/evenly-redirect/readme.md
 create mode 100644 examples/features/broker-balancer/evenly-redirect/src/main/java/org/apache/activemq/artemis/jms/example/EvenlyRedirectExample.java
 create mode 100644 examples/features/broker-balancer/evenly-redirect/src/main/resources/activemq/server0/broker.xml
 create mode 100644 examples/features/broker-balancer/evenly-redirect/src/main/resources/activemq/server1/broker.xml
 create mode 100644 examples/features/broker-balancer/evenly-redirect/src/main/resources/activemq/server2/broker.xml
 create mode 100644 examples/features/broker-balancer/pom.xml
 create mode 100644 examples/features/broker-balancer/symmetric-redirect/pom.xml
 create mode 100644 examples/features/broker-balancer/symmetric-redirect/readme.md
 create mode 100644 examples/features/broker-balancer/symmetric-redirect/src/main/java/org/apache/activemq/artemis/jms/example/SymmetricRedirectExample.java
 create mode 100644 examples/features/broker-balancer/symmetric-redirect/src/main/resources/activemq/server0/broker.xml
 create mode 100644 examples/features/broker-balancer/symmetric-redirect/src/main/resources/activemq/server1/broker.xml
 create mode 100644 tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/balancing/BalancingTestBase.java
 create mode 100644 tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/balancing/MQTTRedirectTest.java
 create mode 100644 tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/balancing/RedirectTest.java
 create mode 100644 tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/balancing/TargetKeyTest.java
 create mode 100644 tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/BrokerBalancerControlTest.java

[activemq-artemis] 03/03: This closes #3634

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/activemq-artemis.git

commit ce87777ead9b5ed67daf85c1e2fc67c44eb862e1
Merge: 49e3843 3555dd7
Author: Clebert Suconic <cl...@apache.org>
AuthorDate: Fri Aug 6 08:34:07 2021 -0400

    This closes #3634

 .../artemis/cli/commands/check/CheckAbstract.java  |   6 +-
 .../artemis/cli/commands/check/CheckContext.java   |   6 +-
 .../artemis/cli/commands/check/NodeCheck.java      |  27 +-
 .../artemis/cli/commands/check/QueueCheck.java     |   2 +-
 .../artemis/api/core/ActiveMQExceptionType.java    |   6 +
 .../api/core/ActiveMQRedirectedException.java      |  24 +-
 .../artemis/api/core/DisconnectReason.java         |  73 ++++
 .../artemis/api/core/TransportConfiguration.java   |   2 +-
 .../artemis/api/core/client/ClientSession.java     |   8 +
 .../api/core/client/ClientSessionFactory.java      |  26 ++
 .../core/management/ActiveMQManagementProxy.java   |  73 ++--
 .../api/core/management/BrokerBalancerControl.java |  25 +-
 .../api/core/management/ObjectNameBuilder.java     |   9 +
 .../artemis/api/core/management/ResourceNames.java |   2 +
 .../core/client/ActiveMQClientMessageBundle.java   |   4 +
 .../core/client/impl/ClientSessionFactoryImpl.java | 248 +++++++------
 .../core/client/impl/ClientSessionImpl.java        |  50 ++-
 .../core/client/impl/ClientSessionInternal.java    |   2 +
 .../artemis/core/protocol/core/Channel.java        |   7 +
 .../core/protocol/core/CoreRemotingConnection.java |  10 +
 .../core/impl/ActiveMQClientProtocolManager.java   |  63 ++--
 .../protocol/core/impl/ActiveMQSessionContext.java |   2 +-
 .../core/protocol/core/impl/ChannelImpl.java       |   9 +
 .../core/protocol/core/impl/PacketDecoder.java     |  18 +
 .../core/protocol/core/impl/PacketImpl.java        |   7 +
 .../protocol/core/impl/RemotingConnectionImpl.java |  23 +-
 .../ClusterTopologyChangeMessage_V3.java           |  29 +-
 ...3.java => ClusterTopologyChangeMessage_V4.java} |  68 ++--
 .../core/impl/wireformat/CreateSessionMessage.java |  37 +-
 .../impl/wireformat/CreateSessionMessage_V2.java   | 103 ++++++
 .../core/impl/wireformat/DisconnectMessage_V3.java | 137 +++++++
 .../remoting/impl/netty/TransportConstants.java    |   5 +
 .../spi/core/protocol/RemotingConnection.java      |   8 +
 .../spi/core/remoting/ClientProtocolManager.java   |   3 +-
 .../artemis/spi/core/remoting/Connection.java      |   8 +
 .../spi/core/remoting/TopologyResponseHandler.java |   3 +-
 .../src/main/resources/activemq-version.properties |   2 +-
 .../core/management/OperationAnnotationTest.java   |   3 +-
 .../artemis/jms/client/ActiveMQConnection.java     |  30 +-
 .../amqp/broker/ProtonProtocolManager.java         |  10 +-
 .../amqp/client/ProtonClientProtocolManager.java   |   2 +-
 .../amqp/connect/AMQPBrokerConnectionManager.java  |   3 +-
 .../amqp/proton/AMQPConnectionContext.java         |   4 +-
 .../protocol/amqp/proton/AMQPRedirectContext.java  |  37 ++
 .../protocol/amqp/proton/AMQPRedirectHandler.java  |  64 ++++
 .../amqp/sasl/AnonymousServerSASLFactory.java      |   3 +-
 .../amqp/sasl/ExternalServerSASLFactory.java       |   3 +-
 .../amqp/sasl/GSSAPIServerSASLFactory.java         |   3 +-
 .../protocol/amqp/sasl/PlainServerSASLFactory.java |   3 +-
 .../protocol/amqp/sasl/ServerSASLFactory.java      |   3 +-
 .../amqp/sasl/scram/SCRAMServerSASLFactory.java    |   3 +-
 .../client/HornetQClientProtocolManager.java       |   9 +-
 .../core/protocol/mqtt/MQTTProtocolHandler.java    |  16 +-
 .../core/protocol/mqtt/MQTTProtocolManager.java    |  10 +-
 .../core/protocol/mqtt/MQTTRedirectContext.java    |  33 +-
 .../core/protocol/mqtt/MQTTRedirectHandler.java    |  55 +++
 .../core/protocol/openwire/OpenWireConnection.java |   6 +
 .../protocol/openwire/OpenWireProtocolManager.java |  10 +-
 .../protocol/openwire/OpenWireRedirectContext.java |  33 +-
 .../protocol/openwire/OpenWireRedirectHandler.java |  58 +++
 .../core/protocol/stomp/StompProtocolManager.java  |   8 +-
 .../artemis/ra/inflow/ActiveMQActivation.java      |   3 +-
 .../artemis/core/config/Configuration.java         |  13 +
 .../balancing/BrokerBalancerConfiguration.java     |  95 +++++
 .../balancing/PolicyConfiguration.java}            |  39 +-
 .../core/config/balancing/PoolConfiguration.java   | 123 +++++++
 .../core/config/impl/ConfigurationImpl.java        |  20 ++
 .../artemis/core/config/impl/Validators.java       |  11 +
 .../deployers/impl/FileConfigurationParser.java    | 102 +++++-
 .../management/impl/BrokerBalancerControlImpl.java | 160 +++++++++
 .../core/management/impl/view/ConsumerView.java    |   2 +-
 .../core/management/impl/view/ProducerView.java    |   2 +-
 .../artemis/core/protocol/ProtocolHandler.java     |   5 +
 .../protocol/core/impl/ActiveMQPacketHandler.java  |  14 +-
 .../core/impl/ActiveMQRedirectContext.java}        |  28 +-
 .../core/impl/ActiveMQRedirectHandler.java         |  52 +++
 .../protocol/core/impl/CoreProtocolManager.java    |  25 +-
 .../core/remoting/impl/netty/NettyAcceptor.java    |   7 +-
 ...onnection.java => NettySNIHostnameHandler.java} |  36 +-
 .../remoting/impl/netty/NettyServerConnection.java |  22 +-
 .../artemis/core/server/ActiveMQMessageBundle.java |  11 +
 .../artemis/core/server/ActiveMQServer.java        |   3 +
 .../artemis/core/server/ActiveMQServerLogger.java  |  14 +
 .../core/server/balancing/BrokerBalancer.java      | 193 ++++++++++
 .../server/balancing/BrokerBalancerManager.java    | 191 ++++++++++
 .../core/server/balancing/RedirectContext.java     |  57 +++
 .../core/server/balancing/RedirectHandler.java     |  74 ++++
 .../balancing/policies/AbstractPolicy.java}        |  46 ++-
 .../balancing/policies/ConsistentHashPolicy.java   |  75 ++++
 .../balancing/policies/DefaultPolicyFactory.java   |  49 +++
 .../balancing/policies/FirstElementPolicy.java}    |  39 +-
 .../balancing/policies/LeastConnectionsPolicy.java | 133 +++++++
 .../balancing/policies/Policy.java}                |  32 +-
 .../balancing/policies/PolicyFactory.java}         |  26 +-
 .../balancing/policies/PolicyFactoryResolver.java  |  74 ++++
 .../balancing/policies/RoundRobinPolicy.java       |  47 +++
 .../core/server/balancing/pools/AbstractPool.java  | 243 +++++++++++++
 .../core/server/balancing/pools/ClusterPool.java   |  69 ++++
 .../balancing/pools/DiscoveryGroupService.java     |  87 +++++
 .../core/server/balancing/pools/DiscoveryPool.java |  70 ++++
 .../server/balancing/pools/DiscoveryService.java   |  88 +++++
 .../artemis/core/server/balancing/pools/Pool.java  |  65 ++++
 .../core/server/balancing/pools/StaticPool.java    |  44 +++
 .../server/balancing/targets/AbstractTarget.java   | 112 ++++++
 .../balancing/targets/AbstractTargetFactory.java}  |  41 ++-
 .../server/balancing/targets/ActiveMQTarget.java   | 132 +++++++
 .../balancing/targets/ActiveMQTargetFactory.java}  |  30 +-
 .../core/server/balancing/targets/LocalTarget.java |  69 ++++
 .../core/server/balancing/targets/Target.java      |  59 +++
 .../balancing/targets/TargetFactory.java}          |  30 +-
 .../core/server/balancing/targets/TargetKey.java   |  53 +++
 .../balancing/targets/TargetKeyResolver.java       | 108 ++++++
 .../balancing/targets/TargetListener.java}         |  26 +-
 .../server/balancing/targets/TargetMonitor.java    | 129 +++++++
 .../balancing/targets/TargetProbe.java}            |  28 +-
 .../core/server/impl/ActiveMQServerImpl.java       |  16 +
 .../core/server/management/ManagementService.java  |   9 +
 .../management/impl/ManagementServiceImpl.java     |  26 +-
 .../spi/core/protocol/AbstractProtocolManager.java |   3 +-
 .../artemis/spi/core/protocol/ProtocolManager.java |   5 +-
 .../resources/schema/artemis-configuration.xsd     | 175 +++++++++
 .../core/config/impl/FileConfigurationTest.java    |  37 ++
 .../policies/ConsistentHashPolicyTest.java         |  55 +++
 .../balancing/policies/FirstElementPolicyTest.java |  47 +++
 .../policies/LeastConnectionsPolicyTest.java       |  95 +++++
 .../server/balancing/policies/PolicyTestBase.java  |  52 +++
 .../balancing/policies/RoundRobinPolicyTest.java   |  58 +++
 .../server/balancing/pools/DiscoveryPoolTest.java  | 174 +++++++++
 .../balancing/pools/MockDiscoveryService.java      |  87 +++++
 .../core/server/balancing/pools/PoolTestBase.java  | 190 ++++++++++
 .../server/balancing/pools/StaticPoolTest.java     |  39 ++
 .../core/server/balancing/targets/MockTarget.java  | 156 ++++++++
 .../balancing/targets/MockTargetFactory.java       | 105 ++++++
 .../server/balancing/targets/MockTargetProbe.java  |  61 ++++
 .../balancing/targets/TargetKeyResolverTest.java   | 110 ++++++
 .../server/group/impl/ClusteredResetMockTest.java  |  21 ++
 .../resources/ConfigurationTest-full-config.xml    |  32 ++
 .../ConfigurationTest-xinclude-config.xml          |  32 ++
 docs/user-manual/en/SUMMARY.md                     |   1 +
 docs/user-manual/en/broker-balancers.md            | 191 ++++++++++
 .../en/images/broker_balancer_workflow.png         | Bin 0 -> 96089 bytes
 .../en/images/management_api_redirect_sequence.png | Bin 0 -> 12611 bytes
 .../en/images/native_redirect_sequence.png         | Bin 0 -> 11169 bytes
 .../broker-balancer/evenly-redirect/pom.xml        | 207 +++++++++++
 .../broker-balancer/evenly-redirect/readme.md      |   8 +
 .../artemis/jms/example/EvenlyRedirectExample.java | 106 ++++++
 .../src/main/resources/activemq/server0/broker.xml | 135 +++++++
 .../src/main/resources/activemq/server1/broker.xml | 113 ++++++
 .../src/main/resources/activemq/server2/broker.xml | 113 ++++++
 examples/features/broker-balancer/pom.xml          |  56 +++
 .../broker-balancer/symmetric-redirect/pom.xml     | 164 +++++++++
 .../broker-balancer/symmetric-redirect/readme.md   |   9 +
 .../jms/example/SymmetricRedirectExample.java      | 107 ++++++
 .../src/main/resources/activemq/server0/broker.xml | 150 ++++++++
 .../src/main/resources/activemq/server1/broker.xml | 150 ++++++++
 pom.xml                                            |   2 +-
 .../integration/balancing/BalancingTestBase.java   | 246 +++++++++++++
 .../integration/balancing/MQTTRedirectTest.java    | 125 +++++++
 .../tests/integration/balancing/RedirectTest.java  | 399 +++++++++++++++++++++
 .../tests/integration/balancing/TargetKeyTest.java | 184 ++++++++++
 .../integration/cluster/failover/FailoverTest.java |  49 ++-
 .../integration/cluster/util/BackupSyncDelay.java  |   5 +
 .../client/SessionMetadataAddExceptionTest.java    |   4 +-
 .../management/ActiveMQServerControlTest.java      |   6 +-
 .../management/BrokerBalancerControlTest.java      | 186 ++++++++++
 .../management/ManagementControlHelper.java        |   7 +
 tests/security-resources/build.sh                  |  18 +-
 tests/security-resources/client-ca-keystore.p12    | Bin 2589 -> 2589 bytes
 .../security-resources/client-ca-truststore.jceks  | Bin 950 -> 950 bytes
 tests/security-resources/client-ca-truststore.jks  | Bin 950 -> 950 bytes
 tests/security-resources/client-ca-truststore.p12  | Bin 1186 -> 1186 bytes
 tests/security-resources/client-ca.pem             |  54 +--
 tests/security-resources/client-keystore.jceks     | Bin 4124 -> 4124 bytes
 tests/security-resources/client-keystore.jks       | Bin 4144 -> 4143 bytes
 tests/security-resources/client-keystore.p12       | Bin 4759 -> 4759 bytes
 tests/security-resources/other-client-crl.pem      |  16 +-
 .../security-resources/other-client-keystore.jceks | Bin 4136 -> 4136 bytes
 tests/security-resources/other-client-keystore.jks | Bin 4156 -> 4155 bytes
 tests/security-resources/other-client-keystore.p12 | Bin 4787 -> 4787 bytes
 tests/security-resources/other-server-crl.pem      |  16 +-
 .../security-resources/other-server-keystore.jceks | Bin 4136 -> 4183 bytes
 tests/security-resources/other-server-keystore.jks | Bin 4155 -> 4202 bytes
 tests/security-resources/other-server-keystore.p12 | Bin 4787 -> 4835 bytes
 .../other-server-truststore.jceks                  | Bin 1053 -> 1100 bytes
 .../security-resources/other-server-truststore.jks | Bin 1053 -> 1100 bytes
 .../security-resources/other-server-truststore.p12 | Bin 1290 -> 1338 bytes
 tests/security-resources/server-ca-keystore.p12    | Bin 2589 -> 2589 bytes
 .../security-resources/server-ca-truststore.jceks  | Bin 950 -> 950 bytes
 tests/security-resources/server-ca-truststore.jks  | Bin 950 -> 950 bytes
 tests/security-resources/server-ca-truststore.p12  | Bin 1186 -> 1186 bytes
 tests/security-resources/server-ca.pem             |  54 +--
 tests/security-resources/server-keystore.jceks     | Bin 4103 -> 4150 bytes
 tests/security-resources/server-keystore.jks       | Bin 4122 -> 4169 bytes
 tests/security-resources/server-keystore.p12       | Bin 4735 -> 4783 bytes
 .../unknown-client-keystore.jceks                  | Bin 4112 -> 4112 bytes
 .../security-resources/unknown-client-keystore.jks | Bin 4132 -> 4131 bytes
 .../security-resources/unknown-client-keystore.p12 | Bin 4767 -> 4767 bytes
 .../unknown-server-keystore.jceks                  | Bin 4112 -> 4112 bytes
 .../security-resources/unknown-server-keystore.jks | Bin 4131 -> 4130 bytes
 .../security-resources/unknown-server-keystore.p12 | Bin 4767 -> 4767 bytes
 200 files changed, 8676 insertions(+), 640 deletions(-)

[activemq-artemis] 02/03: ARTEMIS-3365 Add broker balancers

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/activemq-artemis.git

commit 3555dd7d25492f0adea7795fc2cbf4047d520981
Author: Domenico Francesco Bruscino <br...@apache.org>
AuthorDate: Fri Aug 6 00:49:11 2021 +0200

    ARTEMIS-3365 Add broker balancers
---
 .../artemis/cli/commands/check/CheckAbstract.java  |   6 +-
 .../artemis/cli/commands/check/CheckContext.java   |   6 +-
 .../artemis/cli/commands/check/NodeCheck.java      |  27 +-
 .../artemis/cli/commands/check/QueueCheck.java     |   2 +-
 .../artemis/api/core/ActiveMQExceptionType.java    |   6 +
 .../api/core/ActiveMQRedirectedException.java      |  24 +-
 .../artemis/api/core/DisconnectReason.java         |  73 ++++
 .../artemis/api/core/TransportConfiguration.java   |   2 +-
 .../artemis/api/core/client/ClientSession.java     |   8 +
 .../api/core/client/ClientSessionFactory.java      |  26 ++
 .../core/management/ActiveMQManagementProxy.java   |  73 ++--
 .../api/core/management/BrokerBalancerControl.java |  25 +-
 .../api/core/management/ObjectNameBuilder.java     |   9 +
 .../artemis/api/core/management/ResourceNames.java |   2 +
 .../core/client/ActiveMQClientMessageBundle.java   |   4 +
 .../core/client/impl/ClientSessionFactoryImpl.java | 210 ++++++-----
 .../core/client/impl/ClientSessionImpl.java        |   4 +
 .../core/protocol/core/CoreRemotingConnection.java |  10 +
 .../core/impl/ActiveMQClientProtocolManager.java   |  63 ++--
 .../core/protocol/core/impl/ChannelImpl.java       |   4 +
 .../core/protocol/core/impl/PacketDecoder.java     |  18 +
 .../core/protocol/core/impl/PacketImpl.java        |   7 +
 .../protocol/core/impl/RemotingConnectionImpl.java |  23 +-
 .../ClusterTopologyChangeMessage_V3.java           |  29 +-
 ...3.java => ClusterTopologyChangeMessage_V4.java} |  68 ++--
 .../core/impl/wireformat/CreateSessionMessage.java |  37 +-
 .../impl/wireformat/CreateSessionMessage_V2.java   | 103 ++++++
 .../core/impl/wireformat/DisconnectMessage_V3.java | 137 +++++++
 .../remoting/impl/netty/TransportConstants.java    |   5 +
 .../spi/core/protocol/RemotingConnection.java      |   8 +
 .../spi/core/remoting/ClientProtocolManager.java   |   3 +-
 .../artemis/spi/core/remoting/Connection.java      |   8 +
 .../spi/core/remoting/TopologyResponseHandler.java |   3 +-
 .../src/main/resources/activemq-version.properties |   2 +-
 .../core/management/OperationAnnotationTest.java   |   3 +-
 .../artemis/jms/client/ActiveMQConnection.java     |  30 +-
 .../amqp/broker/ProtonProtocolManager.java         |  10 +-
 .../amqp/client/ProtonClientProtocolManager.java   |   2 +-
 .../amqp/connect/AMQPBrokerConnectionManager.java  |   3 +-
 .../amqp/proton/AMQPConnectionContext.java         |   4 +-
 .../protocol/amqp/proton/AMQPRedirectContext.java  |  37 ++
 .../protocol/amqp/proton/AMQPRedirectHandler.java  |  64 ++++
 .../amqp/sasl/AnonymousServerSASLFactory.java      |   3 +-
 .../amqp/sasl/ExternalServerSASLFactory.java       |   3 +-
 .../amqp/sasl/GSSAPIServerSASLFactory.java         |   3 +-
 .../protocol/amqp/sasl/PlainServerSASLFactory.java |   3 +-
 .../protocol/amqp/sasl/ServerSASLFactory.java      |   3 +-
 .../amqp/sasl/scram/SCRAMServerSASLFactory.java    |   3 +-
 .../client/HornetQClientProtocolManager.java       |   9 +-
 .../core/protocol/mqtt/MQTTProtocolHandler.java    |  16 +-
 .../core/protocol/mqtt/MQTTProtocolManager.java    |  10 +-
 .../core/protocol/mqtt/MQTTRedirectContext.java    |  33 +-
 .../core/protocol/mqtt/MQTTRedirectHandler.java    |  55 +++
 .../core/protocol/openwire/OpenWireConnection.java |   6 +
 .../protocol/openwire/OpenWireProtocolManager.java |  10 +-
 .../protocol/openwire/OpenWireRedirectContext.java |  33 +-
 .../protocol/openwire/OpenWireRedirectHandler.java |  58 +++
 .../core/protocol/stomp/StompProtocolManager.java  |   8 +-
 .../artemis/ra/inflow/ActiveMQActivation.java      |   3 +-
 .../artemis/core/config/Configuration.java         |  13 +
 .../balancing/BrokerBalancerConfiguration.java     |  95 +++++
 .../balancing/PolicyConfiguration.java}            |  39 +-
 .../core/config/balancing/PoolConfiguration.java   | 123 +++++++
 .../core/config/impl/ConfigurationImpl.java        |  20 ++
 .../artemis/core/config/impl/Validators.java       |  11 +
 .../deployers/impl/FileConfigurationParser.java    | 102 +++++-
 .../management/impl/BrokerBalancerControlImpl.java | 160 +++++++++
 .../core/management/impl/view/ConsumerView.java    |   2 +-
 .../core/management/impl/view/ProducerView.java    |   2 +-
 .../artemis/core/protocol/ProtocolHandler.java     |   5 +
 .../protocol/core/impl/ActiveMQPacketHandler.java  |  14 +-
 .../core/impl/ActiveMQRedirectContext.java}        |  28 +-
 .../core/impl/ActiveMQRedirectHandler.java         |  52 +++
 .../protocol/core/impl/CoreProtocolManager.java    |  25 +-
 .../core/remoting/impl/netty/NettyAcceptor.java    |   7 +-
 ...onnection.java => NettySNIHostnameHandler.java} |  36 +-
 .../remoting/impl/netty/NettyServerConnection.java |  22 +-
 .../artemis/core/server/ActiveMQMessageBundle.java |  11 +
 .../artemis/core/server/ActiveMQServer.java        |   3 +
 .../artemis/core/server/ActiveMQServerLogger.java  |  14 +
 .../core/server/balancing/BrokerBalancer.java      | 193 ++++++++++
 .../server/balancing/BrokerBalancerManager.java    | 191 ++++++++++
 .../core/server/balancing/RedirectContext.java     |  57 +++
 .../core/server/balancing/RedirectHandler.java     |  74 ++++
 .../balancing/policies/AbstractPolicy.java}        |  46 ++-
 .../balancing/policies/ConsistentHashPolicy.java   |  75 ++++
 .../balancing/policies/DefaultPolicyFactory.java   |  49 +++
 .../balancing/policies/FirstElementPolicy.java}    |  39 +-
 .../balancing/policies/LeastConnectionsPolicy.java | 133 +++++++
 .../balancing/policies/Policy.java}                |  32 +-
 .../balancing/policies/PolicyFactory.java}         |  26 +-
 .../balancing/policies/PolicyFactoryResolver.java  |  74 ++++
 .../balancing/policies/RoundRobinPolicy.java       |  47 +++
 .../core/server/balancing/pools/AbstractPool.java  | 243 +++++++++++++
 .../core/server/balancing/pools/ClusterPool.java   |  69 ++++
 .../balancing/pools/DiscoveryGroupService.java     |  87 +++++
 .../core/server/balancing/pools/DiscoveryPool.java |  70 ++++
 .../server/balancing/pools/DiscoveryService.java   |  88 +++++
 .../artemis/core/server/balancing/pools/Pool.java  |  65 ++++
 .../core/server/balancing/pools/StaticPool.java    |  44 +++
 .../server/balancing/targets/AbstractTarget.java   | 112 ++++++
 .../balancing/targets/AbstractTargetFactory.java}  |  41 ++-
 .../server/balancing/targets/ActiveMQTarget.java   | 132 +++++++
 .../balancing/targets/ActiveMQTargetFactory.java}  |  30 +-
 .../core/server/balancing/targets/LocalTarget.java |  69 ++++
 .../core/server/balancing/targets/Target.java      |  59 +++
 .../balancing/targets/TargetFactory.java}          |  30 +-
 .../core/server/balancing/targets/TargetKey.java   |  53 +++
 .../balancing/targets/TargetKeyResolver.java       | 108 ++++++
 .../balancing/targets/TargetListener.java}         |  26 +-
 .../server/balancing/targets/TargetMonitor.java    | 129 +++++++
 .../balancing/targets/TargetProbe.java}            |  28 +-
 .../core/server/impl/ActiveMQServerImpl.java       |  16 +
 .../core/server/management/ManagementService.java  |   9 +
 .../management/impl/ManagementServiceImpl.java     |  26 +-
 .../spi/core/protocol/AbstractProtocolManager.java |   3 +-
 .../artemis/spi/core/protocol/ProtocolManager.java |   5 +-
 .../resources/schema/artemis-configuration.xsd     | 175 +++++++++
 .../core/config/impl/FileConfigurationTest.java    |  37 ++
 .../policies/ConsistentHashPolicyTest.java         |  55 +++
 .../balancing/policies/FirstElementPolicyTest.java |  47 +++
 .../policies/LeastConnectionsPolicyTest.java       |  95 +++++
 .../server/balancing/policies/PolicyTestBase.java  |  52 +++
 .../balancing/policies/RoundRobinPolicyTest.java   |  58 +++
 .../server/balancing/pools/DiscoveryPoolTest.java  | 174 +++++++++
 .../balancing/pools/MockDiscoveryService.java      |  87 +++++
 .../core/server/balancing/pools/PoolTestBase.java  | 190 ++++++++++
 .../server/balancing/pools/StaticPoolTest.java     |  39 ++
 .../core/server/balancing/targets/MockTarget.java  | 156 ++++++++
 .../balancing/targets/MockTargetFactory.java       | 105 ++++++
 .../server/balancing/targets/MockTargetProbe.java  |  61 ++++
 .../balancing/targets/TargetKeyResolverTest.java   | 110 ++++++
 .../server/group/impl/ClusteredResetMockTest.java  |  21 ++
 .../resources/ConfigurationTest-full-config.xml    |  32 ++
 .../ConfigurationTest-xinclude-config.xml          |  32 ++
 docs/user-manual/en/SUMMARY.md                     |   1 +
 docs/user-manual/en/broker-balancers.md            | 191 ++++++++++
 .../en/images/broker_balancer_workflow.png         | Bin 0 -> 96089 bytes
 .../en/images/management_api_redirect_sequence.png | Bin 0 -> 12611 bytes
 .../en/images/native_redirect_sequence.png         | Bin 0 -> 11169 bytes
 .../broker-balancer/evenly-redirect/pom.xml        | 207 +++++++++++
 .../broker-balancer/evenly-redirect/readme.md      |   8 +
 .../artemis/jms/example/EvenlyRedirectExample.java | 106 ++++++
 .../src/main/resources/activemq/server0/broker.xml | 135 +++++++
 .../src/main/resources/activemq/server1/broker.xml | 113 ++++++
 .../src/main/resources/activemq/server2/broker.xml | 113 ++++++
 examples/features/broker-balancer/pom.xml          |  56 +++
 .../broker-balancer/symmetric-redirect/pom.xml     | 164 +++++++++
 .../broker-balancer/symmetric-redirect/readme.md   |   9 +
 .../jms/example/SymmetricRedirectExample.java      | 107 ++++++
 .../src/main/resources/activemq/server0/broker.xml | 150 ++++++++
 .../src/main/resources/activemq/server1/broker.xml | 150 ++++++++
 pom.xml                                            |   2 +-
 .../integration/balancing/BalancingTestBase.java   | 246 +++++++++++++
 .../integration/balancing/MQTTRedirectTest.java    | 125 +++++++
 .../tests/integration/balancing/RedirectTest.java  | 399 +++++++++++++++++++++
 .../tests/integration/balancing/TargetKeyTest.java | 184 ++++++++++
 .../integration/cluster/failover/FailoverTest.java |   8 +-
 .../client/SessionMetadataAddExceptionTest.java    |   4 +-
 .../management/ActiveMQServerControlTest.java      |   6 +-
 .../management/BrokerBalancerControlTest.java      | 186 ++++++++++
 .../management/ManagementControlHelper.java        |   7 +
 tests/security-resources/build.sh                  |  18 +-
 tests/security-resources/client-ca-keystore.p12    | Bin 2589 -> 2589 bytes
 .../security-resources/client-ca-truststore.jceks  | Bin 950 -> 950 bytes
 tests/security-resources/client-ca-truststore.jks  | Bin 950 -> 950 bytes
 tests/security-resources/client-ca-truststore.p12  | Bin 1186 -> 1186 bytes
 tests/security-resources/client-ca.pem             |  54 +--
 tests/security-resources/client-keystore.jceks     | Bin 4124 -> 4124 bytes
 tests/security-resources/client-keystore.jks       | Bin 4144 -> 4143 bytes
 tests/security-resources/client-keystore.p12       | Bin 4759 -> 4759 bytes
 tests/security-resources/other-client-crl.pem      |  16 +-
 .../security-resources/other-client-keystore.jceks | Bin 4136 -> 4136 bytes
 tests/security-resources/other-client-keystore.jks | Bin 4156 -> 4155 bytes
 tests/security-resources/other-client-keystore.p12 | Bin 4787 -> 4787 bytes
 tests/security-resources/other-server-crl.pem      |  16 +-
 .../security-resources/other-server-keystore.jceks | Bin 4136 -> 4183 bytes
 tests/security-resources/other-server-keystore.jks | Bin 4155 -> 4202 bytes
 tests/security-resources/other-server-keystore.p12 | Bin 4787 -> 4835 bytes
 .../other-server-truststore.jceks                  | Bin 1053 -> 1100 bytes
 .../security-resources/other-server-truststore.jks | Bin 1053 -> 1100 bytes
 .../security-resources/other-server-truststore.p12 | Bin 1290 -> 1338 bytes
 tests/security-resources/server-ca-keystore.p12    | Bin 2589 -> 2589 bytes
 .../security-resources/server-ca-truststore.jceks  | Bin 950 -> 950 bytes
 tests/security-resources/server-ca-truststore.jks  | Bin 950 -> 950 bytes
 tests/security-resources/server-ca-truststore.p12  | Bin 1186 -> 1186 bytes
 tests/security-resources/server-ca.pem             |  54 +--
 tests/security-resources/server-keystore.jceks     | Bin 4103 -> 4150 bytes
 tests/security-resources/server-keystore.jks       | Bin 4122 -> 4169 bytes
 tests/security-resources/server-keystore.p12       | Bin 4735 -> 4783 bytes
 .../unknown-client-keystore.jceks                  | Bin 4112 -> 4112 bytes
 .../security-resources/unknown-client-keystore.jks | Bin 4132 -> 4131 bytes
 .../security-resources/unknown-client-keystore.p12 | Bin 4767 -> 4767 bytes
 .../unknown-server-keystore.jceks                  | Bin 4112 -> 4112 bytes
 .../security-resources/unknown-server-keystore.jks | Bin 4131 -> 4130 bytes
 .../security-resources/unknown-server-keystore.p12 | Bin 4767 -> 4767 bytes
 196 files changed, 8562 insertions(+), 608 deletions(-)

diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/CheckAbstract.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/CheckAbstract.java
index ff10575..3d7019b 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/CheckAbstract.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/CheckAbstract.java
@@ -25,7 +25,6 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
 import io.airlift.airline.Option;
-import org.apache.activemq.artemis.api.core.client.ServerLocator;
 import org.apache.activemq.artemis.api.core.management.ActiveMQManagementProxy;
 import org.apache.activemq.artemis.cli.CLIException;
 import org.apache.activemq.artemis.cli.commands.AbstractAction;
@@ -72,10 +71,7 @@ public abstract class CheckAbstract extends AbstractAction {
          int successTasks = 0;
 
          try (ActiveMQConnectionFactory factory = createCoreConnectionFactory();
-              ServerLocator serverLocator = factory.getServerLocator();
-              ActiveMQManagementProxy managementProxy = new ActiveMQManagementProxy(serverLocator, user, password)) {
-
-            managementProxy.start();
+              ActiveMQManagementProxy managementProxy = new ActiveMQManagementProxy(factory.getServerLocator(), user, password)) {
 
             StopWatch watch = new StopWatch();
             CheckTask[] checkTasks = getCheckTasks();
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/CheckContext.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/CheckContext.java
index d1e8ca5..617b603 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/CheckContext.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/CheckContext.java
@@ -61,7 +61,7 @@ public class CheckContext extends ActionContext {
 
    public String getNodeId() throws Exception {
       if (nodeId == null) {
-         nodeId = managementProxy.invokeOperation(String.class, "broker", "getNodeID");
+         nodeId = managementProxy.getAttribute("broker", "NodeID", String.class, 0);
       }
 
       return nodeId;
@@ -69,8 +69,8 @@ public class CheckContext extends ActionContext {
 
    public Map<String, NodeInfo> getTopology() throws Exception {
       if (topology == null) {
-         topology = Arrays.stream(NodeInfo.from(managementProxy.invokeOperation(
-            String.class, "broker", "listNetworkTopology"))).
+         topology = Arrays.stream(NodeInfo.from((String)managementProxy.
+            invokeOperation("broker", "listNetworkTopology", null, null, 0))).
             collect(Collectors.toMap(node -> node.getId(), node -> node));
       }
 
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/NodeCheck.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/NodeCheck.java
index 601d254..3897a15 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/NodeCheck.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/NodeCheck.java
@@ -138,7 +138,7 @@ public class NodeCheck extends CheckAbstract {
    }
 
    private void checkNodeUp(final CheckContext context) throws Exception {
-      if (!context.getManagementProxy().invokeOperation(Boolean.class, "broker", "isStarted")) {
+      if (!context.getManagementProxy().getAttribute("broker", "Started", Boolean.class, 0)) {
          throw new CheckException("The node isn't started.");
       }
    }
@@ -182,28 +182,31 @@ public class NodeCheck extends CheckAbstract {
    }
 
    private void checkNodeDiskUsage(final CheckContext context) throws Exception {
-      int thresholdValue;
+      Integer maxDiskUsage;
 
       if (diskUsage == -1) {
-         thresholdValue = context.getManagementProxy().invokeOperation(
-            int.class, "broker", "getMaxDiskUsage");
+         maxDiskUsage = context.getManagementProxy().
+            getAttribute("broker", "MaxDiskUsage", Integer.class, 0);
       } else {
-         thresholdValue = diskUsage;
+         maxDiskUsage = diskUsage;
       }
 
-      checkNodeUsage(context, "getDiskStoreUsage", thresholdValue);
+      Double diskStoreUsage = context.getManagementProxy().
+         getAttribute("broker", "DiskStoreUsage", Double.class, 0);
+
+      checkNodeResourceUsage("DiskStoreUsage", (int)(diskStoreUsage *  100), maxDiskUsage);
    }
 
    private void checkNodeMemoryUsage(final CheckContext context) throws Exception {
-      checkNodeUsage(context, "getAddressMemoryUsagePercentage", memoryUsage);
-   }
+      int addressMemoryUsagePercentage = context.getManagementProxy().
+         getAttribute("broker", "AddressMemoryUsagePercentage", Integer.class, 0);
 
-   private void checkNodeUsage(final CheckContext context, final String name, final int thresholdValue) throws Exception {
-      int usageValue = context.getManagementProxy().invokeOperation(int.class, "broker", name);
+      checkNodeResourceUsage("MemoryUsage", addressMemoryUsagePercentage, memoryUsage);
+   }
 
+   private void checkNodeResourceUsage(final String resourceName, final int usageValue, final int thresholdValue) throws Exception {
       if (usageValue > thresholdValue) {
-         throw new CheckException("The " + (name.startsWith("get") ? name.substring(3) : name) +
-                                     " " + usageValue + " is less than " + thresholdValue);
+         throw new CheckException("The " + resourceName + " " + usageValue + " is less than " + thresholdValue);
       }
    }
 }
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/QueueCheck.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/QueueCheck.java
index 5bd4fd9..ed9a162 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/QueueCheck.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/check/QueueCheck.java
@@ -132,7 +132,7 @@ public class QueueCheck extends CheckAbstract {
    }
 
    private void checkQueueUp(final CheckContext context) throws Exception {
-      if (context.getManagementProxy().invokeOperation(Boolean.class,ResourceNames.QUEUE + getName(), "isPaused")) {
+      if (context.getManagementProxy().getAttribute(ResourceNames.QUEUE + getName(), "Paused", Boolean.class, 0)) {
          throw new CheckException("The queue is paused.");
       }
    }
diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQExceptionType.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQExceptionType.java
index 8bb51c3..2c6e585 100644
--- a/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQExceptionType.java
+++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQExceptionType.java
@@ -267,6 +267,12 @@ public enum ActiveMQExceptionType {
       public ActiveMQException createException(String msg) {
          return new ActiveMQDivertDoesNotExistException(msg);
       }
+   },
+   REDIRECTED(222) {
+      @Override
+      public ActiveMQException createException(String msg) {
+         return new ActiveMQRedirectedException(msg);
+      }
    };
    private static final Map<Integer, ActiveMQExceptionType> TYPE_MAP;
 
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQRedirectedException.java
similarity index 55%
copy from artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
copy to artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQRedirectedException.java
index f9e1b3d..ed52b04 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
+++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/ActiveMQRedirectedException.java
@@ -14,21 +14,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.activemq.artemis.core.remoting.impl.netty;
+package org.apache.activemq.artemis.api.core;
 
-import java.util.Map;
-
-import io.netty.channel.Channel;
-import org.apache.activemq.artemis.spi.core.remoting.ServerConnectionLifeCycleListener;
+/**
+ * A client was redirected.
+ */
+public final class ActiveMQRedirectedException extends ActiveMQException {
 
-public class NettyServerConnection extends NettyConnection {
+   private static final long serialVersionUID = 7414966383933311627L;
 
-   public NettyServerConnection(Map<String, Object> configuration,
-                                Channel channel,
-                                ServerConnectionLifeCycleListener listener,
-                                boolean batchingEnabled,
-                                boolean directDeliver) {
-      super(configuration, channel, listener, batchingEnabled, directDeliver);
+   public ActiveMQRedirectedException() {
+      super(ActiveMQExceptionType.REDIRECTED);
    }
 
+   public ActiveMQRedirectedException(String message) {
+      super(ActiveMQExceptionType.REDIRECTED, message);
+   }
 }
+
diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/DisconnectReason.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/DisconnectReason.java
new file mode 100644
index 0000000..ff5831e
--- /dev/null
+++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/api/core/DisconnectReason.java
@@ -0,0 +1,73 @@
+/*
+ * 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.core;
+
+public enum DisconnectReason {
+   REDIRECT((byte)0, false),
+   REDIRECT_ON_CRITICAL_ERROR((byte)1, true),
+   SCALE_DOWN((byte)2, false),
+   SCALE_DOWN_ON_CRITICAL_ERROR((byte)3, true),
+   SHOUT_DOWN((byte)4, false),
+   SHOUT_DOWN_ON_CRITICAL_ERROR((byte)5, true);
+
+   private final byte type;
+   private final boolean criticalError;
+
+   DisconnectReason(byte type, boolean criticalError) {
+      this.type = type;
+      this.criticalError = criticalError;
+   }
+
+   public byte getType() {
+      return type;
+   }
+
+   public boolean isCriticalError() {
+      return criticalError;
+   }
+
+   public boolean isRedirect() {
+      return this == REDIRECT || this == REDIRECT_ON_CRITICAL_ERROR;
+   }
+
+   public boolean isScaleDown() {
+      return this == SCALE_DOWN || this == SCALE_DOWN_ON_CRITICAL_ERROR;
+   }
+
+   public boolean isShutDown() {
+      return this == SHOUT_DOWN || this == SHOUT_DOWN_ON_CRITICAL_ERROR;
+   }
+
+   public static DisconnectReason getType(byte type) {
+      switch (type) {
+         case 0:
+            return REDIRECT;
+         case 1:
+            return REDIRECT_ON_CRITICAL_ERROR;
+         case 2:
+            return SCALE_DOWN;
+         case 3:
+            return SCALE_DOWN_ON_CRITICAL_ERROR;
+         case 4:
+            return SHOUT_DOWN;
+         case 5:
+            return SHOUT_DOWN_ON_CRITICAL_ERROR;
+         default:
+            return null;
+      }
+   }
+}
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/TransportConfiguration.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/TransportConfiguration.java
index ee285a3..74657cc 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/TransportConfiguration.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/TransportConfiguration.java
@@ -255,7 +255,7 @@ public class TransportConfiguration implements Serializable {
    public String toString() {
       StringBuilder str = new StringBuilder(TransportConfiguration.class.getSimpleName());
       str.append("(name=" + name + ", ");
-      str.append("factory=" + replaceWildcardChars(factoryClassName));
+      str.append("factory=" + (factoryClassName == null ? "null" : replaceWildcardChars(factoryClassName)));
       str.append(") ");
       str.append(toStringParameters(params, extraProps));
       return str.toString();
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/client/ClientSession.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/client/ClientSession.java
index ad7e702..51df731 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/client/ClientSession.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/client/ClientSession.java
@@ -40,6 +40,14 @@ public interface ClientSession extends XAResource, AutoCloseable {
    String JMS_SESSION_IDENTIFIER_PROPERTY = "jms-session";
 
    /**
+    * Just like {@link ClientSession.AddressQuery#JMS_SESSION_IDENTIFIER_PROPERTY} this is
+    * used to identify the ClientID over JMS Session.
+    * However this is only used when the JMS Session.clientID is set (which is optional).
+    * With this property management tools and the server can identify the jms-client-id used over JMS
+    */
+   String JMS_SESSION_CLIENT_ID_PROPERTY = "jms-client-id";
+
+   /**
     * Information returned by a binding query
     *
     * @see ClientSession#addressQuery(SimpleString)
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/client/ClientSessionFactory.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/client/ClientSessionFactory.java
index 9fc1f48..e1e18e9 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/client/ClientSessionFactory.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/client/ClientSessionFactory.java
@@ -136,6 +136,32 @@ public interface ClientSessionFactory extends AutoCloseable {
                                int ackBatchSize) throws ActiveMQException;
 
    /**
+    * Creates an <em>authenticated</em> session.
+    * <p>
+    * It is possible to <em>pre-acknowledge messages on the server</em> so that the client can avoid additional network trip
+    * to the server to acknowledge messages. While this increase performance, this does not guarantee delivery (as messages
+    * can be lost after being pre-acknowledged on the server). Use with caution if your application design permits it.
+    *
+    * @param username        the user name
+    * @param password        the user password
+    * @param xa              whether the session support XA transaction semantic or not
+    * @param autoCommitSends <code>true</code> to automatically commit message sends, <code>false</code> to commit manually
+    * @param autoCommitAcks  <code>true</code> to automatically commit message acknowledgement, <code>false</code> to commit manually
+    * @param preAcknowledge  <code>true</code> to pre-acknowledge messages on the server, <code>false</code> to let the client acknowledge the messages
+    * @param clientID        the session clientID
+    * @return a ClientSession
+    * @throws ActiveMQException if an exception occurs while creating the session
+    */
+   ClientSession createSession(String username,
+                               String password,
+                               boolean xa,
+                               boolean autoCommitSends,
+                               boolean autoCommitAcks,
+                               boolean preAcknowledge,
+                               int ackBatchSize,
+                               String clientID) throws ActiveMQException;
+
+   /**
     * Closes this factory and any session created by it.
     */
    @Override
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 599ff73..7d77a87 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
@@ -18,7 +18,6 @@
 package org.apache.activemq.artemis.api.core.management;
 
 import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
-import org.apache.activemq.artemis.api.core.ActiveMQException;
 import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
 import org.apache.activemq.artemis.api.core.client.ClientMessage;
 import org.apache.activemq.artemis.api.core.client.ClientRequestor;
@@ -28,51 +27,67 @@ import org.apache.activemq.artemis.api.core.client.ServerLocator;
 
 public class ActiveMQManagementProxy implements AutoCloseable {
 
-   private final String username;
-   private final String password;
-   private final ServerLocator locator;
+   private final ServerLocator serverLocator;
 
-   private ClientSessionFactory sessionFactory;
-   private ClientSession session;
-   private ClientRequestor requestor;
+   private final ClientSessionFactory sessionFactory;
 
-   public ActiveMQManagementProxy(final ServerLocator locator, final String username, final String password) {
-      this.locator = locator;
-      this.username = username;
-      this.password = password;
+   private final ClientSession clientSession;
+
+
+   public ActiveMQManagementProxy(final ClientSession session) {
+      serverLocator = null;
+      sessionFactory = null;
+      clientSession = session;
    }
 
-   public void start() throws Exception {
+   public ActiveMQManagementProxy(final ServerLocator locator, final String username, final String password) throws Exception {
+      serverLocator = locator;
       sessionFactory = locator.createSessionFactory();
-      session = sessionFactory.createSession(username, password, false, true, true, false, ActiveMQClient.DEFAULT_ACK_BATCH_SIZE);
-      requestor = new ClientRequestor(session, ActiveMQDefaultConfiguration.getDefaultManagementAddress());
-
-      session.start();
+      clientSession = sessionFactory.createSession(username, password, false, true, true, false, ActiveMQClient.DEFAULT_ACK_BATCH_SIZE).start();
    }
 
-   public <T> T invokeOperation(final Class<T> type, final String resourceName, final String operationName, final Object... operationArgs) throws Exception {
-      ClientMessage request = session.createMessage(false);
+   public <T> T getAttribute(final String resourceName, final String attributeName, final Class<T> attributeClass, final int timeout) throws Exception {
+      try (ClientRequestor requestor = new ClientRequestor(clientSession, ActiveMQDefaultConfiguration.getDefaultManagementAddress())) {
+         ClientMessage request = clientSession.createMessage(false);
 
-      ManagementHelper.putOperationInvocation(request, resourceName, operationName, operationArgs);
+         ManagementHelper.putAttribute(request, resourceName, attributeName);
 
-      ClientMessage reply = requestor.request(request);
+         ClientMessage reply = requestor.request(request, timeout);
 
-      if (ManagementHelper.hasOperationSucceeded(reply)) {
-         return (T)ManagementHelper.getResult(reply, type);
-      } else {
-         throw new Exception("Failed to invoke " + resourceName + "." + operationName + ". Reason: " + ManagementHelper.getResult(reply, String.class));
+         if (ManagementHelper.hasOperationSucceeded(reply)) {
+            return (T)ManagementHelper.getResult(reply, attributeClass);
+         } else {
+            throw new Exception("Failed to get " + resourceName + "." + attributeName + ". Reason: " + ManagementHelper.getResult(reply, String.class));
+         }
       }
    }
 
+   public <T> T invokeOperation(final String resourceName, final String operationName, final Object[] operationParams, final Class<T> operationClass, final int timeout) throws Exception {
+      try (ClientRequestor requestor = new ClientRequestor(clientSession, ActiveMQDefaultConfiguration.getDefaultManagementAddress())) {
+         ClientMessage request = clientSession.createMessage(false);
+
+         ManagementHelper.putOperationInvocation(request, resourceName, operationName, operationParams);
+
+         ClientMessage reply = requestor.request(request, timeout);
 
-   public void stop() throws ActiveMQException {
-      session.stop();
+         if (ManagementHelper.hasOperationSucceeded(reply)) {
+            return (T)ManagementHelper.getResult(reply, operationClass);
+         } else {
+            throw new Exception("Failed to invoke " + resourceName + "." + operationName + ". Reason: " + ManagementHelper.getResult(reply, String.class));
+         }
+      }
    }
 
    @Override
    public void close() throws Exception {
-      requestor.close();
-      session.close();
-      sessionFactory.close();
+      clientSession.close();
+
+      if (sessionFactory != null) {
+         sessionFactory.close();
+      }
+
+      if (serverLocator != null) {
+         serverLocator.close();
+      }
    }
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/BrokerBalancerControl.java
similarity index 55%
copy from artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
copy to artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/BrokerBalancerControl.java
index f9e1b3d..983888f 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/BrokerBalancerControl.java
@@ -14,21 +14,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.activemq.artemis.core.remoting.impl.netty;
+package org.apache.activemq.artemis.api.core.management;
 
-import java.util.Map;
+import javax.management.MBeanOperationInfo;
+import javax.management.openmbean.CompositeData;
 
-import io.netty.channel.Channel;
-import org.apache.activemq.artemis.spi.core.remoting.ServerConnectionLifeCycleListener;
-
-public class NettyServerConnection extends NettyConnection {
-
-   public NettyServerConnection(Map<String, Object> configuration,
-                                Channel channel,
-                                ServerConnectionLifeCycleListener listener,
-                                boolean batchingEnabled,
-                                boolean directDeliver) {
-      super(configuration, channel, listener, batchingEnabled, directDeliver);
-   }
+/**
+ * A BrokerBalancerControl is used to manage a BrokerBalancer.
+ */
+public interface BrokerBalancerControl {
+   @Operation(desc = "Get the target associated with key", impact = MBeanOperationInfo.INFO)
+   CompositeData getTarget(@Parameter(desc = "a key", name = "key") String key) throws Exception;
 
+   @Operation(desc = "Get the target associated with key as JSON", impact = MBeanOperationInfo.INFO)
+   String getTargetAsJSON(@Parameter(desc = "a key", name = "key") String key);
 }
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ObjectNameBuilder.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ObjectNameBuilder.java
index dacc91f..a97ff7c 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ObjectNameBuilder.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ObjectNameBuilder.java
@@ -149,6 +149,15 @@ public final class ObjectNameBuilder {
       return createObjectName("cluster-connection", name);
    }
 
+   /**
+    * Returns the ObjectName used by BrokerBalancerControl.
+    *
+    * @see BrokerBalancerControl
+    */
+   public ObjectName getBrokerBalancerObjectName(final String name) throws Exception {
+      return createObjectName("broker-balancer", name);
+   }
+
    private ObjectName createObjectName(final String type, final String name) throws Exception {
       return ObjectName.getInstance(String.format("%s,component=%ss,name=%s", getActiveMQServerName(), type, ObjectName.quote(name)));
    }
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ResourceNames.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ResourceNames.java
index 7aaa54d..0d45dd9 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ResourceNames.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ResourceNames.java
@@ -44,6 +44,8 @@ public final class ResourceNames {
 
    public static final String BROADCAST_GROUP = "broadcastgroup.";
 
+   public static final String BROKER_BALANCER = "brokerbalancer.";
+
    public static final String RETROACTIVE_SUFFIX = "retro";
 
    public static SimpleString getRetroactiveResourceQueueName(String prefix, String delimiter, SimpleString address, RoutingType routingType) {
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 8468e84..3bbf51b 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
@@ -27,6 +27,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQLargeMessageException;
 import org.apache.activemq.artemis.api.core.ActiveMQLargeMessageInterruptedException;
 import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException;
 import org.apache.activemq.artemis.api.core.ActiveMQObjectClosedException;
+import org.apache.activemq.artemis.api.core.ActiveMQRedirectedException;
 import org.apache.activemq.artemis.api.core.ActiveMQTransactionOutcomeUnknownException;
 import org.apache.activemq.artemis.api.core.ActiveMQTransactionRolledBackException;
 import org.apache.activemq.artemis.api.core.ActiveMQUnBlockedException;
@@ -237,4 +238,7 @@ public interface ActiveMQClientMessageBundle {
 
    @Message(id = 219065, value = "Failed to handle packet.")
    RuntimeException failedToHandlePacket(@Cause Exception e);
+
+   @Message(id = 219066, value = "The connection was redirected")
+   ActiveMQRedirectedException redirected();
 }
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 92236f4..80a2428 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
@@ -37,6 +37,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
 import org.apache.activemq.artemis.api.core.ActiveMQException;
 import org.apache.activemq.artemis.api.core.ActiveMQInterruptedException;
 import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException;
+import org.apache.activemq.artemis.api.core.DisconnectReason;
 import org.apache.activemq.artemis.api.core.Interceptor;
 import org.apache.activemq.artemis.api.core.Pair;
 import org.apache.activemq.artemis.api.core.TransportConfiguration;
@@ -79,11 +80,13 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
 
    private final ClientProtocolManager clientProtocolManager;
 
-   private TransportConfiguration connectorConfig;
+   private final TransportConfiguration connectorConfig;
 
-   private TransportConfiguration currentConnectorConfig;
+   private TransportConfiguration previousConnectorConfig;
 
-   private volatile TransportConfiguration backupConfig;
+   private volatile TransportConfiguration currentConnectorConfig;
+
+   private volatile TransportConfiguration backupConnectorConfig;
 
    private ConnectorFactory connectorFactory;
 
@@ -184,6 +187,8 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
 
       this.clientProtocolManager.setSessionFactory(this);
 
+      this.connectorConfig = connectorConfig.getA();
+
       this.currentConnectorConfig = connectorConfig.getA();
 
       connectorFactory = instantiateConnectorFactory(connectorConfig.getA().getFactoryClassName());
@@ -231,7 +236,7 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
       connectionReadyForWrites = true;
 
       if (connectorConfig.getB() != null) {
-         this.backupConfig = connectorConfig.getB();
+         this.backupConnectorConfig = connectorConfig.getB();
       }
    }
 
@@ -253,8 +258,8 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
 
       if (connection == null) {
          StringBuilder msg = new StringBuilder("Unable to connect to server using configuration ").append(currentConnectorConfig);
-         if (backupConfig != null) {
-            msg.append(" and backup configuration ").append(backupConfig);
+         if (backupConnectorConfig != null) {
+            msg.append(" and backup configuration ").append(backupConnectorConfig);
          }
          throw new ActiveMQNotConnectedException(msg.toString());
       }
@@ -288,7 +293,7 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
          if (logger.isDebugEnabled()) {
             logger.debug("Setting up backup config = " + backUp + " for live = " + live);
          }
-         backupConfig = backUp;
+         backupConnectorConfig = backUp;
       } else {
          if (logger.isDebugEnabled()) {
             logger.debug("ClientSessionFactoryImpl received backup update for live/backup pair = " + live +
@@ -302,7 +307,19 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
 
    @Override
    public Object getBackupConnector() {
-      return backupConfig;
+      return backupConnectorConfig;
+   }
+
+   @Override
+   public ClientSession createSession(final String username,
+                                      final String password,
+                                      final boolean xa,
+                                      final boolean autoCommitSends,
+                                      final boolean autoCommitAcks,
+                                      final boolean preAcknowledge,
+                                      final int ackBatchSize,
+                                      final String clientID) throws ActiveMQException {
+      return createSessionInternal(username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, ackBatchSize, clientID);
    }
 
    @Override
@@ -313,42 +330,42 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
                                       final boolean autoCommitAcks,
                                       final boolean preAcknowledge,
                                       final int ackBatchSize) throws ActiveMQException {
-      return createSessionInternal(username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, ackBatchSize);
+      return createSessionInternal(username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, ackBatchSize, null);
    }
 
    @Override
    public ClientSession createSession(final boolean autoCommitSends,
                                       final boolean autoCommitAcks,
                                       final int ackBatchSize) throws ActiveMQException {
-      return createSessionInternal(null, null, false, autoCommitSends, autoCommitAcks, serverLocator.isPreAcknowledge(), ackBatchSize);
+      return createSessionInternal(null, null, false, autoCommitSends, autoCommitAcks, serverLocator.isPreAcknowledge(), ackBatchSize, null);
    }
 
    @Override
    public ClientSession createXASession() throws ActiveMQException {
-      return createSessionInternal(null, null, true, false, false, serverLocator.isPreAcknowledge(), serverLocator.getAckBatchSize());
+      return createSessionInternal(null, null, true, false, false, serverLocator.isPreAcknowledge(), serverLocator.getAckBatchSize(), null);
    }
 
    @Override
    public ClientSession createTransactedSession() throws ActiveMQException {
-      return createSessionInternal(null, null, false, false, false, serverLocator.isPreAcknowledge(), serverLocator.getAckBatchSize());
+      return createSessionInternal(null, null, false, false, false, serverLocator.isPreAcknowledge(), serverLocator.getAckBatchSize(), null);
    }
 
    @Override
    public ClientSession createSession() throws ActiveMQException {
-      return createSessionInternal(null, null, false, true, true, serverLocator.isPreAcknowledge(), serverLocator.getAckBatchSize());
+      return createSessionInternal(null, null, false, true, true, serverLocator.isPreAcknowledge(), serverLocator.getAckBatchSize(), null);
    }
 
    @Override
    public ClientSession createSession(final boolean autoCommitSends,
                                       final boolean autoCommitAcks) throws ActiveMQException {
-      return createSessionInternal(null, null, false, autoCommitSends, autoCommitAcks, serverLocator.isPreAcknowledge(), serverLocator.getAckBatchSize());
+      return createSessionInternal(null, null, false, autoCommitSends, autoCommitAcks, serverLocator.isPreAcknowledge(), serverLocator.getAckBatchSize(), null);
    }
 
    @Override
    public ClientSession createSession(final boolean xa,
                                       final boolean autoCommitSends,
                                       final boolean autoCommitAcks) throws ActiveMQException {
-      return createSessionInternal(null, null, xa, autoCommitSends, autoCommitAcks, serverLocator.isPreAcknowledge(), serverLocator.getAckBatchSize());
+      return createSessionInternal(null, null, xa, autoCommitSends, autoCommitAcks, serverLocator.isPreAcknowledge(), serverLocator.getAckBatchSize(), null);
    }
 
    @Override
@@ -356,7 +373,7 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
                                       final boolean autoCommitSends,
                                       final boolean autoCommitAcks,
                                       final boolean preAcknowledge) throws ActiveMQException {
-      return createSessionInternal(null, null, xa, autoCommitSends, autoCommitAcks, preAcknowledge, serverLocator.getAckBatchSize());
+      return createSessionInternal(null, null, xa, autoCommitSends, autoCommitAcks, preAcknowledge, serverLocator.getAckBatchSize(), null);
    }
 
    // ClientConnectionLifeCycleListener implementation --------------------------------------------------
@@ -717,10 +734,11 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
                                                final boolean autoCommitSends,
                                                final boolean autoCommitAcks,
                                                final boolean preAcknowledge,
-                                               final int ackBatchSize) throws ActiveMQException {
+                                               final int ackBatchSize,
+                                               final String clientID) throws ActiveMQException {
       String name = UUIDGenerator.getInstance().generateStringUUID();
 
-      SessionContext context = createSessionChannel(name, username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge);
+      SessionContext context = createSessionChannel(name, username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, clientID);
 
       ClientSessionInternal session = new ClientSessionImpl(this, name, username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, serverLocator.isBlockOnAcknowledge(), serverLocator.isAutoGroup(), ackBatchSize, serverLocator.getConsumerWindowSize(), serverLocator.getConsumerMaxRate(), serverLocator.getConfirmationWindowSize(), serverLocator.getProducerWindowSize(), serverLocator.getProducerMaxRate(), serverLocator.isBlockOnNonDurableSend(), serverLocator.isBlockOnDurableSe [...]
 
@@ -1050,11 +1068,13 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
    public class CloseRunnable implements Runnable {
 
       private final RemotingConnection conn;
-      private final String scaleDownTargetNodeID;
+      private final DisconnectReason reason;
+      private final String targetNodeID;
 
-      public CloseRunnable(RemotingConnection conn, String scaleDownTargetNodeID) {
+      public CloseRunnable(RemotingConnection conn, DisconnectReason reason, String targetNodeID) {
          this.conn = conn;
-         this.scaleDownTargetNodeID = scaleDownTargetNodeID;
+         this.reason = reason;
+         this.targetNodeID = targetNodeID;
       }
 
       // Must be executed on new thread since cannot block the Netty thread for a long time and fail
@@ -1063,10 +1083,12 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
       public void run() {
          try {
             CLOSE_RUNNABLES.add(this);
-            if (scaleDownTargetNodeID == null) {
-               conn.fail(ActiveMQClientMessageBundle.BUNDLE.disconnected());
+            if (reason.isRedirect()) {
+               conn.fail(ActiveMQClientMessageBundle.BUNDLE.redirected());
+            } else if (reason.isScaleDown()) {
+               conn.fail(ActiveMQClientMessageBundle.BUNDLE.disconnected(), targetNodeID);
             } else {
-               conn.fail(ActiveMQClientMessageBundle.BUNDLE.disconnected(), scaleDownTargetNodeID);
+               conn.fail(ActiveMQClientMessageBundle.BUNDLE.disconnected());
             }
          } finally {
             CLOSE_RUNNABLES.remove(this);
@@ -1146,72 +1168,39 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
       Connection transportConnection = null;
 
       try {
-         if (logger.isDebugEnabled()) {
-            logger.debug("Trying to connect with connectorFactory = " + connectorFactory +
-                           ", connectorConfig=" + currentConnectorConfig);
-         }
-
-         Connector liveConnector = createConnector(connectorFactory, currentConnectorConfig);
-
-         if ((transportConnection = openTransportConnection(liveConnector)) != null) {
-            // if we can't connect the connect method will return null, hence we have to try the backup
-            connector = liveConnector;
+         //Try to connect with the current connector configuration
+         transportConnection = createTransportConnection("current", currentConnectorConfig);
+         if (transportConnection != null) {
             return transportConnection;
-         } else if (backupConfig != null) {
-            if (logger.isDebugEnabled()) {
-               logger.debug("Trying backup config = " + backupConfig);
-            }
-
-            ConnectorFactory backupConnectorFactory = instantiateConnectorFactory(backupConfig.getFactoryClassName());
-
-            Connector backupConnector = createConnector(backupConnectorFactory, backupConfig);
+         }
 
-            transportConnection = openTransportConnection(backupConnector);
 
+         if (backupConnectorConfig != null) {
+            //Try to connect with the backup connector configuration
+            transportConnection = createTransportConnection("backup", backupConnectorConfig);
             if (transportConnection != null) {
-            /*looks like the backup is now live, let's use that*/
-
-               if (logger.isDebugEnabled()) {
-                  logger.debug("Connected to the backup at " + backupConfig);
-               }
-
-               // Switching backup as live
-               connector = backupConnector;
-               connectorConfig = currentConnectorConfig;
-               currentConnectorConfig = backupConfig;
-               connectorFactory = backupConnectorFactory;
                return transportConnection;
             }
          }
 
-         if (logger.isDebugEnabled()) {
-            logger.debug("Backup is not active, trying original connection configuration now.");
+         if (previousConnectorConfig != null && !currentConnectorConfig.equals(previousConnectorConfig)) {
+            //Try to connect with the previous connector configuration
+            transportConnection = createTransportConnection("previous", previousConnectorConfig);
+            if (transportConnection != null) {
+               return transportConnection;
+            }
          }
 
-
-         if (currentConnectorConfig.equals(connectorConfig) || connectorConfig == null) {
-
-            // There was no changes on current and original connectors, just return null here and let the retry happen at the first portion of this method on the next retry
-            return null;
+         if (!currentConnectorConfig.equals(connectorConfig)) {
+            //Try to connect with the initial connector configuration
+            transportConnection = createTransportConnection("initial", connectorConfig);
+            if (transportConnection != null) {
+               return transportConnection;
+            }
          }
 
-         ConnectorFactory lastConnectorFactory = instantiateConnectorFactory(connectorConfig.getFactoryClassName());
-
-         Connector lastConnector = createConnector(lastConnectorFactory, connectorConfig);
-
-         transportConnection = openTransportConnection(lastConnector);
-
-         if (transportConnection != null) {
-            logger.debug("Returning into original connector");
-            connector = lastConnector;
-            TransportConfiguration temp = currentConnectorConfig;
-            currentConnectorConfig = connectorConfig;
-            connectorConfig = temp;
-            return transportConnection;
-         } else {
-            logger.debug("no connection been made, returning null");
-            return null;
-         }
+         logger.debug("no connection been made, returning null");
+         return null;
       } catch (Exception cause) {
          // Sanity catch for badly behaved remoting plugins
 
@@ -1236,6 +1225,33 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
 
    }
 
+   private Connection createTransportConnection(String name, TransportConfiguration transportConnectorConfig) {
+      ConnectorFactory transportConnectorFactory = instantiateConnectorFactory(
+         transportConnectorConfig.getFactoryClassName());
+
+      if (logger.isDebugEnabled()) {
+         logger.debug("Trying to connect with connectorFactory=" + transportConnectorFactory
+            + " and " + name + "ConnectorConfig: " + transportConnectorConfig);
+      }
+
+      Connector transportConnector = createConnector(transportConnectorFactory, transportConnectorConfig);
+
+      Connection transportConnection = openTransportConnection(transportConnector);
+
+      if (transportConnection != null) {
+         if (logger.isDebugEnabled()) {
+            logger.debug("Connected with the " + name + "ConnectorConfig=" + transportConnectorConfig);
+         }
+
+         connector = transportConnector;
+         connectorFactory = transportConnectorFactory;
+         previousConnectorConfig = currentConnectorConfig;
+         currentConnectorConfig = transportConnectorConfig;
+      }
+
+      return transportConnection;
+   }
+
    private class DelegatingBufferHandler implements BufferHandler {
 
       @Override
@@ -1413,9 +1429,10 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
                                                  final boolean xa,
                                                  final boolean autoCommitSends,
                                                  final boolean autoCommitAcks,
-                                                 final boolean preAcknowledge) throws ActiveMQException {
+                                                 final boolean preAcknowledge,
+                                                 final String clientID) throws ActiveMQException {
       synchronized (createSessionLock) {
-         return clientProtocolManager.createSessionContext(name, username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, serverLocator.getMinLargeMessageSize(), serverLocator.getConfirmationWindowSize());
+         return clientProtocolManager.createSessionContext(name, username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, serverLocator.getMinLargeMessageSize(), serverLocator.getConfirmationWindowSize(), clientID);
       }
    }
 
@@ -1427,20 +1444,39 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
    class SessionFactoryTopologyHandler implements TopologyResponseHandler {
 
       @Override
-      public void nodeDisconnected(RemotingConnection conn, String nodeID, String scaleDownTargetNodeID) {
+      public void nodeDisconnected(RemotingConnection conn, String nodeID, DisconnectReason reason, String targetNodeID, TransportConfiguration tagetConnector) {
 
          if (logger.isTraceEnabled()) {
             logger.trace("Disconnect being called on client:" +
-                            " server locator = " +
-                            serverLocator +
-                            " notifying node " +
-                            nodeID +
-                            " as down", new Exception("trace"));
+                    " server locator = " +
+                    serverLocator +
+                    " notifying node " +
+                    nodeID +
+                    " as down with reason " +
+                    reason, new Exception("trace"));
          }
 
          serverLocator.notifyNodeDown(System.currentTimeMillis(), nodeID);
 
-         closeExecutor.execute(new CloseRunnable(conn, scaleDownTargetNodeID));
+         if (reason.isRedirect()) {
+            if (serverLocator.isHA()) {
+               TopologyMemberImpl topologyMember = serverLocator.getTopology().getMember(nodeID);
+
+               if (topologyMember != null) {
+                  if (topologyMember.getConnector().getB() != null) {
+                     backupConnectorConfig = topologyMember.getConnector().getB();
+                  } else if (logger.isDebugEnabled()) {
+                     logger.debug("The topology member " + nodeID + " with connector " + tagetConnector + " has no backup");
+                  }
+               } else if (logger.isDebugEnabled()) {
+                  logger.debug("The topology member " + nodeID + " with connector " + tagetConnector + " not found");
+               }
+            }
+
+            currentConnectorConfig = tagetConnector;
+         }
+
+         closeExecutor.execute(new CloseRunnable(conn, reason, targetNodeID));
 
       }
 
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 33e5a77..5d831da 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
@@ -33,6 +33,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
 import org.apache.activemq.artemis.api.core.ActiveMQBuffers;
 import org.apache.activemq.artemis.api.core.ActiveMQException;
 import org.apache.activemq.artemis.api.core.ActiveMQExceptionType;
+import org.apache.activemq.artemis.api.core.ActiveMQRedirectedException;
 import org.apache.activemq.artemis.api.core.Message;
 import org.apache.activemq.artemis.api.core.QueueAttributes;
 import org.apache.activemq.artemis.api.core.QueueConfiguration;
@@ -1460,6 +1461,9 @@ public final class ClientSessionImpl implements ClientSessionInternal, FailureLi
 
                sessionContext.returnBlocking(cause);
             }
+         } catch (ActiveMQRedirectedException e) {
+            logger.info("failedToHandleFailover.ActiveMQRedirectedException");
+            suc = false;
          } catch (Throwable t) {
             ActiveMQClientLogger.LOGGER.failedToHandleFailover(t);
             suc = false;
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/CoreRemotingConnection.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/CoreRemotingConnection.java
index 76f87cf..61a01dc 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/CoreRemotingConnection.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/CoreRemotingConnection.java
@@ -51,6 +51,16 @@ public interface CoreRemotingConnection extends RemotingConnection {
       return  version >= PacketImpl.ARTEMIS_2_7_0_VERSION;
    }
 
+   default boolean isVersionSupportClientID() {
+      int version = getChannelVersion();
+      return  version >= PacketImpl.ARTEMIS_2_18_0_VERSION;
+   }
+
+   default boolean isVersionSupportRedirect() {
+      int version = getChannelVersion();
+      return  version >= PacketImpl.ARTEMIS_2_18_0_VERSION;
+   }
+
    /**
     * Sets the client protocol used on the communication. This will determine if the client has
     * support for certain packet types
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 b1d6cc8..84ae30b 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
@@ -27,6 +27,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
 import org.apache.activemq.artemis.api.core.ActiveMQException;
 import org.apache.activemq.artemis.api.core.ActiveMQExceptionType;
 import org.apache.activemq.artemis.api.core.ActiveMQInterruptedException;
+import org.apache.activemq.artemis.api.core.DisconnectReason;
 import org.apache.activemq.artemis.api.core.Interceptor;
 import org.apache.activemq.artemis.api.core.Pair;
 import org.apache.activemq.artemis.api.core.SimpleString;
@@ -45,10 +46,13 @@ import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CheckFailo
 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;
+import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage_V4;
 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.DisconnectMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectMessage_V2;
+import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectMessage_V3;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.Ping;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.SubscribeClusterTopologyUpdatesMessageV2;
 import org.apache.activemq.artemis.core.remoting.impl.netty.ActiveMQFrameDecoder2;
@@ -243,10 +247,11 @@ public class ActiveMQClientProtocolManager implements ClientProtocolManager {
                                               boolean autoCommitAcks,
                                               boolean preAcknowledge,
                                               int minLargeMessageSize,
-                                              int confirmationWindowSize) throws ActiveMQException {
+                                              int confirmationWindowSize,
+                                              String clientID) throws ActiveMQException {
       for (Version clientVersion : VersionLoader.getClientVersions()) {
          try {
-            return createSessionContext(clientVersion, name, username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, minLargeMessageSize, confirmationWindowSize);
+            return createSessionContext(clientVersion, name, username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, minLargeMessageSize, confirmationWindowSize, clientID);
          } catch (ActiveMQException e) {
             if (e.getType() != ActiveMQExceptionType.INCOMPATIBLE_CLIENT_SERVER_VERSIONS) {
                throw e;
@@ -266,7 +271,8 @@ public class ActiveMQClientProtocolManager implements ClientProtocolManager {
                                               boolean autoCommitAcks,
                                               boolean preAcknowledge,
                                               int minLargeMessageSize,
-                                              int confirmationWindowSize) throws ActiveMQException {
+                                              int confirmationWindowSize,
+                                              String clientID) throws ActiveMQException {
       if (!isAlive())
          throw ActiveMQClientMessageBundle.BUNDLE.clientSessionClosed();
 
@@ -293,7 +299,7 @@ public class ActiveMQClientProtocolManager implements ClientProtocolManager {
 
             long sessionChannelID = connection.generateChannelID();
 
-            Packet request = newCreateSessionPacket(clientVersion, name, username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, minLargeMessageSize, confirmationWindowSize, sessionChannelID);
+            Packet request = newCreateSessionPacket(clientVersion.getIncrementingVersion(), name, username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, minLargeMessageSize, confirmationWindowSize, sessionChannelID, clientID);
 
             try {
                // channel1 reference here has to go away
@@ -302,7 +308,8 @@ public class ActiveMQClientProtocolManager implements ClientProtocolManager {
                if (!isAlive())
                   throw cause;
 
-               if (cause.getType() == ActiveMQExceptionType.UNBLOCKED) {
+               if (cause.getType() == ActiveMQExceptionType.UNBLOCKED ||
+                       cause.getType() == ActiveMQExceptionType.REDIRECTED) {
                   // This means the thread was blocked on create session and failover unblocked it
                   // so failover could occur
 
@@ -339,11 +346,11 @@ public class ActiveMQClientProtocolManager implements ClientProtocolManager {
       }
       while (retry);
       sessionChannel.getConnection().setChannelVersion(response.getServerVersion());
-      return newSessionContext(name, confirmationWindowSize, sessionChannel, response);
 
+      return newSessionContext(name, confirmationWindowSize, sessionChannel, response);
    }
 
-   protected Packet newCreateSessionPacket(Version clientVersion,
+   protected Packet newCreateSessionPacket(int clientVersion,
                                            String name,
                                            String username,
                                            String password,
@@ -353,8 +360,13 @@ public class ActiveMQClientProtocolManager implements ClientProtocolManager {
                                            boolean preAcknowledge,
                                            int minLargeMessageSize,
                                            int confirmationWindowSize,
-                                           long sessionChannelID) {
-      return new CreateSessionMessage(name, sessionChannelID, clientVersion.getIncrementingVersion(), username, password, minLargeMessageSize, xa, autoCommitSends, autoCommitAcks, preAcknowledge, confirmationWindowSize, null);
+                                           long sessionChannelID,
+                                           String clientID) {
+      if (connection.isVersionSupportClientID()) {
+         return new CreateSessionMessage_V2(name, sessionChannelID, clientVersion, username, password, minLargeMessageSize, xa, autoCommitSends, autoCommitAcks, preAcknowledge, confirmationWindowSize, null, clientID);
+      } else {
+         return new CreateSessionMessage(name, sessionChannelID, clientVersion, username, password, minLargeMessageSize, xa, autoCommitSends, autoCommitAcks, preAcknowledge, confirmationWindowSize, null);
+      }
    }
 
    protected SessionContext newSessionContext(String name,
@@ -459,19 +471,15 @@ public class ActiveMQClientProtocolManager implements ClientProtocolManager {
       public void handlePacket(final Packet packet) {
          final byte type = packet.getType();
 
-         if (type == PacketImpl.DISCONNECT || type == PacketImpl.DISCONNECT_V2) {
-            final DisconnectMessage msg = (DisconnectMessage) packet;
-            String scaleDownTargetNodeID = null;
-
-            SimpleString nodeID = msg.getNodeID();
-
-            if (packet instanceof DisconnectMessage_V2) {
-               final DisconnectMessage_V2 msg_v2 = (DisconnectMessage_V2) packet;
-               scaleDownTargetNodeID = msg_v2.getScaleDownNodeID() == null ? null : msg_v2.getScaleDownNodeID().toString();
-            }
-
-            if (topologyResponseHandler != null)
-               topologyResponseHandler.nodeDisconnected(conn, nodeID == null ? null : nodeID.toString(), scaleDownTargetNodeID);
+         if (type == PacketImpl.DISCONNECT) {
+            final DisconnectMessage disMessage = (DisconnectMessage) packet;
+            handleDisconnect(disMessage.getNodeID(), null, null, null);
+         } else if (type == PacketImpl.DISCONNECT_V2) {
+            final DisconnectMessage_V2 disMessage = (DisconnectMessage_V2) packet;
+            handleDisconnect(disMessage.getNodeID(), DisconnectReason.SCALE_DOWN, disMessage.getScaleDownNodeID(), null);
+         } else if (type == PacketImpl.DISCONNECT_V3) {
+            final DisconnectMessage_V3 disMessage = (DisconnectMessage_V3) packet;
+            handleDisconnect(disMessage.getNodeID(), disMessage.getReason(), disMessage.getTargetNodeID(), disMessage.getTargetConnector());
          } else if (type == PacketImpl.CLUSTER_TOPOLOGY) {
             ClusterTopologyChangeMessage topMessage = (ClusterTopologyChangeMessage) packet;
             notifyTopologyChange(updateTransportConfiguration(topMessage));
@@ -481,11 +489,22 @@ public class ActiveMQClientProtocolManager implements ClientProtocolManager {
          } else if (type == PacketImpl.CLUSTER_TOPOLOGY_V3) {
             ClusterTopologyChangeMessage_V3 topMessage = (ClusterTopologyChangeMessage_V3) packet;
             notifyTopologyChange(updateTransportConfiguration(topMessage));
+         } else if (type == PacketImpl.CLUSTER_TOPOLOGY_V4) {
+            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");
          }
       }
 
+      private void handleDisconnect(SimpleString nodeID, DisconnectReason reason, SimpleString targetNodeID, TransportConfiguration tagetConnector) {
+         if (topologyResponseHandler != null) {
+            topologyResponseHandler.nodeDisconnected(conn, nodeID == null ? null : nodeID.toString(), reason,
+                    targetNodeID == null ? null : targetNodeID.toString(), tagetConnector);
+         }
+      }
+
       /**
        * @param topMessage
        */
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 18bb08c..d133826 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
@@ -184,6 +184,10 @@ public final class ChannelImpl implements Channel {
             return version >= 129;
          case PacketImpl.SESS_BINDINGQUERY_RESP_V4:
             return version >= 129;
+         case PacketImpl.CLUSTER_TOPOLOGY_V4:
+         case PacketImpl.CREATESESSION_V2:
+         case PacketImpl.DISCONNECT_V3:
+            return version >= PacketImpl.ARTEMIS_2_18_0_VERSION;
          default:
             return true;
       }
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 9cc62b2..640e079 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
@@ -29,10 +29,12 @@ import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CheckFailo
 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;
+import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage_V4;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateAddressMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateQueueMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateQueueMessage_V2;
 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.CreateSharedQueueMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSharedQueueMessage_V2;
@@ -40,6 +42,7 @@ import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.Disconnect
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectConsumerWithKillMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectMessage_V2;
+import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectMessage_V3;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.FederationDownstreamConnectMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.NullResponseMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.NullResponseMessage_V2;
@@ -98,8 +101,10 @@ import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CHE
 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;
+import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CLUSTER_TOPOLOGY_V4;
 import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CREATESESSION;
 import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CREATESESSION_RESP;
+import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CREATESESSION_V2;
 import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CREATE_ADDRESS;
 import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CREATE_QUEUE;
 import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.CREATE_QUEUE_V2;
@@ -109,6 +114,7 @@ import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.DEL
 import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.DISCONNECT;
 import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.DISCONNECT_CONSUMER;
 import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.DISCONNECT_V2;
+import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.DISCONNECT_V3;
 import static org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl.EXCEPTION;
 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;
@@ -477,6 +483,18 @@ public abstract class PacketDecoder implements Serializable {
             packet = new FederationDownstreamConnectMessage();
             break;
          }
+         case CLUSTER_TOPOLOGY_V4: {
+            packet = new ClusterTopologyChangeMessage_V4();
+            break;
+         }
+         case CREATESESSION_V2: {
+            packet = new CreateSessionMessage_V2();
+            break;
+         }
+         case DISCONNECT_V3: {
+            packet = new DisconnectMessage_V3();
+            break;
+         }
          default: {
             throw ActiveMQClientMessageBundle.BUNDLE.invalidType(packetType);
          }
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 e4a759b..85275bf 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
@@ -38,6 +38,8 @@ public class PacketImpl implements Packet {
    public static final int CONSUMER_PRIORITY_CHANGE_VERSION = ARTEMIS_2_7_0_VERSION;
    public static final int FQQN_CHANGE_VERSION = ARTEMIS_2_7_0_VERSION;
 
+   // 2.18.0
+   public static final int ARTEMIS_2_18_0_VERSION = 131;
 
    public static final SimpleString OLD_QUEUE_PREFIX = new SimpleString("jms.queue.");
    public static final SimpleString OLD_TEMP_QUEUE_PREFIX = new SimpleString("jms.tempqueue.");
@@ -279,6 +281,11 @@ public class PacketImpl implements Packet {
 
    public static final byte FEDERATION_DOWNSTREAM_CONNECT = -16;
 
+   public static final byte CLUSTER_TOPOLOGY_V4 = -17;
+
+   public static final byte CREATESESSION_V2 = -18;
+
+   public static final byte DISCONNECT_V3 = -19;
 
    // Static --------------------------------------------------------
 
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/RemotingConnectionImpl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/RemotingConnectionImpl.java
index 8f4e1b7..c62e0a2 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/RemotingConnectionImpl.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/RemotingConnectionImpl.java
@@ -26,9 +26,12 @@ import java.util.concurrent.TimeUnit;
 
 import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
 import org.apache.activemq.artemis.api.core.ActiveMQException;
+import org.apache.activemq.artemis.api.core.ActiveMQRedirectedException;
 import org.apache.activemq.artemis.api.core.ActiveMQRemoteDisconnectException;
+import org.apache.activemq.artemis.api.core.DisconnectReason;
 import org.apache.activemq.artemis.api.core.Interceptor;
 import org.apache.activemq.artemis.api.core.SimpleString;
+import org.apache.activemq.artemis.api.core.TransportConfiguration;
 import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
 import org.apache.activemq.artemis.core.client.ActiveMQClientLogger;
 import org.apache.activemq.artemis.core.protocol.core.Channel;
@@ -38,6 +41,7 @@ import org.apache.activemq.artemis.core.protocol.core.impl.ChannelImpl.CHANNEL_I
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectConsumerWithKillMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectMessage_V2;
+import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.DisconnectMessage_V3;
 import org.apache.activemq.artemis.core.security.ActiveMQPrincipal;
 import org.apache.activemq.artemis.spi.core.protocol.AbstractRemotingConnection;
 import org.apache.activemq.artemis.spi.core.remoting.Connection;
@@ -206,7 +210,7 @@ public class RemotingConnectionImpl extends AbstractRemotingConnection implement
          destroyed = true;
       }
 
-      if (!(me instanceof ActiveMQRemoteDisconnectException)) {
+      if (!(me instanceof ActiveMQRemoteDisconnectException) && !(me instanceof ActiveMQRedirectedException)) {
          ActiveMQClientLogger.LOGGER.connectionFailureDetected(transportConnection.getRemoteAddress(), me.getMessage(), me.getType());
       }
 
@@ -250,11 +254,16 @@ public class RemotingConnectionImpl extends AbstractRemotingConnection implement
 
    @Override
    public void disconnect(final boolean criticalError) {
-      disconnect(null, criticalError);
+      disconnect(criticalError ? DisconnectReason.SHOUT_DOWN_ON_CRITICAL_ERROR : DisconnectReason.SHOUT_DOWN, null, null);
    }
 
    @Override
    public void disconnect(String scaleDownNodeID, final boolean criticalError) {
+      disconnect(criticalError ? DisconnectReason.SCALE_DOWN_ON_CRITICAL_ERROR : DisconnectReason.SCALE_DOWN, scaleDownNodeID, null);
+   }
+
+   @Override
+   public void disconnect(DisconnectReason reason, String targetNodeID, TransportConfiguration targetConnector) {
       Channel channel0 = getChannel(ChannelImpl.CHANNEL_ID.PING.id, -1);
 
       // And we remove all channels from the connection, this ensures no more packets will be processed after this
@@ -263,7 +272,7 @@ public class RemotingConnectionImpl extends AbstractRemotingConnection implement
 
       Set<Channel> allChannels = new HashSet<>(channels.values());
 
-      if (!criticalError) {
+      if (!reason.isCriticalError()) {
          removeAllChannels();
       } else {
          // We can't hold a lock if a critical error is happening...
@@ -273,15 +282,17 @@ public class RemotingConnectionImpl extends AbstractRemotingConnection implement
 
       // Now we are 100% sure that no more packets will be processed we can flush then send the disconnect
 
-      if (!criticalError) {
+      if (!reason.isCriticalError()) {
          for (Channel channel : allChannels) {
             channel.flushConfirmations();
          }
       }
       Packet disconnect;
 
-      if (channel0.supports(PacketImpl.DISCONNECT_V2)) {
-         disconnect = new DisconnectMessage_V2(nodeID, scaleDownNodeID);
+      if (channel0.supports(PacketImpl.DISCONNECT_V3)) {
+         disconnect = new DisconnectMessage_V3(nodeID, reason, SimpleString.toSimpleString(targetNodeID), targetConnector);
+      } else if (channel0.supports(PacketImpl.DISCONNECT_V2)) {
+         disconnect = new DisconnectMessage_V2(nodeID, reason.isScaleDown() ? targetNodeID : null);
       } else {
          disconnect = new DisconnectMessage(nodeID);
       }
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ClusterTopologyChangeMessage_V3.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ClusterTopologyChangeMessage_V3.java
index d371eb5..f3bcfd7 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ClusterTopologyChangeMessage_V3.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ClusterTopologyChangeMessage_V3.java
@@ -30,7 +30,17 @@ public class ClusterTopologyChangeMessage_V3 extends ClusterTopologyChangeMessag
                                           final String scaleDownGroupName,
                                           final Pair<TransportConfiguration, TransportConfiguration> pair,
                                           final boolean last) {
-      super(CLUSTER_TOPOLOGY_V3);
+      this(CLUSTER_TOPOLOGY_V3, uniqueEventID, nodeID, backupGroupName, scaleDownGroupName, pair, last);
+   }
+
+   protected ClusterTopologyChangeMessage_V3(final byte type,
+                                          final long uniqueEventID,
+                                          final String nodeID,
+                                          final String backupGroupName,
+                                          final String scaleDownGroupName,
+                                          final Pair<TransportConfiguration, TransportConfiguration> pair,
+                                          final boolean last) {
+      super(type);
 
       this.nodeID = nodeID;
 
@@ -51,6 +61,10 @@ public class ClusterTopologyChangeMessage_V3 extends ClusterTopologyChangeMessag
       super(CLUSTER_TOPOLOGY_V3);
    }
 
+   public ClusterTopologyChangeMessage_V3(byte type) {
+      super(type);
+   }
+
    public String getScaleDownGroupName() {
       return scaleDownGroupName;
    }
@@ -76,7 +90,16 @@ public class ClusterTopologyChangeMessage_V3 extends ClusterTopologyChangeMessag
    }
 
    @Override
+   protected String getParentString() {
+      return toString(false);
+   }
+
+   @Override
    public String toString() {
+      return toString(true);
+   }
+
+   private String toString(boolean closed) {
       StringBuffer buff = new StringBuffer(getParentString());
       buff.append(", exit=" + exit);
       buff.append(", last=" + last);
@@ -85,7 +108,9 @@ public class ClusterTopologyChangeMessage_V3 extends ClusterTopologyChangeMessag
       buff.append(", backupGroupName=" + backupGroupName);
       buff.append(", uniqueEventID=" + uniqueEventID);
       buff.append(", scaleDownGroupName=" + scaleDownGroupName);
-      buff.append("]");
+      if (closed) {
+         buff.append("]");
+      }
       return buff.toString();
    }
 
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ClusterTopologyChangeMessage_V3.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ClusterTopologyChangeMessage_V4.java
similarity index 53%
copy from artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ClusterTopologyChangeMessage_V3.java
copy to artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ClusterTopologyChangeMessage_V4.java
index d371eb5..fde8cd8 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ClusterTopologyChangeMessage_V3.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/ClusterTopologyChangeMessage_V4.java
@@ -20,73 +20,58 @@ import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
 import org.apache.activemq.artemis.api.core.Pair;
 import org.apache.activemq.artemis.api.core.TransportConfiguration;
 
-public class ClusterTopologyChangeMessage_V3 extends ClusterTopologyChangeMessage_V2 {
+public class ClusterTopologyChangeMessage_V4 extends ClusterTopologyChangeMessage_V3 {
 
-   private String scaleDownGroupName;
+   private int serverVersion;
 
-   public ClusterTopologyChangeMessage_V3(final long uniqueEventID,
+   public ClusterTopologyChangeMessage_V4(final long uniqueEventID,
                                           final String nodeID,
                                           final String backupGroupName,
                                           final String scaleDownGroupName,
                                           final Pair<TransportConfiguration, TransportConfiguration> pair,
-                                          final boolean last) {
-      super(CLUSTER_TOPOLOGY_V3);
+                                          final boolean last,
+                                          final int serverVersion) {
+      super(CLUSTER_TOPOLOGY_V4, uniqueEventID, nodeID, backupGroupName, scaleDownGroupName, pair, last);
 
-      this.nodeID = nodeID;
-
-      this.pair = pair;
-
-      this.last = last;
-
-      this.exit = false;
-
-      this.uniqueEventID = uniqueEventID;
-
-      this.backupGroupName = backupGroupName;
-
-      this.scaleDownGroupName = scaleDownGroupName;
+      this.serverVersion = serverVersion;
    }
 
-   public ClusterTopologyChangeMessage_V3() {
-      super(CLUSTER_TOPOLOGY_V3);
+   public ClusterTopologyChangeMessage_V4() {
+      super(CLUSTER_TOPOLOGY_V4);
    }
 
-   public String getScaleDownGroupName() {
-      return scaleDownGroupName;
+   public int getServerVersion() {
+      return serverVersion;
    }
 
    @Override
    public void encodeRest(final ActiveMQBuffer buffer) {
       super.encodeRest(buffer);
-      buffer.writeNullableString(scaleDownGroupName);
+
+      buffer.writeInt(serverVersion);
    }
 
    @Override
    public void decodeRest(final ActiveMQBuffer buffer) {
       super.decodeRest(buffer);
-      scaleDownGroupName = buffer.readNullableString();
+
+      serverVersion = buffer.readInt();
    }
 
    @Override
    public int hashCode() {
       final int prime = 31;
       int result = super.hashCode();
-      result = prime * result + ((scaleDownGroupName == null) ? 0 : scaleDownGroupName.hashCode());
+      result = prime * result + serverVersion;
       return result;
    }
 
    @Override
    public String toString() {
-      StringBuffer buff = new StringBuffer(getParentString());
-      buff.append(", exit=" + exit);
-      buff.append(", last=" + last);
-      buff.append(", nodeID=" + nodeID);
-      buff.append(", pair=" + pair);
-      buff.append(", backupGroupName=" + backupGroupName);
-      buff.append(", uniqueEventID=" + uniqueEventID);
-      buff.append(", scaleDownGroupName=" + scaleDownGroupName);
-      buff.append("]");
-      return buff.toString();
+      StringBuffer buf = new StringBuffer(getParentString());
+      buf.append(", clientVersion=" + serverVersion);
+      buf.append("]");
+      return buf.toString();
    }
 
    @Override
@@ -97,17 +82,10 @@ public class ClusterTopologyChangeMessage_V3 extends ClusterTopologyChangeMessag
       if (!super.equals(obj)) {
          return false;
       }
-      if (!(obj instanceof ClusterTopologyChangeMessage_V3)) {
-         return false;
-      }
-      ClusterTopologyChangeMessage_V3 other = (ClusterTopologyChangeMessage_V3) obj;
-      if (scaleDownGroupName == null) {
-         if (other.scaleDownGroupName != null) {
-            return false;
-         }
-      } else if (!scaleDownGroupName.equals(other.scaleDownGroupName)) {
+      if (!(obj instanceof ClusterTopologyChangeMessage_V4)) {
          return false;
       }
-      return true;
+      ClusterTopologyChangeMessage_V4 other = (ClusterTopologyChangeMessage_V4) obj;
+      return serverVersion == other.serverVersion;
    }
 }
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CreateSessionMessage.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CreateSessionMessage.java
index 1315249..7f82c83 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CreateSessionMessage.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CreateSessionMessage.java
@@ -57,7 +57,23 @@ public class CreateSessionMessage extends PacketImpl {
                                final boolean preAcknowledge,
                                final int windowSize,
                                final String defaultAddress) {
-      super(CREATESESSION);
+      this(CREATESESSION, name, sessionChannelID, version, username, password, minLargeMessageSize, xa, autoCommitSends, autoCommitAcks, preAcknowledge, windowSize, defaultAddress);
+   }
+
+   protected CreateSessionMessage(final byte type,
+                               final String name,
+                               final long sessionChannelID,
+                               final int version,
+                               final String username,
+                               final String password,
+                               final int minLargeMessageSize,
+                               final boolean xa,
+                               final boolean autoCommitSends,
+                               final boolean autoCommitAcks,
+                               final boolean preAcknowledge,
+                               final int windowSize,
+                               final String defaultAddress) {
+      super(type);
 
       this.name = name;
 
@@ -88,6 +104,10 @@ public class CreateSessionMessage extends PacketImpl {
       super(CREATESESSION);
    }
 
+   protected CreateSessionMessage(final byte type) {
+      super(type);
+   }
+
    // Public --------------------------------------------------------
 
    public String getName() {
@@ -195,8 +215,17 @@ public class CreateSessionMessage extends PacketImpl {
    }
 
    @Override
+   protected String getParentString() {
+      return toString(false);
+   }
+
+   @Override
    public String toString() {
-      StringBuffer buff = new StringBuffer(getParentString());
+      return toString(true);
+   }
+
+   private String toString(boolean closed) {
+      StringBuffer buff = new StringBuffer(super.getParentString());
       buff.append(", autoCommitAcks=" + autoCommitAcks);
       buff.append(", autoCommitSends=" + autoCommitSends);
       buff.append(", defaultAddress=" + defaultAddress);
@@ -209,7 +238,9 @@ public class CreateSessionMessage extends PacketImpl {
       buff.append(", version=" + version);
       buff.append(", windowSize=" + windowSize);
       buff.append(", xa=" + xa);
-      buff.append("]");
+      if (closed) {
+         buff.append("]");
+      }
       return buff.toString();
    }
 
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CreateSessionMessage_V2.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CreateSessionMessage_V2.java
new file mode 100644
index 0000000..be2f8d3
--- /dev/null
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/CreateSessionMessage_V2.java
@@ -0,0 +1,103 @@
+/*
+ * 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;
+
+public class CreateSessionMessage_V2 extends CreateSessionMessage {
+
+   private String clientID = null;
+
+   public CreateSessionMessage_V2(final String name,
+                               final long sessionChannelID,
+                               final int version,
+                               final String username,
+                               final String password,
+                               final int minLargeMessageSize,
+                               final boolean xa,
+                               final boolean autoCommitSends,
+                               final boolean autoCommitAcks,
+                               final boolean preAcknowledge,
+                               final int windowSize,
+                               final String defaultAddress,
+                               final String clientID) {
+      super(CREATESESSION_V2, name, sessionChannelID, version, username, password, minLargeMessageSize, xa, autoCommitSends, autoCommitAcks, preAcknowledge, windowSize, defaultAddress);
+
+      this.clientID = clientID;
+   }
+
+   public CreateSessionMessage_V2() {
+      super(CREATESESSION_V2);
+   }
+
+   // Public --------------------------------------------------------
+
+
+   public String getClientID() {
+      return clientID;
+   }
+
+   @Override
+   public void encodeRest(final ActiveMQBuffer buffer) {
+      super.encodeRest(buffer);
+
+      buffer.writeNullableString(clientID);
+   }
+
+   @Override
+   public void decodeRest(final ActiveMQBuffer buffer) {
+      super.decodeRest(buffer);
+
+      clientID = buffer.readNullableString();
+   }
+
+   @Override
+   public String toString() {
+      StringBuffer buf = new StringBuffer(getParentString());
+      buf.append(", metadata=" + clientID);
+      buf.append("]");
+      return buf.toString();
+   }
+
+   @Override
+   public int hashCode() {
+      final int prime = 31;
+      int result = super.hashCode();
+      result = prime * result + ((clientID == null) ? 0 : clientID.hashCode());
+      return result;
+   }
+
+   @Override
+   public boolean equals(Object obj) {
+      if (this == obj) {
+         return true;
+      }
+      if (!super.equals(obj)) {
+         return false;
+      }
+      if (!(obj instanceof CreateSessionMessage_V2)) {
+         return false;
+      }
+      CreateSessionMessage_V2 other = (CreateSessionMessage_V2) obj;
+      if (clientID == null) {
+         if (other.clientID != null)
+            return false;
+      } else if (!clientID.equals(other.clientID))
+         return false;
+      return true;
+   }
+}
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/DisconnectMessage_V3.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/DisconnectMessage_V3.java
new file mode 100644
index 0000000..671e1aa
--- /dev/null
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/wireformat/DisconnectMessage_V3.java
@@ -0,0 +1,137 @@
+/*
+ * 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.api.core.DisconnectReason;
+import org.apache.activemq.artemis.api.core.SimpleString;
+import org.apache.activemq.artemis.api.core.TransportConfiguration;
+
+public class DisconnectMessage_V3 extends DisconnectMessage {
+
+   private DisconnectReason reason;
+   private SimpleString targetNodeID;
+   private TransportConfiguration targetConnector;
+
+   public DisconnectMessage_V3(final SimpleString nodeID,
+                               final DisconnectReason reason,
+                               final SimpleString targetNodeID,
+                               final TransportConfiguration targetConnector) {
+      super(DISCONNECT_V3);
+
+      this.nodeID = nodeID;
+
+      this.reason = reason;
+
+      this.targetNodeID = targetNodeID;
+
+      this.targetConnector = targetConnector;
+   }
+
+   public DisconnectMessage_V3() {
+      super(DISCONNECT_V3);
+   }
+
+   // Public --------------------------------------------------------
+
+   public DisconnectReason getReason() {
+      return reason;
+   }
+
+   public SimpleString getTargetNodeID() {
+      return targetNodeID;
+   }
+
+   public TransportConfiguration getTargetConnector() {
+      return targetConnector;
+   }
+
+   @Override
+   public void encodeRest(final ActiveMQBuffer buffer) {
+      super.encodeRest(buffer);
+      buffer.writeByte(reason == null ? -1 : reason.getType());
+      buffer.writeNullableSimpleString(targetNodeID);
+      if (targetConnector != null) {
+         buffer.writeBoolean(true);
+         targetConnector.encode(buffer);
+      } else {
+         buffer.writeBoolean(false);
+      }
+   }
+
+   @Override
+   public void decodeRest(final ActiveMQBuffer buffer) {
+      super.decodeRest(buffer);
+      reason = DisconnectReason.getType(buffer.readByte());
+      targetNodeID = buffer.readNullableSimpleString();
+      boolean hasTargetConnector = buffer.readBoolean();
+      if (hasTargetConnector) {
+         targetConnector = new TransportConfiguration();
+         targetConnector.decode(buffer);
+      } else {
+         targetConnector = null;
+      }
+   }
+
+   @Override
+   public String toString() {
+      StringBuffer buf = new StringBuffer(getParentString());
+      buf.append(", nodeID=" + nodeID);
+      buf.append(", reason=" + reason);
+      buf.append(", targetNodeID=" + targetNodeID);
+      buf.append(", targetConnector=" + targetConnector);
+      buf.append("]");
+      return buf.toString();
+   }
+
+   @Override
+   public int hashCode() {
+      final int prime = 31;
+      int result = super.hashCode();
+      result = prime * result + (reason.getType());
+      result = prime * result + ((targetNodeID == null) ? 0 : targetNodeID.hashCode());
+      result = prime * result + ((targetConnector == null) ? 0 : targetConnector.hashCode());
+      return result;
+   }
+
+   @Override
+   public boolean equals(Object obj) {
+      if (this == obj) {
+         return true;
+      }
+      if (!super.equals(obj)) {
+         return false;
+      }
+      if (!(obj instanceof DisconnectMessage_V3)) {
+         return false;
+      }
+      DisconnectMessage_V3 other = (DisconnectMessage_V3) obj;
+      if (reason == null) {
+         if (other.reason != null)
+            return false;
+      } else if (!reason.equals(other.reason))
+         return false;
+      if (targetNodeID == null) {
+         if (other.targetNodeID != null) {
+            return false;
+         }
+      } else if (!targetNodeID.equals(other.targetNodeID)) {
+         return false;
+      }
+      return true;
+   }
+}
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/TransportConstants.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/TransportConstants.java
index 3710087..37a4e80 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/TransportConstants.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/TransportConstants.java
@@ -361,6 +361,10 @@ public class TransportConstants {
 
    public static final boolean DEFAULT_PROXY_REMOTE_DNS = false;
 
+   public static final String REDIRECT_TO = "redirect-to";
+
+   public static final String DEFAULT_REDIRECT_TO = null;
+
    private static int parseDefaultVariable(String variableName, int defaultValue) {
       try {
          String variable = System.getProperty(TransportConstants.class.getName() + "." + variableName);
@@ -437,6 +441,7 @@ public class TransportConstants {
       allowableAcceptorKeys.add(TransportConstants.QUIET_PERIOD);
       allowableAcceptorKeys.add(TransportConstants.DISABLE_STOMP_SERVER_HEADER);
       allowableAcceptorKeys.add(TransportConstants.AUTO_START);
+      allowableAcceptorKeys.add(TransportConstants.REDIRECT_TO);
 
       ALLOWABLE_ACCEPTOR_KEYS = Collections.unmodifiableSet(allowableAcceptorKeys);
 
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 f9f4fa5..81f378e 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
@@ -21,6 +21,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.DisconnectReason;
 import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.api.core.TransportConfiguration;
 import org.apache.activemq.artemis.core.remoting.CloseListener;
@@ -184,6 +185,13 @@ public interface RemotingConnection extends BufferHandler {
    void disconnect(String scaleDownNodeID, boolean criticalError);
 
    /**
+    * Disconnect the connection, closing all channels
+    */
+   default void disconnect(DisconnectReason reason, String targetNodeID, TransportConfiguration targetConnector) {
+      disconnect(reason.isScaleDown() ? targetNodeID : null, reason.isCriticalError());
+   }
+
+   /**
     * returns true if any data has been received since the last time this method was called.
     *
     * @return true if data has been received.
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 37e699e..764c284 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
@@ -69,7 +69,8 @@ public interface ClientProtocolManager {
                                        boolean autoCommitAcks,
                                        boolean preAcknowledge,
                                        int minLargeMessageSize,
-                                       int confirmationWindowSize) throws ActiveMQException;
+                                       int confirmationWindowSize,
+                                       String clientID) throws ActiveMQException;
 
    boolean cleanupBeforeFailover(ActiveMQException cause);
 
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/Connection.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/Connection.java
index 28584ae..0067c1e 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/Connection.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/Connection.java
@@ -172,4 +172,12 @@ public interface Connection {
    //returns true if one of the configs points to the same
    //node as this connection does.
    boolean isSameTarget(TransportConfiguration... configs);
+
+   default String getSNIHostName() {
+      return null;
+   }
+
+   default String getRedirectTo() {
+      return null;
+   }
 }
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/TopologyResponseHandler.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/TopologyResponseHandler.java
index 55e202c..c987339 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/TopologyResponseHandler.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/TopologyResponseHandler.java
@@ -16,6 +16,7 @@
  */
 package org.apache.activemq.artemis.spi.core.remoting;
 
+import org.apache.activemq.artemis.api.core.DisconnectReason;
 import org.apache.activemq.artemis.api.core.Pair;
 import org.apache.activemq.artemis.api.core.TransportConfiguration;
 import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
@@ -23,7 +24,7 @@ import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
 public interface TopologyResponseHandler {
 
    // This is sent when the server is telling the client the node is being disconnected
-   void nodeDisconnected(RemotingConnection conn, String nodeID, String scaleDownTargetNodeID);
+   void nodeDisconnected(RemotingConnection conn, String nodeID, DisconnectReason reason, String targetNodeID, TransportConfiguration tagetConnector);
 
    void notifyNodeUp(long uniqueEventID,
                      String backupGroupName,
diff --git a/artemis-core-client/src/main/resources/activemq-version.properties b/artemis-core-client/src/main/resources/activemq-version.properties
index ff65ff9..e8ece76 100644
--- a/artemis-core-client/src/main/resources/activemq-version.properties
+++ b/artemis-core-client/src/main/resources/activemq-version.properties
@@ -20,4 +20,4 @@ activemq.version.minorVersion=${activemq.version.minorVersion}
 activemq.version.microVersion=${activemq.version.microVersion}
 activemq.version.incrementingVersion=${activemq.version.incrementingVersion}
 activemq.version.versionTag=${activemq.version.versionTag}
-activemq.version.compatibleVersionList=121,122,123,124,125,126,127,128,129,130
+activemq.version.compatibleVersionList=121,122,123,124,125,126,127,128,129,130,131
diff --git a/artemis-core-client/src/test/java/org/apache/activemq/artemis/api/core/management/OperationAnnotationTest.java b/artemis-core-client/src/test/java/org/apache/activemq/artemis/api/core/management/OperationAnnotationTest.java
index d59799a..1451f15 100644
--- a/artemis-core-client/src/test/java/org/apache/activemq/artemis/api/core/management/OperationAnnotationTest.java
+++ b/artemis-core-client/src/test/java/org/apache/activemq/artemis/api/core/management/OperationAnnotationTest.java
@@ -42,7 +42,8 @@ public class OperationAnnotationTest {
                                           {DivertControl.class},
                                           {AcceptorControl.class},
                                           {ClusterConnectionControl.class},
-                                          {BroadcastGroupControl.class}});
+                                          {BroadcastGroupControl.class},
+                                          {BrokerBalancerControl.class}});
    }
 
    private Class<?> managementClass;
diff --git a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQConnection.java b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQConnection.java
index b3026ad..61f5f3f 100644
--- a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQConnection.java
+++ b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQConnection.java
@@ -79,14 +79,6 @@ public class ActiveMQConnection extends ActiveMQConnectionForContextImpl impleme
 
    public static final SimpleString CONNECTION_ID_PROPERTY_NAME = MessageUtil.CONNECTION_ID_PROPERTY_NAME;
 
-   /**
-    * Just like {@link ClientSession.AddressQuery#JMS_SESSION_IDENTIFIER_PROPERTY} this is
-    * used to identify the ClientID over JMS Session.
-    * However this is only used when the JMS Session.clientID is set (which is optional).
-    * With this property management tools and the server can identify the jms-client-id used over JMS
-    */
-   public static String JMS_SESSION_CLIENT_ID_PROPERTY = "jms-client-id";
-
    // Static ---------------------------------------------------------------------------------------
 
    // Attributes -----------------------------------------------------------------------------------
@@ -271,7 +263,7 @@ public class ActiveMQConnection extends ActiveMQConnectionForContextImpl impleme
    private void validateClientID(ClientSession validateSession, String clientID)
          throws InvalidClientIDException, ActiveMQException {
       try {
-         validateSession.addUniqueMetaData(JMS_SESSION_CLIENT_ID_PROPERTY, clientID);
+         validateSession.addUniqueMetaData(ClientSession.JMS_SESSION_CLIENT_ID_PROPERTY, clientID);
       } catch (ActiveMQException e) {
          if (e.getType() == ActiveMQExceptionType.DUPLICATE_METADATA) {
             throw new InvalidClientIDException("clientID=" + clientID + " was already set into another connection");
@@ -605,17 +597,17 @@ public class ActiveMQConnection extends ActiveMQConnectionForContextImpl impleme
          boolean isBlockOnAcknowledge = sessionFactory.getServerLocator().isBlockOnAcknowledge();
          int ackBatchSize = sessionFactory.getServerLocator().getAckBatchSize();
          if (acknowledgeMode == Session.SESSION_TRANSACTED) {
-            session = sessionFactory.createSession(username, password, isXA, false, false, sessionFactory.getServerLocator().isPreAcknowledge(), transactionBatchSize);
+            session = sessionFactory.createSession(username, password, isXA, false, false, sessionFactory.getServerLocator().isPreAcknowledge(), transactionBatchSize, clientID);
          } else if (acknowledgeMode == Session.AUTO_ACKNOWLEDGE) {
-            session = sessionFactory.createSession(username, password, isXA, true, true, sessionFactory.getServerLocator().isPreAcknowledge(), 0);
+            session = sessionFactory.createSession(username, password, isXA, true, true, sessionFactory.getServerLocator().isPreAcknowledge(), 0, clientID);
          } else if (acknowledgeMode == Session.DUPS_OK_ACKNOWLEDGE) {
-            session = sessionFactory.createSession(username, password, isXA, true, true, sessionFactory.getServerLocator().isPreAcknowledge(), dupsOKBatchSize);
+            session = sessionFactory.createSession(username, password, isXA, true, true, sessionFactory.getServerLocator().isPreAcknowledge(), dupsOKBatchSize, clientID);
          } else if (acknowledgeMode == Session.CLIENT_ACKNOWLEDGE) {
-            session = sessionFactory.createSession(username, password, isXA, true, false, sessionFactory.getServerLocator().isPreAcknowledge(), isBlockOnAcknowledge ? transactionBatchSize : ackBatchSize);
+            session = sessionFactory.createSession(username, password, isXA, true, false, sessionFactory.getServerLocator().isPreAcknowledge(), isBlockOnAcknowledge ? transactionBatchSize : ackBatchSize, clientID);
          } else if (acknowledgeMode == ActiveMQJMSConstants.INDIVIDUAL_ACKNOWLEDGE) {
-            session = sessionFactory.createSession(username, password, isXA, true, false, false, isBlockOnAcknowledge ? transactionBatchSize : ackBatchSize);
+            session = sessionFactory.createSession(username, password, isXA, true, false, false, isBlockOnAcknowledge ? transactionBatchSize : ackBatchSize, clientID);
          } else if (acknowledgeMode == ActiveMQJMSConstants.PRE_ACKNOWLEDGE) {
-            session = sessionFactory.createSession(username, password, isXA, true, false, true, transactionBatchSize);
+            session = sessionFactory.createSession(username, password, isXA, true, false, true, transactionBatchSize, clientID);
          } else {
             throw new JMSRuntimeException("Invalid ackmode: " + acknowledgeMode);
          }
@@ -636,8 +628,6 @@ public class ActiveMQConnection extends ActiveMQConnectionForContextImpl impleme
             session.start();
          }
 
-         this.addSessionMetaData(session);
-
          return jbs;
       } catch (ActiveMQException e) {
          throw JMSExceptionHelper.convertFromActiveMQException(e);
@@ -681,13 +671,13 @@ public class ActiveMQConnection extends ActiveMQConnectionForContextImpl impleme
 
    public void authorize(boolean validateClientId) throws JMSException {
       try {
-         initialSession = sessionFactory.createSession(username, password, false, false, false, false, 0);
+         initialSession = sessionFactory.createSession(username, password, false, false, false, false, 0, clientID);
 
          if (clientID != null) {
             if (validateClientId) {
                validateClientID(initialSession, clientID);
             } else {
-               initialSession.addMetaData(JMS_SESSION_CLIENT_ID_PROPERTY, clientID);
+               initialSession.addMetaData(ClientSession.JMS_SESSION_CLIENT_ID_PROPERTY, clientID);
             }
          }
 
@@ -703,7 +693,7 @@ public class ActiveMQConnection extends ActiveMQConnectionForContextImpl impleme
    private void addSessionMetaData(ClientSession session) throws ActiveMQException {
       session.addMetaData(ClientSession.JMS_SESSION_IDENTIFIER_PROPERTY, "");
       if (clientID != null) {
-         session.addMetaData(JMS_SESSION_CLIENT_ID_PROPERTY, clientID);
+         session.addMetaData(ClientSession.JMS_SESSION_CLIENT_ID_PROPERTY, clientID);
       }
    }
 
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/ProtonProtocolManager.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/ProtonProtocolManager.java
index 6168c31..30b4b51 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/ProtonProtocolManager.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/ProtonProtocolManager.java
@@ -36,6 +36,7 @@ import org.apache.activemq.artemis.protocol.amqp.client.ProtonClientProtocolMana
 import org.apache.activemq.artemis.protocol.amqp.connect.mirror.ReferenceNodeStore;
 import org.apache.activemq.artemis.protocol.amqp.proton.AMQPConnectionContext;
 import org.apache.activemq.artemis.protocol.amqp.proton.AMQPConstants;
+import org.apache.activemq.artemis.protocol.amqp.proton.AMQPRedirectHandler;
 import org.apache.activemq.artemis.protocol.amqp.proton.AmqpSupport;
 import org.apache.activemq.artemis.protocol.amqp.sasl.ClientSASLFactory;
 import org.apache.activemq.artemis.protocol.amqp.sasl.MechanismFinder;
@@ -53,7 +54,7 @@ import org.jboss.logging.Logger;
 /**
  * A proton protocol manager, basically reads the Proton Input and maps proton resources to ActiveMQ Artemis resources
  */
-public class ProtonProtocolManager extends AbstractProtocolManager<AMQPMessage, AmqpInterceptor, ActiveMQProtonRemotingConnection> implements NotificationListener {
+public class ProtonProtocolManager extends AbstractProtocolManager<AMQPMessage, AmqpInterceptor, ActiveMQProtonRemotingConnection, AMQPRedirectHandler> implements NotificationListener {
 
    private static final Logger logger = Logger.getLogger(ProtonProtocolManager.class);
 
@@ -105,6 +106,7 @@ public class ProtonProtocolManager extends AbstractProtocolManager<AMQPMessage,
 
    private boolean directDeliver = true;
 
+   private final AMQPRedirectHandler redirectHandler;
 
    /*
    * used when you want to treat senders as a subscription on an address rather than consuming from the actual queue for
@@ -118,6 +120,7 @@ public class ProtonProtocolManager extends AbstractProtocolManager<AMQPMessage,
       this.factory = factory;
       this.server = server;
       this.updateInterceptors(incomingInterceptors, outgoingInterceptors);
+      redirectHandler = new AMQPRedirectHandler(server);
    }
 
    public synchronized ReferenceNodeStore getReferenceIDSupplier() {
@@ -349,6 +352,11 @@ public class ProtonProtocolManager extends AbstractProtocolManager<AMQPMessage,
       return prefixes;
    }
 
+   @Override
+   public AMQPRedirectHandler getRedirectHandler() {
+      return redirectHandler;
+   }
+
    public String invokeIncoming(AMQPMessage message, ActiveMQProtonRemotingConnection connection) {
       return super.invokeInterceptors(this.incomingInterceptors, message, connection);
    }
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 43ae226..8fb23ef 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
@@ -95,7 +95,7 @@ public class ProtonClientProtocolManager extends ProtonProtocolManager implement
    }
 
    @Override
-   public SessionContext createSessionContext(String name, String username, String password, boolean xa, boolean autoCommitSends, boolean autoCommitAcks, boolean preAcknowledge, int minLargeMessageSize, int confirmationWindowSize) throws ActiveMQException {
+   public SessionContext createSessionContext(String name, String username, String password, boolean xa, boolean autoCommitSends, boolean autoCommitAcks, boolean preAcknowledge, int minLargeMessageSize, int confirmationWindowSize, String clientID) 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 aad8884..41fc2a4 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
@@ -215,7 +215,8 @@ public class AMQPBrokerConnectionManager implements ActiveMQComponent, ClientCon
                                                  boolean autoCommitAcks,
                                                  boolean preAcknowledge,
                                                  int minLargeMessageSize,
-                                                 int confirmationWindowSize) throws ActiveMQException {
+                                                 int confirmationWindowSize,
+                                                 String clientID) throws ActiveMQException {
          return null;
       }
 
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPConnectionContext.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPConnectionContext.java
index 998ebf5..17d6b8f 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPConnectionContext.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPConnectionContext.java
@@ -525,7 +525,9 @@ public class AMQPConnectionContext extends ProtonInitializable implements EventH
       } catch (Exception e) {
          log.error("Error init connection", e);
       }
-      if (!validateConnection(connection)) {
+
+      if ((connectionCallback.getTransportConnection().getRedirectTo() != null && protocolManager.getRedirectHandler()
+         .redirect(this, connection)) || !validateConnection(connection)) {
          connection.close();
       } else {
          connection.setContext(AMQPConnectionContext.this);
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPRedirectContext.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPRedirectContext.java
new file mode 100644
index 0000000..9c24bd2
--- /dev/null
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPRedirectContext.java
@@ -0,0 +1,37 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.activemq.artemis.protocol.amqp.proton;
+
+import org.apache.activemq.artemis.core.server.balancing.RedirectContext;
+import org.apache.qpid.proton.engine.Connection;
+
+public class AMQPRedirectContext extends RedirectContext {
+   private final Connection protonConnection;
+
+
+   public Connection getProtonConnection() {
+      return protonConnection;
+   }
+
+
+   public AMQPRedirectContext(AMQPConnectionContext connectionContext, Connection protonConnection) {
+      super(connectionContext.getConnectionCallback().getProtonConnectionDelegate(), connectionContext.getRemoteContainer(),
+         connectionContext.getSASLResult() != null ? connectionContext.getSASLResult().getUser() : null);
+      this.protonConnection = protonConnection;
+   }
+}
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPRedirectHandler.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPRedirectHandler.java
new file mode 100644
index 0000000..d852a3a
--- /dev/null
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPRedirectHandler.java
@@ -0,0 +1,64 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.activemq.artemis.protocol.amqp.proton;
+
+import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants;
+import org.apache.activemq.artemis.core.server.ActiveMQServer;
+import org.apache.activemq.artemis.core.server.balancing.RedirectHandler;
+import org.apache.activemq.artemis.utils.ConfigurationHelper;
+import org.apache.qpid.proton.amqp.transport.ConnectionError;
+import org.apache.qpid.proton.amqp.transport.ErrorCondition;
+import org.apache.qpid.proton.engine.Connection;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class AMQPRedirectHandler extends RedirectHandler<AMQPRedirectContext> {
+
+   public AMQPRedirectHandler(ActiveMQServer server) {
+      super(server);
+   }
+
+
+   public boolean redirect(AMQPConnectionContext connectionContext, Connection protonConnection) throws Exception {
+      return redirect(new AMQPRedirectContext(connectionContext, protonConnection));
+   }
+
+   @Override
+   protected void cannotRedirect(AMQPRedirectContext context) throws Exception {
+      ErrorCondition error = new ErrorCondition();
+      error.setCondition(ConnectionError.CONNECTION_FORCED);
+      error.setDescription(String.format("Broker balancer %s is not ready to redirect", context.getConnection().getTransportConnection().getRedirectTo()));
+      context.getProtonConnection().setCondition(error);
+   }
+
+   @Override
+   protected void redirectTo(AMQPRedirectContext context) throws Exception {
+      String host = ConfigurationHelper.getStringProperty(TransportConstants.HOST_PROP_NAME, TransportConstants.DEFAULT_HOST, context.getTarget().getConnector().getParams());
+      int port = ConfigurationHelper.getIntProperty(TransportConstants.PORT_PROP_NAME, TransportConstants.DEFAULT_PORT, context.getTarget().getConnector().getParams());
+
+      ErrorCondition error = new ErrorCondition();
+      error.setCondition(ConnectionError.REDIRECT);
+      error.setDescription(String.format("Connection redirected to %s:%d by broker balancer %s", host, port, context.getConnection().getTransportConnection().getRedirectTo()));
+      Map info = new HashMap();
+      info.put(AmqpSupport.NETWORK_HOST, host);
+      info.put(AmqpSupport.PORT, port);
+      error.setInfo(info);
+      context.getProtonConnection().setCondition(error);
+   }
+}
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/AnonymousServerSASLFactory.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/AnonymousServerSASLFactory.java
index 82d3ec1..485b0ef 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/AnonymousServerSASLFactory.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/AnonymousServerSASLFactory.java
@@ -18,6 +18,7 @@ package org.apache.activemq.artemis.protocol.amqp.sasl;
 
 import org.apache.activemq.artemis.core.server.ActiveMQServer;
 import org.apache.activemq.artemis.protocol.amqp.broker.AmqpInterceptor;
+import org.apache.activemq.artemis.protocol.amqp.proton.AMQPRedirectHandler;
 import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager;
 import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
 import org.apache.activemq.artemis.spi.core.remoting.Connection;
@@ -30,7 +31,7 @@ public class AnonymousServerSASLFactory implements ServerSASLFactory {
    }
 
    @Override
-   public ServerSASL create(ActiveMQServer server, ProtocolManager<AmqpInterceptor> manager, Connection connection,
+   public ServerSASL create(ActiveMQServer server, ProtocolManager<AmqpInterceptor, AMQPRedirectHandler> manager, Connection connection,
                             RemotingConnection remotingConnection) {
       return new AnonymousServerSASL();
    }
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ExternalServerSASLFactory.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ExternalServerSASLFactory.java
index e9087be..9bcaf05 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ExternalServerSASLFactory.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ExternalServerSASLFactory.java
@@ -21,6 +21,7 @@ import java.security.Principal;
 import org.apache.activemq.artemis.core.remoting.CertificateUtil;
 import org.apache.activemq.artemis.core.server.ActiveMQServer;
 import org.apache.activemq.artemis.protocol.amqp.broker.AmqpInterceptor;
+import org.apache.activemq.artemis.protocol.amqp.proton.AMQPRedirectHandler;
 import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager;
 import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
 import org.apache.activemq.artemis.spi.core.remoting.Connection;
@@ -39,7 +40,7 @@ public class ExternalServerSASLFactory implements ServerSASLFactory {
    }
 
    @Override
-   public ServerSASL create(ActiveMQServer server, ProtocolManager<AmqpInterceptor> manager, Connection connection,
+   public ServerSASL create(ActiveMQServer server, ProtocolManager<AmqpInterceptor, AMQPRedirectHandler> manager, Connection connection,
                             RemotingConnection remotingConnection) {
       // validate ssl cert present
       Principal principal = CertificateUtil.getPeerPrincipalFromConnection(remotingConnection);
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPIServerSASLFactory.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPIServerSASLFactory.java
index e31632a..098668e 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPIServerSASLFactory.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPIServerSASLFactory.java
@@ -19,6 +19,7 @@ package org.apache.activemq.artemis.protocol.amqp.sasl;
 import org.apache.activemq.artemis.core.server.ActiveMQServer;
 import org.apache.activemq.artemis.protocol.amqp.broker.AmqpInterceptor;
 import org.apache.activemq.artemis.protocol.amqp.broker.ProtonProtocolManager;
+import org.apache.activemq.artemis.protocol.amqp.proton.AMQPRedirectHandler;
 import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager;
 import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
 import org.apache.activemq.artemis.spi.core.remoting.Connection;
@@ -34,7 +35,7 @@ public class GSSAPIServerSASLFactory implements ServerSASLFactory {
    }
 
    @Override
-   public ServerSASL create(ActiveMQServer server, ProtocolManager<AmqpInterceptor> manager, Connection connection,
+   public ServerSASL create(ActiveMQServer server, ProtocolManager<AmqpInterceptor, AMQPRedirectHandler> manager, Connection connection,
                             RemotingConnection remotingConnection) {
       if (manager instanceof ProtonProtocolManager) {
          GSSAPIServerSASL gssapiServerSASL = new GSSAPIServerSASL();
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/PlainServerSASLFactory.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/PlainServerSASLFactory.java
index 5a88a82..2596c82 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/PlainServerSASLFactory.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/PlainServerSASLFactory.java
@@ -18,6 +18,7 @@ package org.apache.activemq.artemis.protocol.amqp.sasl;
 
 import org.apache.activemq.artemis.core.server.ActiveMQServer;
 import org.apache.activemq.artemis.protocol.amqp.broker.AmqpInterceptor;
+import org.apache.activemq.artemis.protocol.amqp.proton.AMQPRedirectHandler;
 import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager;
 import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
 import org.apache.activemq.artemis.spi.core.remoting.Connection;
@@ -30,7 +31,7 @@ public class PlainServerSASLFactory implements ServerSASLFactory {
    }
 
    @Override
-   public ServerSASL create(ActiveMQServer server, ProtocolManager<AmqpInterceptor> manager, Connection connection,
+   public ServerSASL create(ActiveMQServer server, ProtocolManager<AmqpInterceptor, AMQPRedirectHandler> manager, Connection connection,
                             RemotingConnection remotingConnection) {
       return new PlainSASL(server.getSecurityStore(), manager.getSecurityDomain(), connection.getProtocolConnection());
    }
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ServerSASLFactory.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ServerSASLFactory.java
index 9831652..ded4b27 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ServerSASLFactory.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ServerSASLFactory.java
@@ -18,6 +18,7 @@ package org.apache.activemq.artemis.protocol.amqp.sasl;
 
 import org.apache.activemq.artemis.core.server.ActiveMQServer;
 import org.apache.activemq.artemis.protocol.amqp.broker.AmqpInterceptor;
+import org.apache.activemq.artemis.protocol.amqp.proton.AMQPRedirectHandler;
 import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager;
 import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
 import org.apache.activemq.artemis.spi.core.remoting.Connection;
@@ -40,7 +41,7 @@ public interface ServerSASLFactory {
     * @param remotingConnection
     * @return a new instance of {@link ServerSASL} that implements the provided mechanism
     */
-   ServerSASL create(ActiveMQServer server, ProtocolManager<AmqpInterceptor> manager, Connection connection,
+   ServerSASL create(ActiveMQServer server, ProtocolManager<AmqpInterceptor, AMQPRedirectHandler> manager, Connection connection,
                      RemotingConnection remotingConnection);
 
    /**
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/scram/SCRAMServerSASLFactory.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/scram/SCRAMServerSASLFactory.java
index 67ec428..3719005 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/scram/SCRAMServerSASLFactory.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/scram/SCRAMServerSASLFactory.java
@@ -31,6 +31,7 @@ import javax.security.auth.login.LoginException;
 import org.apache.activemq.artemis.core.server.ActiveMQServer;
 import org.apache.activemq.artemis.protocol.amqp.broker.AmqpInterceptor;
 import org.apache.activemq.artemis.protocol.amqp.broker.ProtonProtocolManager;
+import org.apache.activemq.artemis.protocol.amqp.proton.AMQPRedirectHandler;
 import org.apache.activemq.artemis.protocol.amqp.sasl.ServerSASL;
 import org.apache.activemq.artemis.protocol.amqp.sasl.ServerSASLFactory;
 import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager;
@@ -67,7 +68,7 @@ public abstract class SCRAMServerSASLFactory implements ServerSASLFactory {
    }
 
    @Override
-   public ServerSASL create(ActiveMQServer server, ProtocolManager<AmqpInterceptor> manager, Connection connection,
+   public ServerSASL create(ActiveMQServer server, ProtocolManager<AmqpInterceptor, AMQPRedirectHandler> manager, Connection connection,
                             RemotingConnection remotingConnection) {
       try {
          if (manager instanceof ProtonProtocolManager) {
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 2730383..84498d9 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
@@ -23,11 +23,9 @@ import org.apache.activemq.artemis.core.protocol.core.Channel;
 import org.apache.activemq.artemis.core.protocol.core.Packet;
 import org.apache.activemq.artemis.core.protocol.core.impl.ActiveMQClientProtocolManager;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage;
-import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSessionMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSessionResponseMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.SubscribeClusterTopologyUpdatesMessageV2;
 import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory;
-import org.apache.activemq.artemis.core.version.Version;
 import org.apache.activemq.artemis.spi.core.remoting.Connection;
 import org.apache.activemq.artemis.spi.core.remoting.SessionContext;
 
@@ -49,7 +47,7 @@ public class HornetQClientProtocolManager extends ActiveMQClientProtocolManager
    }
 
    @Override
-   protected Packet newCreateSessionPacket(Version clientVersion,
+   protected Packet newCreateSessionPacket(int clientVersion,
                                            String name,
                                            String username,
                                            String password,
@@ -59,8 +57,9 @@ public class HornetQClientProtocolManager extends ActiveMQClientProtocolManager
                                            boolean preAcknowledge,
                                            int minLargeMessageSize,
                                            int confirmationWindowSize,
-                                           long sessionChannelID) {
-      return new CreateSessionMessage(name, sessionChannelID, VERSION_PLAYED, username, password, minLargeMessageSize, xa, autoCommitSends, autoCommitAcks, preAcknowledge, confirmationWindowSize, null);
+                                           long sessionChannelID,
+                                           String clientID) {
+      return super.newCreateSessionPacket(VERSION_PLAYED, name, username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, minLargeMessageSize, confirmationWindowSize, sessionChannelID, clientID);
    }
 
    @Override
diff --git a/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTProtocolHandler.java b/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTProtocolHandler.java
index d34ade5..82bf221 100644
--- a/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTProtocolHandler.java
+++ b/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTProtocolHandler.java
@@ -28,6 +28,7 @@ import io.netty.handler.codec.mqtt.MqttFixedHeader;
 import io.netty.handler.codec.mqtt.MqttMessage;
 import io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader;
 import io.netty.handler.codec.mqtt.MqttMessageType;
+import io.netty.handler.codec.mqtt.MqttProperties;
 import io.netty.handler.codec.mqtt.MqttPubAckMessage;
 import io.netty.handler.codec.mqtt.MqttPublishMessage;
 import io.netty.handler.codec.mqtt.MqttPublishVariableHeader;
@@ -176,10 +177,13 @@ public class MQTTProtocolHandler extends ChannelInboundHandlerAdapter {
     * @param connect
     */
    void handleConnect(MqttConnectMessage connect) throws Exception {
-      connectionEntry.ttl = connect.variableHeader().keepAliveTimeSeconds() * 1500L;
+      if (connection.getTransportConnection().getRedirectTo() == null ||
+         !protocolManager.getRedirectHandler().redirect(connection, session, connect)) {
+         connectionEntry.ttl = connect.variableHeader().keepAliveTimeSeconds() * 1500L;
 
-      String clientId = connect.payload().clientIdentifier();
-      session.getConnectionManager().connect(clientId, connect.payload().userName(), connect.payload().passwordInBytes(), connect.variableHeader().isWillFlag(), connect.payload().willMessageInBytes(), connect.payload().willTopic(), connect.variableHeader().isWillRetain(), connect.variableHeader().willQos(), connect.variableHeader().isCleanSession());
+         String clientId = connect.payload().clientIdentifier();
+         session.getConnectionManager().connect(clientId, connect.payload().userName(), connect.payload().passwordInBytes(), connect.variableHeader().isWillFlag(), connect.payload().willMessageInBytes(), connect.payload().willTopic(), connect.variableHeader().isWillRetain(), connect.variableHeader().willQos(), connect.variableHeader().isCleanSession());
+      }
    }
 
    void disconnect(boolean error) {
@@ -187,8 +191,12 @@ public class MQTTProtocolHandler extends ChannelInboundHandlerAdapter {
    }
 
    void sendConnack(MqttConnectReturnCode returnCode) {
+      sendConnack(returnCode, MqttProperties.NO_PROPERTIES);
+   }
+
+   void sendConnack(MqttConnectReturnCode returnCode, MqttProperties properties) {
       MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 0);
-      MqttConnAckVariableHeader varHeader = new MqttConnAckVariableHeader(returnCode, true);
+      MqttConnAckVariableHeader varHeader = new MqttConnAckVariableHeader(returnCode, true, properties);
       MqttConnAckMessage message = new MqttConnAckMessage(fixedHeader, varHeader);
       sendToClient(message);
    }
diff --git a/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTProtocolManager.java b/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTProtocolManager.java
index d0f5d12..6513247 100644
--- a/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTProtocolManager.java
+++ b/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTProtocolManager.java
@@ -48,7 +48,7 @@ import org.apache.activemq.artemis.utils.collections.TypedProperties;
 /**
  * MQTTProtocolManager
  */
-public class MQTTProtocolManager extends AbstractProtocolManager<MqttMessage, MQTTInterceptor, MQTTConnection> implements NotificationListener {
+public class MQTTProtocolManager extends AbstractProtocolManager<MqttMessage, MQTTInterceptor, MQTTConnection, MQTTRedirectHandler> implements NotificationListener {
 
    private static final List<String> websocketRegistryNames = Arrays.asList("mqtt", "mqttv3.1");
 
@@ -62,6 +62,8 @@ public class MQTTProtocolManager extends AbstractProtocolManager<MqttMessage, MQ
    private final Map<String, MQTTConnection> connectedClients;
    private final Map<String, MQTTSessionState> sessionStates;
 
+   private final MQTTRedirectHandler redirectHandler;
+
    MQTTProtocolManager(ActiveMQServer server,
                        Map<String, MQTTConnection> connectedClients,
                        Map<String, MQTTSessionState> sessionStates,
@@ -72,6 +74,7 @@ public class MQTTProtocolManager extends AbstractProtocolManager<MqttMessage, MQ
       this.sessionStates = sessionStates;
       this.updateInterceptors(incomingInterceptors, outgoingInterceptors);
       server.getManagementService().addNotificationListener(this);
+      redirectHandler = new MQTTRedirectHandler(server);
    }
 
    @Override
@@ -209,6 +212,11 @@ public class MQTTProtocolManager extends AbstractProtocolManager<MqttMessage, MQ
       return websocketRegistryNames;
    }
 
+   @Override
+   public MQTTRedirectHandler getRedirectHandler() {
+      return redirectHandler;
+   }
+
    public String invokeIncoming(MqttMessage mqttMessage, MQTTConnection connection) {
       return super.invokeInterceptors(this.incomingInterceptors, mqttMessage, connection);
    }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java b/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTRedirectContext.java
similarity index 51%
copy from artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
copy to artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTRedirectContext.java
index f9e1b3d..1cb4059 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
+++ b/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTRedirectContext.java
@@ -1,34 +1,37 @@
-/*
+/**
  * 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
- *
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
  * 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.util.Map;
+package org.apache.activemq.artemis.core.protocol.mqtt;
 
-import io.netty.channel.Channel;
-import org.apache.activemq.artemis.spi.core.remoting.ServerConnectionLifeCycleListener;
+import io.netty.handler.codec.mqtt.MqttConnectMessage;
+import org.apache.activemq.artemis.core.server.balancing.RedirectContext;
 
-public class NettyServerConnection extends NettyConnection {
+public class MQTTRedirectContext extends RedirectContext {
 
-   public NettyServerConnection(Map<String, Object> configuration,
-                                Channel channel,
-                                ServerConnectionLifeCycleListener listener,
-                                boolean batchingEnabled,
-                                boolean directDeliver) {
-      super(configuration, channel, listener, batchingEnabled, directDeliver);
+   private final MQTTSession mqttSession;
+
+
+   public MQTTSession getMQTTSession() {
+      return mqttSession;
    }
 
+
+   public MQTTRedirectContext(MQTTConnection mqttConnection, MQTTSession mqttSession, MqttConnectMessage connect) {
+      super(mqttConnection, connect.payload().clientIdentifier(), connect.payload().userName());
+      this.mqttSession = mqttSession;
+   }
 }
diff --git a/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTRedirectHandler.java b/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTRedirectHandler.java
new file mode 100644
index 0000000..3b37203
--- /dev/null
+++ b/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTRedirectHandler.java
@@ -0,0 +1,55 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.mqtt;
+
+import io.netty.handler.codec.mqtt.MqttConnectMessage;
+import io.netty.handler.codec.mqtt.MqttConnectReturnCode;
+import io.netty.handler.codec.mqtt.MqttProperties;
+import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants;
+import org.apache.activemq.artemis.core.server.ActiveMQServer;
+import org.apache.activemq.artemis.core.server.balancing.RedirectHandler;
+import org.apache.activemq.artemis.utils.ConfigurationHelper;
+
+public class MQTTRedirectHandler extends RedirectHandler<MQTTRedirectContext> {
+
+   protected MQTTRedirectHandler(ActiveMQServer server) {
+      super(server);
+   }
+
+   public boolean redirect(MQTTConnection mqttConnection, MQTTSession mqttSession, MqttConnectMessage connect) throws Exception {
+      return redirect(new MQTTRedirectContext(mqttConnection, mqttSession, connect));
+   }
+
+   @Override
+   protected void cannotRedirect(MQTTRedirectContext context) throws Exception {
+      context.getMQTTSession().getProtocolHandler().sendConnack(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE);
+      context.getMQTTSession().getProtocolHandler().disconnect(true);
+   }
+
+   @Override
+   protected void redirectTo(MQTTRedirectContext context) throws Exception {
+      String host = ConfigurationHelper.getStringProperty(TransportConstants.HOST_PROP_NAME, TransportConstants.DEFAULT_HOST, context.getTarget().getConnector().getParams());
+      int port = ConfigurationHelper.getIntProperty(TransportConstants.PORT_PROP_NAME, TransportConstants.DEFAULT_PORT, context.getTarget().getConnector().getParams());
+
+      MqttProperties mqttProperties = new MqttProperties();
+      mqttProperties.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.SERVER_REFERENCE.value(), String.format("%s:%d", host, port)));
+
+      context.getMQTTSession().getProtocolHandler().sendConnack(MqttConnectReturnCode.CONNECTION_REFUSED_USE_ANOTHER_SERVER, mqttProperties);
+      context.getMQTTSession().getProtocolHandler().disconnect(true);
+   }
+}
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 5b4586d..a77fec6 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
@@ -1146,6 +1146,12 @@ public class OpenWireConnection extends AbstractRemotingConnection implements Se
       @Override
       public Response processAddConnection(ConnectionInfo info) throws Exception {
          try {
+            if (transportConnection.getRedirectTo() != null && protocolManager.getRedirectHandler()
+               .redirect(OpenWireConnection.this, info)) {
+               shutdown(true);
+               return null;
+            }
+
             protocolManager.addConnection(OpenWireConnection.this, info);
          } catch (Exception e) {
             Response resp = new ExceptionResponse(e);
diff --git a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireProtocolManager.java b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireProtocolManager.java
index bcc39fe..852f4dc 100644
--- a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireProtocolManager.java
+++ b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireProtocolManager.java
@@ -81,7 +81,7 @@ import org.apache.activemq.util.LongSequenceGenerator;
 
 import static org.apache.activemq.artemis.core.protocol.openwire.util.OpenWireUtil.SELECTOR_AWARE_OPTION;
 
-public class OpenWireProtocolManager  extends AbstractProtocolManager<Command, OpenWireInterceptor, OpenWireConnection> implements ClusterTopologyListener {
+public class OpenWireProtocolManager  extends AbstractProtocolManager<Command, OpenWireInterceptor, OpenWireConnection, OpenWireRedirectHandler> implements ClusterTopologyListener {
 
    private static final List<String> websocketRegistryNames = Collections.EMPTY_LIST;
 
@@ -137,6 +137,7 @@ public class OpenWireProtocolManager  extends AbstractProtocolManager<Command, O
    private final List<OpenWireInterceptor> incomingInterceptors = new ArrayList<>();
    private final List<OpenWireInterceptor> outgoingInterceptors = new ArrayList<>();
 
+   private final OpenWireRedirectHandler redirectHandler;
 
    protected static class VirtualTopicConfig {
       public int filterPathTerminus;
@@ -187,6 +188,8 @@ public class OpenWireProtocolManager  extends AbstractProtocolManager<Command, O
 
       //make sure we don't cluster advisories
       clusterManager.addProtocolIgnoredAddress(AdvisorySupport.ADVISORY_TOPIC_PREFIX);
+
+      redirectHandler = new OpenWireRedirectHandler(server, this);
    }
 
    @Override
@@ -637,6 +640,11 @@ public class OpenWireProtocolManager  extends AbstractProtocolManager<Command, O
    }
 
    @Override
+   public OpenWireRedirectHandler getRedirectHandler() {
+      return redirectHandler;
+   }
+
+   @Override
    public String getSecurityDomain() {
       return securityDomain;
    }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireRedirectContext.java
similarity index 50%
copy from artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
copy to artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireRedirectContext.java
index f9e1b3d..047a4c9 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
+++ b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireRedirectContext.java
@@ -1,34 +1,37 @@
-/*
+/**
  * 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
- *
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
  * 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.util.Map;
+package org.apache.activemq.artemis.core.protocol.openwire;
 
-import io.netty.channel.Channel;
-import org.apache.activemq.artemis.spi.core.remoting.ServerConnectionLifeCycleListener;
+import org.apache.activemq.artemis.core.server.balancing.RedirectContext;
+import org.apache.activemq.command.ConnectionInfo;
 
-public class NettyServerConnection extends NettyConnection {
+public class OpenWireRedirectContext extends RedirectContext {
 
-   public NettyServerConnection(Map<String, Object> configuration,
-                                Channel channel,
-                                ServerConnectionLifeCycleListener listener,
-                                boolean batchingEnabled,
-                                boolean directDeliver) {
-      super(configuration, channel, listener, batchingEnabled, directDeliver);
+   private final OpenWireConnection openWireConnection;
+
+
+   public OpenWireConnection getOpenWireConnection() {
+      return openWireConnection;
    }
 
+
+   public OpenWireRedirectContext(OpenWireConnection openWireConnection, ConnectionInfo connectionInfo) {
+      super(openWireConnection.getRemotingConnection(), connectionInfo.getClientId(), connectionInfo.getUserName());
+      this.openWireConnection = openWireConnection;
+   }
 }
diff --git a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireRedirectHandler.java b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireRedirectHandler.java
new file mode 100644
index 0000000..83510af
--- /dev/null
+++ b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireRedirectHandler.java
@@ -0,0 +1,58 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.openwire;
+
+import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants;
+import org.apache.activemq.artemis.core.server.ActiveMQServer;
+import org.apache.activemq.artemis.core.server.balancing.RedirectHandler;
+import org.apache.activemq.artemis.utils.ConfigurationHelper;
+import org.apache.activemq.command.ConnectionControl;
+import org.apache.activemq.command.ConnectionInfo;
+
+public class OpenWireRedirectHandler extends RedirectHandler<OpenWireRedirectContext> {
+
+   private final OpenWireProtocolManager protocolManager;
+
+   protected OpenWireRedirectHandler(ActiveMQServer server, OpenWireProtocolManager protocolManager) {
+      super(server);
+      this.protocolManager = protocolManager;
+   }
+
+   public boolean redirect(OpenWireConnection openWireConnection, ConnectionInfo connectionInfo) throws Exception {
+      if (!connectionInfo.isFaultTolerant()) {
+         throw new java.lang.IllegalStateException("Client not fault tolerant");
+      }
+
+      return redirect(new OpenWireRedirectContext(openWireConnection, connectionInfo));
+   }
+
+   @Override
+   protected void cannotRedirect(OpenWireRedirectContext context) throws Exception {
+   }
+
+   @Override
+   protected void redirectTo(OpenWireRedirectContext context) throws Exception {
+      String host = ConfigurationHelper.getStringProperty(TransportConstants.HOST_PROP_NAME, TransportConstants.DEFAULT_HOST, context.getTarget().getConnector().getParams());
+      int port = ConfigurationHelper.getIntProperty(TransportConstants.PORT_PROP_NAME, TransportConstants.DEFAULT_PORT, context.getTarget().getConnector().getParams());
+
+      ConnectionControl command = protocolManager.newConnectionControl();
+      command.setConnectedBrokers(String.format("tcp://%s:%d", host, port));
+      command.setRebalanceConnection(true);
+      context.getOpenWireConnection().dispatchSync(command);
+   }
+}
diff --git a/artemis-protocols/artemis-stomp-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/stomp/StompProtocolManager.java b/artemis-protocols/artemis-stomp-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/stomp/StompProtocolManager.java
index 8546f9a..e0bfecd 100644
--- a/artemis-protocols/artemis-stomp-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/stomp/StompProtocolManager.java
+++ b/artemis-protocols/artemis-stomp-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/stomp/StompProtocolManager.java
@@ -38,6 +38,7 @@ import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants;
 import org.apache.activemq.artemis.core.server.ActiveMQServer;
 import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
 import org.apache.activemq.artemis.core.server.ServerSession;
+import org.apache.activemq.artemis.core.server.balancing.RedirectHandler;
 import org.apache.activemq.artemis.logs.AuditLogger;
 import org.apache.activemq.artemis.spi.core.protocol.AbstractProtocolManager;
 import org.apache.activemq.artemis.spi.core.protocol.ConnectionEntry;
@@ -52,7 +53,7 @@ import static org.apache.activemq.artemis.core.protocol.stomp.ActiveMQStompProto
 /**
  * StompProtocolManager
  */
-public class StompProtocolManager extends AbstractProtocolManager<StompFrame, StompFrameInterceptor, StompConnection> {
+public class StompProtocolManager extends AbstractProtocolManager<StompFrame, StompFrameInterceptor, StompConnection, RedirectHandler> {
 
    private static final List<String> websocketRegistryNames = Arrays.asList("v10.stomp", "v11.stomp", "v12.stomp");
 
@@ -190,6 +191,11 @@ public class StompProtocolManager extends AbstractProtocolManager<StompFrame, St
       return websocketRegistryNames;
    }
 
+   @Override
+   public RedirectHandler getRedirectHandler() {
+      return null;
+   }
+
    // Public --------------------------------------------------------
 
    public boolean send(final StompConnection connection, final StompFrame frame) {
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 169acbe..2ded699 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
@@ -53,7 +53,6 @@ import org.apache.activemq.artemis.api.core.client.TopologyMember;
 import org.apache.activemq.artemis.api.jms.ActiveMQJMSClient;
 import org.apache.activemq.artemis.core.client.impl.ClientSessionInternal;
 import org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl;
-import org.apache.activemq.artemis.jms.client.ActiveMQConnection;
 import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;
 import org.apache.activemq.artemis.jms.client.ActiveMQDestination;
 import org.apache.activemq.artemis.ra.ActiveMQRABundle;
@@ -494,7 +493,7 @@ public class ActiveMQActivation {
          result.addMetaData(ClientSession.JMS_SESSION_IDENTIFIER_PROPERTY, "");
          String clientID = ra.getClientID() == null ? spec.getClientID() : ra.getClientID();
          if (clientID != null) {
-            result.addMetaData(ActiveMQConnection.JMS_SESSION_CLIENT_ID_PROPERTY, clientID);
+            result.addMetaData(ClientSession.JMS_SESSION_CLIENT_ID_PROPERTY, clientID);
          }
 
          logger.debug("Using queue connection " + result);
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java
index d6301f8..a67d09b 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java
@@ -26,6 +26,7 @@ import java.util.concurrent.TimeUnit;
 
 import org.apache.activemq.artemis.api.core.QueueConfiguration;
 import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectConfiguration;
+import org.apache.activemq.artemis.core.config.balancing.BrokerBalancerConfiguration;
 import org.apache.activemq.artemis.core.server.metrics.ActiveMQMetricsPlugin;
 import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerFederationPlugin;
 import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerAddressPlugin;
@@ -467,6 +468,18 @@ public interface Configuration {
    Configuration addDivertConfiguration(DivertConfiguration config);
 
    /**
+    * Returns the redirects configured for this server.
+    */
+   List<BrokerBalancerConfiguration> getBalancerConfigurations();
+
+   /**
+    * Sets the redirects configured for this server.
+    */
+   Configuration setBalancerConfigurations(List<BrokerBalancerConfiguration> configs);
+
+   Configuration addBalancerConfiguration(BrokerBalancerConfiguration config);
+
+   /**
     * Returns the cluster connections configured for this server.
     * <p>
     * Modifying the returned list will modify the list of {@link ClusterConnectionConfiguration}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/balancing/BrokerBalancerConfiguration.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/balancing/BrokerBalancerConfiguration.java
new file mode 100644
index 0000000..1da1c04
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/balancing/BrokerBalancerConfiguration.java
@@ -0,0 +1,95 @@
+/*
+ * 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.balancing;
+
+import org.apache.activemq.artemis.core.server.balancing.targets.TargetKey;
+
+import java.io.Serializable;
+
+public class BrokerBalancerConfiguration implements Serializable {
+
+   private String name = null;
+   private TargetKey targetKey = TargetKey.SOURCE_IP;
+   private String targetKeyFilter = null;
+   private String localTargetFilter = null;
+   private int cacheTimeout = -1;
+   private PoolConfiguration poolConfiguration = null;
+   private PolicyConfiguration policyConfiguration = null;
+
+   public String getName() {
+      return name;
+   }
+
+   public BrokerBalancerConfiguration setName(String name) {
+      this.name = name;
+      return this;
+   }
+
+   public TargetKey getTargetKey() {
+      return targetKey;
+   }
+
+   public BrokerBalancerConfiguration setTargetKey(TargetKey targetKey) {
+      this.targetKey = targetKey;
+      return this;
+   }
+
+   public String getTargetKeyFilter() {
+      return targetKeyFilter;
+   }
+
+   public BrokerBalancerConfiguration setTargetKeyFilter(String targetKeyFilter) {
+      this.targetKeyFilter = targetKeyFilter;
+      return this;
+   }
+
+   public String getLocalTargetFilter() {
+      return localTargetFilter;
+   }
+
+   public BrokerBalancerConfiguration setLocalTargetFilter(String localTargetFilter) {
+      this.localTargetFilter = localTargetFilter;
+      return this;
+   }
+
+   public int getCacheTimeout() {
+      return cacheTimeout;
+   }
+
+   public BrokerBalancerConfiguration setCacheTimeout(int cacheTimeout) {
+      this.cacheTimeout = cacheTimeout;
+      return this;
+   }
+
+   public PolicyConfiguration getPolicyConfiguration() {
+      return policyConfiguration;
+   }
+
+   public BrokerBalancerConfiguration setPolicyConfiguration(PolicyConfiguration policyConfiguration) {
+      this.policyConfiguration = policyConfiguration;
+      return this;
+   }
+
+   public PoolConfiguration getPoolConfiguration() {
+      return poolConfiguration;
+   }
+
+   public BrokerBalancerConfiguration setPoolConfiguration(PoolConfiguration poolConfiguration) {
+      this.poolConfiguration = poolConfiguration;
+      return this;
+   }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/balancing/PolicyConfiguration.java
similarity index 52%
copy from artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
copy to artemis-server/src/main/java/org/apache/activemq/artemis/core/config/balancing/PolicyConfiguration.java
index f9e1b3d..f1f8630 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/balancing/PolicyConfiguration.java
@@ -1,34 +1,45 @@
-/*
+/**
  * 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
- *
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
  * 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;
 
+package org.apache.activemq.artemis.core.config.balancing;
+
+import java.io.Serializable;
 import java.util.Map;
 
-import io.netty.channel.Channel;
-import org.apache.activemq.artemis.spi.core.remoting.ServerConnectionLifeCycleListener;
+public class PolicyConfiguration implements Serializable {
+   private String name;
+
+   private Map<String, String> properties;
+
+   public String getName() {
+      return name;
+   }
 
-public class NettyServerConnection extends NettyConnection {
+   public PolicyConfiguration setName(String name) {
+      this.name = name;
+      return this;
+   }
 
-   public NettyServerConnection(Map<String, Object> configuration,
-                                Channel channel,
-                                ServerConnectionLifeCycleListener listener,
-                                boolean batchingEnabled,
-                                boolean directDeliver) {
-      super(configuration, channel, listener, batchingEnabled, directDeliver);
+   public Map<String, String> getProperties() {
+      return properties;
    }
 
+   public PolicyConfiguration setProperties(Map<String, String> properties) {
+      this.properties = properties;
+      return this;
+   }
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/balancing/PoolConfiguration.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/balancing/PoolConfiguration.java
new file mode 100644
index 0000000..699184a
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/balancing/PoolConfiguration.java
@@ -0,0 +1,123 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.balancing;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+
+public class PoolConfiguration implements Serializable {
+   private String username;
+
+   private String password;
+
+   private boolean localTargetEnabled = false;
+
+   private String clusterConnection = null;
+
+   private List<String> staticConnectors = Collections.emptyList();
+
+   private String discoveryGroupName = null;
+
+   private int checkPeriod = 5000;
+
+   private int quorumSize = 1;
+
+   private int quorumTimeout = 3000;
+
+   public String getUsername() {
+      return username;
+   }
+
+   public PoolConfiguration setUsername(String username) {
+      this.username = username;
+      return this;
+   }
+
+   public String getPassword() {
+      return password;
+   }
+
+   public PoolConfiguration setPassword(String password) {
+      this.password = password;
+      return this;
+   }
+
+   public int getCheckPeriod() {
+      return checkPeriod;
+   }
+
+   public PoolConfiguration setCheckPeriod(int checkPeriod) {
+      this.checkPeriod = checkPeriod;
+      return this;
+   }
+
+   public int getQuorumSize() {
+      return quorumSize;
+   }
+
+   public PoolConfiguration setQuorumSize(int quorumSize) {
+      this.quorumSize = quorumSize;
+      return this;
+   }
+
+   public int getQuorumTimeout() {
+      return quorumTimeout;
+   }
+
+   public PoolConfiguration setQuorumTimeout(int quorumTimeout) {
+      this.quorumTimeout = quorumTimeout;
+      return this;
+   }
+
+   public boolean isLocalTargetEnabled() {
+      return localTargetEnabled;
+   }
+
+   public PoolConfiguration setLocalTargetEnabled(boolean localTargetEnabled) {
+      this.localTargetEnabled = localTargetEnabled;
+      return this;
+   }
+
+   public String getClusterConnection() {
+      return clusterConnection;
+   }
+
+   public PoolConfiguration setClusterConnection(String clusterConnection) {
+      this.clusterConnection = clusterConnection;
+      return this;
+   }
+
+   public List<String> getStaticConnectors() {
+      return staticConnectors;
+   }
+
+   public PoolConfiguration setStaticConnectors(List<String> staticConnectors) {
+      this.staticConnectors = staticConnectors;
+      return this;
+   }
+
+   public String getDiscoveryGroupName() {
+      return discoveryGroupName;
+   }
+
+   public PoolConfiguration setDiscoveryGroupName(String discoveryGroupName) {
+      this.discoveryGroupName = discoveryGroupName;
+      return this;
+   }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java
index d36a804..6aa7785 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java
@@ -47,6 +47,7 @@ import org.apache.activemq.artemis.api.core.DiscoveryGroupConfiguration;
 import org.apache.activemq.artemis.api.core.QueueConfiguration;
 import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.api.core.TransportConfiguration;
+import org.apache.activemq.artemis.core.config.balancing.BrokerBalancerConfiguration;
 import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectConfiguration;
 import org.apache.activemq.artemis.core.config.BridgeConfiguration;
 import org.apache.activemq.artemis.core.config.ClusterConnectionConfiguration;
@@ -166,6 +167,8 @@ public class ConfigurationImpl implements Configuration, Serializable {
 
    protected List<DivertConfiguration> divertConfigurations = new ArrayList<>();
 
+   protected List<BrokerBalancerConfiguration> brokerBalancerConfigurations = new ArrayList<>();
+
    protected List<ClusterConnectionConfiguration> clusterConfigurations = new ArrayList<>();
 
    protected List<AMQPBrokerConnectConfiguration> amqpBrokerConnectConfigurations = new ArrayList<>();
@@ -820,6 +823,23 @@ public class ConfigurationImpl implements Configuration, Serializable {
       return this;
    }
 
+   @Override
+   public List<BrokerBalancerConfiguration> getBalancerConfigurations() {
+      return brokerBalancerConfigurations;
+   }
+
+   @Override
+   public ConfigurationImpl setBalancerConfigurations(final List<BrokerBalancerConfiguration> configs) {
+      brokerBalancerConfigurations = configs;
+      return this;
+   }
+
+   @Override
+   public ConfigurationImpl addBalancerConfiguration(final BrokerBalancerConfiguration config) {
+      brokerBalancerConfigurations.add(config);
+      return this;
+   }
+
    @Deprecated
    @Override
    public List<CoreQueueConfiguration> getQueueConfigurations() {
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/Validators.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/Validators.java
index a6e04a8..4f1e2ee 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/Validators.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/Validators.java
@@ -18,6 +18,7 @@ package org.apache.activemq.artemis.core.config.impl;
 
 import java.util.EnumSet;
 
+import org.apache.activemq.artemis.core.server.balancing.targets.TargetKey;
 import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle;
 import org.apache.activemq.artemis.core.server.ComponentConfigurationRoutingType;
 import org.apache.activemq.artemis.core.server.JournalType;
@@ -272,4 +273,14 @@ public final class Validators {
          }
       }
    };
+
+   public static final Validator TARGET_KEY = new Validator() {
+      @Override
+      public void validate(final String name, final Object value) {
+         String val = (String) value;
+         if (val == null || !EnumSet.allOf(TargetKey.class).contains(TargetKey.valueOf(val))) {
+            throw ActiveMQMessageBundle.BUNDLE.invalidTargetKey(val);
+         }
+      }
+   };
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java
index 93eb2db..d2e1d75 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java
@@ -46,6 +46,8 @@ import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.api.core.TransportConfiguration;
 import org.apache.activemq.artemis.api.core.UDPBroadcastEndpointFactory;
 import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
+import org.apache.activemq.artemis.core.config.balancing.BrokerBalancerConfiguration;
+import org.apache.activemq.artemis.core.config.balancing.PolicyConfiguration;
 import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectConfiguration;
 import org.apache.activemq.artemis.core.config.BridgeConfiguration;
 import org.apache.activemq.artemis.core.config.ClusterConnectionConfiguration;
@@ -62,6 +64,7 @@ import org.apache.activemq.artemis.core.config.WildcardConfiguration;
 import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectionElement;
 import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectionAddressType;
 import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPMirrorBrokerConnectionElement;
+import org.apache.activemq.artemis.core.config.balancing.PoolConfiguration;
 import org.apache.activemq.artemis.core.config.federation.FederationAddressPolicyConfiguration;
 import org.apache.activemq.artemis.core.config.federation.FederationDownstreamConfiguration;
 import org.apache.activemq.artemis.core.config.federation.FederationPolicySet;
@@ -88,6 +91,8 @@ import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
 import org.apache.activemq.artemis.core.server.ComponentConfigurationRoutingType;
 import org.apache.activemq.artemis.core.server.JournalType;
 import org.apache.activemq.artemis.core.server.SecuritySettingPlugin;
+import org.apache.activemq.artemis.core.server.balancing.policies.PolicyFactoryResolver;
+import org.apache.activemq.artemis.core.server.balancing.targets.TargetKey;
 import org.apache.activemq.artemis.core.server.cluster.impl.MessageLoadBalancingType;
 import org.apache.activemq.artemis.core.server.group.impl.GroupingHandlerConfiguration;
 import org.apache.activemq.artemis.core.server.metrics.ActiveMQMetricsPlugin;
@@ -624,6 +629,21 @@ public final class FileConfigurationParser extends XMLConfigurationUtil {
 
          parseDivertConfiguration(dvNode, config);
       }
+
+      NodeList ccBalancers = e.getElementsByTagName("broker-balancers");
+
+      if (ccBalancers != null) {
+         NodeList ccBalancer = e.getElementsByTagName("broker-balancer");
+
+         if (ccBalancer != null) {
+            for (int i = 0; i < ccBalancer.getLength(); i++) {
+               Element ccNode = (Element) ccBalancer.item(i);
+
+               parseBalancerConfiguration(ccNode, config);
+            }
+         }
+      }
+
       // Persistence config
 
       config.setLargeMessagesDirectory(getString(e, "large-messages-directory", config.getLargeMessagesDirectory(), Validators.NOT_NULL_OR_EMPTY));
@@ -2620,7 +2640,87 @@ public final class FileConfigurationParser extends XMLConfigurationUtil {
       mainConfig.getDivertConfigurations().add(config);
    }
 
-   /**
+   private void parseBalancerConfiguration(final Element e, final Configuration config) throws Exception {
+      BrokerBalancerConfiguration brokerBalancerConfiguration = new BrokerBalancerConfiguration();
+
+      brokerBalancerConfiguration.setName(e.getAttribute("name"));
+
+      brokerBalancerConfiguration.setTargetKey(TargetKey.valueOf(getString(e, "target-key", brokerBalancerConfiguration.getTargetKey().name(), Validators.TARGET_KEY)));
+
+      brokerBalancerConfiguration.setTargetKeyFilter(getString(e, "target-key-filter", brokerBalancerConfiguration.getTargetKeyFilter(), Validators.NO_CHECK));
+
+      brokerBalancerConfiguration.setLocalTargetFilter(getString(e, "local-target-filter", brokerBalancerConfiguration.getLocalTargetFilter(), Validators.NO_CHECK));
+
+      brokerBalancerConfiguration.setCacheTimeout(getInteger(e, "cache-timeout",
+         brokerBalancerConfiguration.getCacheTimeout(), Validators.MINUS_ONE_OR_GE_ZERO));
+
+      PolicyConfiguration policyConfiguration = null;
+      PoolConfiguration poolConfiguration = null;
+      NodeList children = e.getChildNodes();
+
+      for (int j = 0; j < children.getLength(); j++) {
+         Node child = children.item(j);
+
+         if (child.getNodeName().equals("policy")) {
+            policyConfiguration = new PolicyConfiguration();
+            parsePolicyConfiguration((Element)child, policyConfiguration);
+            brokerBalancerConfiguration.setPolicyConfiguration(policyConfiguration);
+         } else if (child.getNodeName().equals("pool")) {
+            poolConfiguration = new PoolConfiguration();
+            parsePoolConfiguration((Element) child, config, poolConfiguration);
+            brokerBalancerConfiguration.setPoolConfiguration(poolConfiguration);
+         }
+      }
+
+      config.getBalancerConfigurations().add(brokerBalancerConfiguration);
+   }
+
+   private void parsePolicyConfiguration(final Element e, final PolicyConfiguration policyConfiguration) throws ClassNotFoundException {
+      String name = e.getAttribute("name");
+
+      PolicyFactoryResolver.getInstance().resolve(name);
+
+      policyConfiguration.setName(name);
+
+      policyConfiguration.setProperties(getMapOfChildPropertyElements(e));
+   }
+
+   private void parsePoolConfiguration(final Element e, final Configuration config, final PoolConfiguration poolConfiguration) throws Exception {
+      poolConfiguration.setUsername(getString(e, "username", null, Validators.NO_CHECK));
+
+      String password = getString(e, "password", null, Validators.NO_CHECK);
+      poolConfiguration.setPassword(password != null ? PasswordMaskingUtil.resolveMask(
+         config.isMaskPassword(), password, config.getPasswordCodec()) : null);
+
+      poolConfiguration.setCheckPeriod(getInteger(e, "check-period",
+         poolConfiguration.getCheckPeriod(), Validators.GT_ZERO));
+
+      poolConfiguration.setQuorumSize(getInteger(e, "quorum-size",
+         poolConfiguration.getQuorumSize(), Validators.GT_ZERO));
+
+      poolConfiguration.setQuorumTimeout(getInteger(e, "quorum-timeout",
+         poolConfiguration.getQuorumTimeout(), Validators.GE_ZERO));
+
+      poolConfiguration.setLocalTargetEnabled(getBoolean(e, "local-target-enabled", poolConfiguration.isLocalTargetEnabled()));
+
+      poolConfiguration.setClusterConnection(getString(e, "cluster-connection", null, Validators.NO_CHECK));
+
+      NodeList children = e.getChildNodes();
+
+      for (int i = 0; i < children.getLength(); i++) {
+         Node child = children.item(i);
+
+         if (child.getNodeName().equals("discovery-group-ref")) {
+            poolConfiguration.setDiscoveryGroupName(child.getAttributes().getNamedItem("discovery-group-name").getNodeValue());
+         } else if (child.getNodeName().equals("static-connectors")) {
+            List<String> staticConnectorNames = new ArrayList<>();
+            getStaticConnectors(staticConnectorNames, child);
+            poolConfiguration.setStaticConnectors(staticConnectorNames);
+         }
+      }
+   }
+
+   /**RedirectConfiguration
     * @param e
     */
    protected void parseWildcardConfiguration(final Element e, final Configuration mainConfig) {
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/BrokerBalancerControlImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/BrokerBalancerControlImpl.java
new file mode 100644
index 0000000..72963bb
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/BrokerBalancerControlImpl.java
@@ -0,0 +1,160 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.management.impl;
+
+import org.apache.activemq.artemis.api.core.TransportConfiguration;
+import org.apache.activemq.artemis.api.core.management.BrokerBalancerControl;
+import org.apache.activemq.artemis.core.persistence.StorageManager;
+import org.apache.activemq.artemis.core.server.balancing.BrokerBalancer;
+import org.apache.activemq.artemis.core.server.balancing.targets.Target;
+import org.apache.activemq.artemis.utils.JsonLoader;
+
+import javax.json.JsonObjectBuilder;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.NotCompliantMBeanException;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.TabularDataSupport;
+import javax.management.openmbean.TabularType;
+import java.util.Map;
+
+public class BrokerBalancerControlImpl extends AbstractControl implements BrokerBalancerControl {
+   private final BrokerBalancer balancer;
+
+
+   private static CompositeType parameterType;
+
+   private static TabularType parametersType;
+
+   private static CompositeType transportConfigurationType;
+
+   private static CompositeType targetType;
+
+
+   public BrokerBalancerControlImpl(final BrokerBalancer balancer, final StorageManager storageManager) throws NotCompliantMBeanException {
+      super(BrokerBalancerControl.class, storageManager);
+      this.balancer = balancer;
+   }
+
+   @Override
+   public CompositeData getTarget(String key) throws Exception {
+      Target target = balancer.getTarget(key);
+
+      if (target != null) {
+         CompositeData connectorData = null;
+         TransportConfiguration connector = target.getConnector();
+
+         if (connector != null) {
+            TabularData paramsData = new TabularDataSupport(getParametersType());
+            for (Map.Entry<String, Object> param : connector.getParams().entrySet()) {
+               paramsData.put(new CompositeDataSupport(getParameterType(), new String[]{"key", "value"},
+                  new Object[]{param.getKey(), param == null ? param : param.getValue().toString()}));
+            }
+
+            connectorData = new CompositeDataSupport(getTransportConfigurationType(),
+               new String[]{"name", "factoryClassName", "params"},
+               new Object[]{connector.getName(), connector.getFactoryClassName(), paramsData});
+         }
+
+         CompositeData targetData = new CompositeDataSupport(getTargetCompositeType(),
+            new String[]{"nodeID", "local", "connector"},
+            new Object[]{target.getNodeID(), target.isLocal(), connectorData});
+
+         return targetData;
+      }
+
+      return null;
+   }
+
+   @Override
+   public String getTargetAsJSON(String key) {
+      Target target = balancer.getTarget(key);
+
+      if (target != null) {
+         TransportConfiguration connector = target.getConnector();
+
+         JsonObjectBuilder targetDataBuilder = JsonLoader.createObjectBuilder()
+            .add("nodeID", target.getNodeID())
+            .add("local", target.isLocal());
+
+         if (connector == null) {
+            targetDataBuilder.addNull("connector");
+         } else {
+            targetDataBuilder.add("connector", connector.toJson());
+         }
+
+         return targetDataBuilder.build().toString();
+      }
+
+      return null;
+   }
+
+   @Override
+   protected MBeanOperationInfo[] fillMBeanOperationInfo() {
+      return MBeanInfoHelper.getMBeanOperationsInfo(BrokerBalancerControl.class);
+   }
+
+   @Override
+   protected MBeanAttributeInfo[] fillMBeanAttributeInfo() {
+      return MBeanInfoHelper.getMBeanAttributesInfo(BrokerBalancerControl.class);
+   }
+
+
+   private CompositeType getParameterType() throws OpenDataException {
+      if (parameterType == null) {
+         parameterType = new CompositeType("java.util.Map.Entry<java.lang.String, java.lang.String>",
+            "Parameter", new String[]{"key", "value"}, new String[]{"Parameter key", "Parameter value"},
+            new OpenType[]{SimpleType.STRING, SimpleType.STRING});
+      }
+      return parameterType;
+   }
+
+   private TabularType getParametersType() throws OpenDataException {
+      if (parametersType == null) {
+         parametersType = new TabularType("java.util.Map<java.lang.String, java.lang.String>",
+            "Parameters", getParameterType(), new String[]{"key"});
+      }
+      return parametersType;
+   }
+
+   private CompositeType getTransportConfigurationType() throws OpenDataException {
+      if (transportConfigurationType == null) {
+         transportConfigurationType = new CompositeType(TransportConfiguration.class.getName(),
+            "TransportConfiguration", new String[]{"name", "factoryClassName", "params"},
+            new String[]{"TransportConfiguration name", "TransportConfiguration factoryClassName", "TransportConfiguration params"},
+            new OpenType[]{SimpleType.STRING, SimpleType.STRING, getParametersType()});
+      }
+      return transportConfigurationType;
+   }
+
+   private CompositeType getTargetCompositeType() throws OpenDataException {
+      if (targetType == null) {
+         targetType = new CompositeType(Target.class.getName(),
+            "Target", new String[]{"nodeID", "local", "connector"},
+            new String[]{"Target nodeID", "Target local", "Target connector"},
+            new OpenType[]{SimpleType.STRING, SimpleType.BOOLEAN, getTransportConfigurationType()});
+      }
+      return targetType;
+   }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ConsumerView.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ConsumerView.java
index 34eeffb..17919ca 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ConsumerView.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ConsumerView.java
@@ -55,7 +55,7 @@ public class ConsumerView extends ActiveMQAbstractView<ServerConsumer> {
       String consumerClientID = consumer.getConnectionClientID();
       if (consumerClientID == null && session.getMetaData(ClientSession.JMS_SESSION_IDENTIFIER_PROPERTY) != null) {
          //for the special case for JMS
-         consumerClientID = session.getMetaData("jms-client-id");
+         consumerClientID = session.getMetaData(ClientSession.JMS_SESSION_CLIENT_ID_PROPERTY);
       }
 
       JsonObjectBuilder obj = JsonLoader.createObjectBuilder()
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ProducerView.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ProducerView.java
index ac3fcf6..fb4a6d6 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ProducerView.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/view/ProducerView.java
@@ -54,7 +54,7 @@ public class ProducerView extends ActiveMQAbstractView<ServerProducer> {
       String sessionClientID = session.getRemotingConnection().getClientID();
       //for the special case for JMS
       if (sessionClientID == null && session.getMetaData(ClientSession.JMS_SESSION_IDENTIFIER_PROPERTY) != null) {
-         sessionClientID = session.getMetaData("jms-client-id");
+         sessionClientID = session.getMetaData(ClientSession.JMS_SESSION_CLIENT_ID_PROPERTY);
       }
 
       JsonObjectBuilder obj = JsonLoader.createObjectBuilder()
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/ProtocolHandler.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/ProtocolHandler.java
index 6bd0d2b..4286eac 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/ProtocolHandler.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/ProtocolHandler.java
@@ -44,6 +44,7 @@ import org.apache.activemq.artemis.core.remoting.impl.netty.HttpAcceptorHandler;
 import org.apache.activemq.artemis.core.remoting.impl.netty.HttpKeepAliveRunnable;
 import org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptor;
 import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnector;
+import org.apache.activemq.artemis.core.remoting.impl.netty.NettySNIHostnameHandler;
 import org.apache.activemq.artemis.core.remoting.impl.netty.NettyServerConnection;
 import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants;
 import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
@@ -111,6 +112,7 @@ public class ProtocolHandler {
 
       private int handshakeTimeout;
 
+      private NettySNIHostnameHandler nettySNIHostnameHandler;
 
       ProtocolDecoder(boolean http, boolean httpEnabled) {
          this.http = http;
@@ -120,6 +122,8 @@ public class ProtocolHandler {
 
       @Override
       public void channelActive(ChannelHandlerContext ctx) throws Exception {
+         nettySNIHostnameHandler = ctx.pipeline().get(NettySNIHostnameHandler.class);
+
          if (handshakeTimeout > 0) {
             timeoutFuture = scheduledThreadPool.schedule( () -> {
                ActiveMQServerLogger.LOGGER.handshakeTimeout(handshakeTimeout, nettyAcceptor.getName(), ctx.channel().remoteAddress().toString());
@@ -220,6 +224,7 @@ public class ProtocolHandler {
          protocolManagerToUse.addChannelHandlers(pipeline);
          pipeline.addLast("handler", channelHandler);
          NettyServerConnection connection = channelHandler.createConnection(ctx, protocolToUse, httpEnabled);
+         connection.setSNIHostname(nettySNIHostnameHandler != null ? nettySNIHostnameHandler.getHostname() : null);
          protocolManagerToUse.handshake(connection, new ChannelBufferWrapper(in));
          pipeline.remove(this);
 
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 e90bcb2..e63dbf5 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
@@ -37,6 +37,7 @@ import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CheckFailo
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CheckFailoverReplyMessage;
 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;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ReattachSessionMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ReattachSessionResponseMessage;
@@ -89,7 +90,8 @@ public class ActiveMQPacketHandler implements ChannelHandler {
       }
 
       switch (type) {
-         case PacketImpl.CREATESESSION: {
+         case PacketImpl.CREATESESSION:
+         case PacketImpl.CREATESESSION_V2: {
             CreateSessionMessage request = (CreateSessionMessage) packet;
 
             handleCreateSession(request);
@@ -157,6 +159,14 @@ public class ActiveMQPacketHandler implements ChannelHandler {
             ActiveMQServerLogger.LOGGER.incompatibleVersionAfterConnect(request.getVersion(), connection.getChannelVersion());
          }
 
+         if (request instanceof CreateSessionMessage_V2) {
+            connection.setClientID(((CreateSessionMessage_V2) request).getClientID());
+         }
+
+         if (connection.getTransportConnection().getRedirectTo() != null) {
+            protocolManager.getRedirectHandler().redirect(connection, request);
+         }
+
          Channel channel = connection.getChannel(request.getSessionChannelID(), request.getWindowSize());
 
          ActiveMQPrincipal activeMQPrincipal = null;
@@ -187,6 +197,8 @@ public class ActiveMQPacketHandler implements ChannelHandler {
          if (e.getType() == ActiveMQExceptionType.INCOMPATIBLE_CLIENT_SERVER_VERSIONS) {
             incompatibleVersion = true;
             logger.debug("Sending ActiveMQException after Incompatible client", e);
+         } else if (e.getType() == ActiveMQExceptionType.REDIRECTED) {
+            logger.debug("Sending ActiveMQException after redirected client", e);
          } else {
             ActiveMQServerLogger.LOGGER.failedToCreateSession(e);
          }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQRedirectContext.java
similarity index 51%
copy from artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
copy to artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQRedirectContext.java
index f9e1b3d..575c9c0 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQRedirectContext.java
@@ -1,34 +1,28 @@
-/*
+/**
  * 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
- *
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
  * 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.util.Map;
+package org.apache.activemq.artemis.core.protocol.core.impl;
 
-import io.netty.channel.Channel;
-import org.apache.activemq.artemis.spi.core.remoting.ServerConnectionLifeCycleListener;
+import org.apache.activemq.artemis.core.protocol.core.CoreRemotingConnection;
+import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSessionMessage;
+import org.apache.activemq.artemis.core.server.balancing.RedirectContext;
 
-public class NettyServerConnection extends NettyConnection {
-
-   public NettyServerConnection(Map<String, Object> configuration,
-                                Channel channel,
-                                ServerConnectionLifeCycleListener listener,
-                                boolean batchingEnabled,
-                                boolean directDeliver) {
-      super(configuration, channel, listener, batchingEnabled, directDeliver);
+public class ActiveMQRedirectContext extends RedirectContext {
+   public ActiveMQRedirectContext(CoreRemotingConnection connection, CreateSessionMessage message) {
+      super(connection, connection.getClientID(), message.getUsername());
    }
-
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQRedirectHandler.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQRedirectHandler.java
new file mode 100644
index 0000000..937bd27
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/ActiveMQRedirectHandler.java
@@ -0,0 +1,52 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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 org.apache.activemq.artemis.api.core.DisconnectReason;
+import org.apache.activemq.artemis.core.protocol.core.CoreRemotingConnection;
+import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.CreateSessionMessage;
+import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle;
+import org.apache.activemq.artemis.core.server.ActiveMQServer;
+import org.apache.activemq.artemis.core.server.balancing.RedirectHandler;
+
+public class ActiveMQRedirectHandler extends RedirectHandler<ActiveMQRedirectContext> {
+
+   public ActiveMQRedirectHandler(ActiveMQServer server) {
+      super(server);
+   }
+
+   public boolean redirect(CoreRemotingConnection connection, CreateSessionMessage message) throws Exception {
+      if (!connection.isVersionSupportRedirect()) {
+         throw ActiveMQMessageBundle.BUNDLE.incompatibleClientServer();
+      }
+
+      return redirect(new ActiveMQRedirectContext(connection, message));
+   }
+
+   @Override
+   public void cannotRedirect(ActiveMQRedirectContext context) throws Exception {
+      throw ActiveMQMessageBundle.BUNDLE.cannotRedirect();
+   }
+
+   @Override
+   public void redirectTo(ActiveMQRedirectContext context) throws Exception {
+      context.getConnection().disconnect(DisconnectReason.REDIRECT, context.getTarget().getNodeID(), context.getTarget().getConnector());
+
+      throw ActiveMQMessageBundle.BUNDLE.redirectConnection(context.getTarget().getConnector());
+   }
+}
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 84baf25..c56513b 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
@@ -55,6 +55,7 @@ import org.apache.activemq.artemis.core.protocol.core.impl.ChannelImpl.CHANNEL_I
 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;
+import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ClusterTopologyChangeMessage_V4;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.FederationDownstreamConnectMessage;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.Ping;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.SubscribeClusterTopologyUpdatesMessage;
@@ -72,7 +73,7 @@ import org.apache.activemq.artemis.spi.core.remoting.Acceptor;
 import org.apache.activemq.artemis.spi.core.remoting.Connection;
 import org.jboss.logging.Logger;
 
-public class CoreProtocolManager implements ProtocolManager<Interceptor> {
+public class CoreProtocolManager implements ProtocolManager<Interceptor, ActiveMQRedirectHandler> {
 
    private static final Logger logger = Logger.getLogger(CoreProtocolManager.class);
 
@@ -90,6 +91,8 @@ public class CoreProtocolManager implements ProtocolManager<Interceptor> {
 
    private String securityDomain;
 
+   private final ActiveMQRedirectHandler redirectHandler;
+
    public CoreProtocolManager(final CoreProtocolManagerFactory factory,
                               final ActiveMQServer server,
                               final List<Interceptor> incomingInterceptors,
@@ -101,6 +104,8 @@ public class CoreProtocolManager implements ProtocolManager<Interceptor> {
       this.incomingInterceptors = incomingInterceptors;
 
       this.outgoingInterceptors = outgoingInterceptors;
+
+      this.redirectHandler = new ActiveMQRedirectHandler(server);
    }
 
    @Override
@@ -233,6 +238,11 @@ public class CoreProtocolManager implements ProtocolManager<Interceptor> {
       return securityDomain;
    }
 
+   @Override
+   public ActiveMQRedirectHandler getRedirectHandler() {
+      return redirectHandler;
+   }
+
    private boolean isArtemis(ActiveMQBuffer buffer) {
       return buffer.getByte(0) == 'A' &&
          buffer.getByte(1) == 'R' &&
@@ -304,7 +314,13 @@ public class CoreProtocolManager implements ProtocolManager<Interceptor> {
                      entry.connectionExecutor.execute(new Runnable() {
                         @Override
                         public void run() {
-                           if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V3)) {
+                           if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V4)) {
+                              channel0.send(new ClusterTopologyChangeMessage_V4(
+                                 topologyMember.getUniqueEventID(), nodeID,
+                                 topologyMember.getBackupGroupName(),
+                                 topologyMember.getScaleDownGroupName(), connectorPair,
+                                 last, server.getVersion().getIncrementingVersion()));
+                           } else if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V3)) {
                               channel0.send(new ClusterTopologyChangeMessage_V3(
                                  topologyMember.getUniqueEventID(), nodeID,
                                  topologyMember.getBackupGroupName(),
@@ -380,7 +396,10 @@ public class CoreProtocolManager implements ProtocolManager<Interceptor> {
                      String nodeId = server.getNodeID().toString();
                      Pair<TransportConfiguration, TransportConfiguration> emptyConfig = new Pair<>(
                         null, null);
-                     if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V2)) {
+                     if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V4)) {
+                        channel0.send(new ClusterTopologyChangeMessage_V4(System.currentTimeMillis(), nodeId,
+                           null, null, emptyConfig, true, server.getVersion().getIncrementingVersion()));
+                     } else if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V2)) {
                         channel0.send(
                            new ClusterTopologyChangeMessage_V2(System.currentTimeMillis(),
                                                                nodeId, null, emptyConfig, true));
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyAcceptor.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyAcceptor.java
index 72c732f..2005c2d 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyAcceptor.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyAcceptor.java
@@ -237,6 +237,8 @@ public class NettyAcceptor extends AbstractAcceptor {
 
    private final boolean autoStart;
 
+   private final String redirectTo;
+
    final AtomicBoolean warningPrinted = new AtomicBoolean(false);
 
    final Executor failureExecutor;
@@ -378,6 +380,8 @@ public class NettyAcceptor extends AbstractAcceptor {
       connectionsAllowed = ConfigurationHelper.getLongProperty(TransportConstants.CONNECTIONS_ALLOWED, TransportConstants.DEFAULT_CONNECTIONS_ALLOWED, configuration);
 
       autoStart = ConfigurationHelper.getBooleanProperty(TransportConstants.AUTO_START, TransportConstants.DEFAULT_AUTO_START, configuration);
+
+      redirectTo = ConfigurationHelper.getStringProperty(TransportConstants.REDIRECT_TO, TransportConstants.DEFAULT_REDIRECT_TO, configuration);
    }
 
    private Object loadSSLContext() {
@@ -460,6 +464,7 @@ public class NettyAcceptor extends AbstractAcceptor {
             if (sslEnabled) {
                final Pair<String, Integer> peerInfo = getPeerInfo(channel);
                try {
+                  pipeline.addLast("sni", new NettySNIHostnameHandler());
                   pipeline.addLast("ssl", getSslHandler(channel.alloc(), peerInfo.getA(), peerInfo.getB()));
                   pipeline.addLast("sslHandshakeExceptionHandler", new SslHandshakeExceptionHandler());
                } catch (Exception e) {
@@ -930,7 +935,7 @@ public class NettyAcceptor extends AbstractAcceptor {
             super.channelActive(ctx);
             Listener connectionListener = new Listener();
 
-            NettyServerConnection nc = new NettyServerConnection(configuration, ctx.channel(), connectionListener, !httpEnabled && batchDelay > 0, directDeliver);
+            NettyServerConnection nc = new NettyServerConnection(configuration, ctx.channel(), connectionListener, !httpEnabled && batchDelay > 0, directDeliver, redirectTo);
 
             connectionListener.connectionCreated(NettyAcceptor.this, nc, protocolHandler.getProtocol(protocol));
 
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettySNIHostnameHandler.java
similarity index 52%
copy from artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
copy to artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettySNIHostnameHandler.java
index f9e1b3d..9345738 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettySNIHostnameHandler.java
@@ -1,34 +1,42 @@
-/*
+/**
  * 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
- *
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
  * 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.util.Map;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.ssl.AbstractSniHandler;
+import io.netty.util.concurrent.Future;
+
+public class NettySNIHostnameHandler extends AbstractSniHandler {
 
-import io.netty.channel.Channel;
-import org.apache.activemq.artemis.spi.core.remoting.ServerConnectionLifeCycleListener;
+   private String hostname = null;
 
-public class NettyServerConnection extends NettyConnection {
+   public String getHostname() {
+      return hostname;
+   }
 
-   public NettyServerConnection(Map<String, Object> configuration,
-                                Channel channel,
-                                ServerConnectionLifeCycleListener listener,
-                                boolean batchingEnabled,
-                                boolean directDeliver) {
-      super(configuration, channel, listener, batchingEnabled, directDeliver);
+   @Override
+   protected Future lookup(ChannelHandlerContext ctx, String hostname) throws Exception {
+      return ctx.executor().<Void>newPromise().setSuccess(null);
    }
 
+   @Override
+   protected void onLookupComplete(ChannelHandlerContext ctx, String hostname, Future future) throws Exception {
+      this.hostname = hostname;
+      ctx.pipeline().remove(this);
+   }
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
index f9e1b3d..7fadb18 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
@@ -23,12 +23,32 @@ import org.apache.activemq.artemis.spi.core.remoting.ServerConnectionLifeCycleLi
 
 public class NettyServerConnection extends NettyConnection {
 
+   private String sniHostname;
+
+   private final String redirectTo;
+
    public NettyServerConnection(Map<String, Object> configuration,
                                 Channel channel,
                                 ServerConnectionLifeCycleListener listener,
                                 boolean batchingEnabled,
-                                boolean directDeliver) {
+                                boolean directDeliver,
+                                String redirectTo) {
       super(configuration, channel, listener, batchingEnabled, directDeliver);
+
+      this.redirectTo = redirectTo;
+   }
+
+   @Override
+   public String getSNIHostName() {
+      return sniHostname;
+   }
+
+   public void setSNIHostname(String sniHostname) {
+      this.sniHostname = sniHostname;
    }
 
+   @Override
+   public String getRedirectTo() {
+      return redirectTo;
+   }
 }
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 e37fddd..f1e6170 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
@@ -39,6 +39,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQInvalidTransientQueueUseExce
 import org.apache.activemq.artemis.api.core.ActiveMQNonExistentQueueException;
 import org.apache.activemq.artemis.api.core.ActiveMQQueueExistsException;
 import org.apache.activemq.artemis.api.core.ActiveMQQueueMaxConsumerLimitReached;
+import org.apache.activemq.artemis.api.core.ActiveMQRedirectedException;
 import org.apache.activemq.artemis.api.core.ActiveMQReplicationTimeooutException;
 import org.apache.activemq.artemis.api.core.ActiveMQSecurityException;
 import org.apache.activemq.artemis.api.core.ActiveMQSessionCreationException;
@@ -46,6 +47,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQUnexpectedRoutingTypeForAddr
 import org.apache.activemq.artemis.api.core.DiscoveryGroupConfiguration;
 import org.apache.activemq.artemis.api.core.RoutingType;
 import org.apache.activemq.artemis.api.core.SimpleString;
+import org.apache.activemq.artemis.api.core.TransportConfiguration;
 import org.apache.activemq.artemis.core.io.SequentialFile;
 import org.apache.activemq.artemis.core.postoffice.Binding;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.ReplicationSyncFileMessage;
@@ -504,4 +506,13 @@ public interface ActiveMQMessageBundle {
 
    @Message(id = 229235, value = "Incompatible binding with name {0} already exists: {1}", format = Message.Format.MESSAGE_FORMAT)
    ActiveMQIllegalStateException bindingAlreadyExists(String name, String binding);
+
+   @Message(id = 229236, value = "Invalid target key {0}", format = Message.Format.MESSAGE_FORMAT)
+   IllegalArgumentException invalidTargetKey(String val);
+
+   @Message(id = 229237, value = "Connection redirected to {0}", format = Message.Format.MESSAGE_FORMAT)
+   ActiveMQRedirectedException redirectConnection(TransportConfiguration connector);
+
+   @Message(id = 229238, value = "No target to redirect the connection")
+   ActiveMQRedirectedException cannotRedirect();
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServer.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServer.java
index e324979..27cb0d6 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServer.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServer.java
@@ -67,6 +67,7 @@ import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerMessagePlugi
 import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerQueuePlugin;
 import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerResourcePlugin;
 import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerSessionPlugin;
+import org.apache.activemq.artemis.core.server.balancing.BrokerBalancerManager;
 import org.apache.activemq.artemis.core.server.reload.ReloadManager;
 import org.apache.activemq.artemis.core.settings.HierarchicalRepository;
 import org.apache.activemq.artemis.core.settings.impl.AddressSettings;
@@ -943,4 +944,6 @@ public interface ActiveMQServer extends ServiceComponent {
    double getDiskStoreUsage();
 
    void reloadConfigurationFile() throws Exception;
+
+   BrokerBalancerManager getBalancerManager();
 }
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 7d46a0d..bb72f57 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
@@ -40,6 +40,7 @@ import org.apache.activemq.artemis.core.persistence.OperationContext;
 import org.apache.activemq.artemis.core.protocol.core.Packet;
 import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.BackupReplicationStartFailedMessage;
 import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants;
+import org.apache.activemq.artemis.core.server.balancing.targets.Target;
 import org.apache.activemq.artemis.core.server.cluster.Bridge;
 import org.apache.activemq.artemis.core.server.cluster.impl.BridgeImpl;
 import org.apache.activemq.artemis.core.server.cluster.impl.ClusterConnectionImpl;
@@ -47,6 +48,7 @@ import org.apache.activemq.artemis.core.server.cluster.qourum.ServerConnectVote;
 import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl;
 import org.apache.activemq.artemis.core.server.impl.ServerSessionImpl;
 import org.apache.activemq.artemis.core.server.management.Notification;
+import org.apache.activemq.artemis.spi.core.remoting.Connection;
 import org.jboss.logging.BasicLogger;
 import org.jboss.logging.Logger;
 import org.jboss.logging.annotations.Cause;
@@ -451,6 +453,14 @@ public interface ActiveMQServerLogger extends BasicLogger {
    @Message(id = 221084, value = "Requested {0} quorum votes", format = Message.Format.MESSAGE_FORMAT)
    void requestedQuorumVotes(int vote);
 
+   @LogMessage(level = Logger.Level.INFO)
+   @Message(id = 221085, value = "Redirect {0} to {1}", format = Message.Format.MESSAGE_FORMAT)
+   void redirectClientConnection(Connection connection, Target target);
+
+   @LogMessage(level = Logger.Level.INFO)
+   @Message(id = 221086, value = "Cannot redirect {0}", format = Message.Format.MESSAGE_FORMAT)
+   void cannotRedirectClientConnection(Connection connection);
+
    @LogMessage(level = Logger.Level.WARN)
    @Message(id = 222000, value = "ActiveMQServer is being finalized and has not been stopped. Please remember to stop the server before letting it go out of scope",
       format = Message.Format.MESSAGE_FORMAT)
@@ -2156,4 +2166,8 @@ public interface ActiveMQServerLogger extends BasicLogger {
    @LogMessage(level = Logger.Level.WARN)
    @Message(id = 224108, value = "Stopped paging on address ''{0}''; size is currently: {1} bytes; max-size-bytes: {2}; global-size-bytes: {3}", format = Message.Format.MESSAGE_FORMAT)
    void pageStoreStop(SimpleString storeName, long addressSize, long maxSize, long globalMaxSize);
+
+   @LogMessage(level = Logger.Level.WARN)
+   @Message(id = 224109, value = "BrokerBalancer {0} not found", format = Message.Format.MESSAGE_FORMAT)
+   void brokerBalancerNotFound(String name);
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/BrokerBalancer.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/BrokerBalancer.java
new file mode 100644
index 0000000..93e38b1
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/BrokerBalancer.java
@@ -0,0 +1,193 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
+import org.apache.activemq.artemis.core.server.ActiveMQComponent;
+import org.apache.activemq.artemis.core.server.balancing.policies.Policy;
+import org.apache.activemq.artemis.core.server.balancing.pools.Pool;
+import org.apache.activemq.artemis.core.server.balancing.targets.Target;
+import org.apache.activemq.artemis.core.server.balancing.targets.TargetKey;
+import org.apache.activemq.artemis.core.server.balancing.targets.TargetKeyResolver;
+import org.apache.activemq.artemis.spi.core.remoting.Connection;
+import org.jboss.logging.Logger;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+
+public class BrokerBalancer implements ActiveMQComponent {
+   private static final Logger logger = Logger.getLogger(BrokerBalancer.class);
+
+
+   public static final String CLIENT_ID_PREFIX = ActiveMQDefaultConfiguration.DEFAULT_INTERNAL_NAMING_PREFIX + "balancer.client.";
+
+
+   private final String name;
+
+   private final TargetKey targetKey;
+
+   private final TargetKeyResolver targetKeyResolver;
+
+   private final Target localTarget;
+
+   private final Pattern localTargetFilter;
+
+   private final Pool pool;
+
+   private final Policy policy;
+
+   private final Cache<String, Target> cache;
+
+   private volatile boolean started = false;
+
+   public String getName() {
+      return name;
+   }
+
+   public TargetKey getTargetKey() {
+      return targetKey;
+   }
+
+   public Target getLocalTarget() {
+      return localTarget;
+   }
+
+   public String getLocalTargetFilter() {
+      return localTargetFilter != null ? localTargetFilter.pattern() : null;
+   }
+
+   public Pool getPool() {
+      return pool;
+   }
+
+   public Policy getPolicy() {
+      return policy;
+   }
+
+   public Cache<String, Target> getCache() {
+      return cache;
+   }
+
+   @Override
+   public boolean isStarted() {
+      return started;
+   }
+
+
+   public BrokerBalancer(final String name, final TargetKey targetKey, final String targetKeyFilter, final Target localTarget, final String localTargetFilter, final Pool pool, final Policy policy, final int cacheTimeout) {
+      this.name = name;
+
+      this.targetKey = targetKey;
+
+      this.targetKeyResolver = new TargetKeyResolver(targetKey, targetKeyFilter);
+
+      this.localTarget = localTarget;
+
+      this.localTargetFilter = localTargetFilter != null ? Pattern.compile(localTargetFilter) : null;
+
+      this.pool = pool;
+
+      this.policy = policy;
+
+      if (cacheTimeout == -1) {
+         this.cache = CacheBuilder.newBuilder().build();
+      } else if (cacheTimeout > 0) {
+         this.cache = CacheBuilder.newBuilder().expireAfterAccess(cacheTimeout, TimeUnit.MILLISECONDS).build();
+      } else {
+         this.cache = null;
+      }
+   }
+
+   @Override
+   public void start() throws Exception {
+      pool.start();
+
+      started = true;
+   }
+
+   @Override
+   public void stop() throws Exception {
+      started = false;
+
+      pool.stop();
+   }
+
+   public Target getTarget(Connection connection, String clientID, String username) {
+      if (clientID != null && clientID.startsWith(BrokerBalancer.CLIENT_ID_PREFIX)) {
+         if (logger.isDebugEnabled()) {
+            logger.debug("The clientID [" + clientID + "] starts with BrokerBalancer.CLIENT_ID_PREFIX");
+         }
+
+         return localTarget;
+      }
+
+      return getTarget(targetKeyResolver.resolve(connection, clientID, username));
+   }
+
+   public Target getTarget(String key) {
+
+      if (this.localTargetFilter != null && this.localTargetFilter.matcher(key).matches()) {
+         if (logger.isDebugEnabled()) {
+            logger.debug("The " + targetKey + "[" + key + "] matches the localTargetFilter " + localTargetFilter.pattern());
+         }
+
+         return localTarget;
+      }
+
+      Target target = null;
+
+      if (cache != null) {
+         target = cache.getIfPresent(key);
+      }
+
+      if (target != null) {
+         if (pool.isTargetReady(target)) {
+            if (logger.isDebugEnabled()) {
+               logger.debug("The cache returns [" + target + "] ready for " + targetKey + "[" + key + "]");
+            }
+
+            return target;
+         }
+
+         if (logger.isDebugEnabled()) {
+            logger.debug("The cache returns [" + target + "] not ready for " + targetKey + "[" + key + "]");
+         }
+      }
+
+      List<Target> targets = pool.getTargets();
+
+      target = policy.selectTarget(targets, key);
+
+      if (logger.isDebugEnabled()) {
+         logger.debug("The policy selects [" + target + "] from " + targets + " for " + targetKey + "[" + key + "]");
+      }
+
+      if (target != null && cache != null) {
+         if (logger.isDebugEnabled()) {
+            logger.debug("Caching " + targetKey + "[" + key + "] for [" + target + "]");
+         }
+
+         cache.put(key, target);
+      }
+
+      return target;
+   }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/BrokerBalancerManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/BrokerBalancerManager.java
new file mode 100644
index 0000000..fc5fba6
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/BrokerBalancerManager.java
@@ -0,0 +1,191 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing;
+
+import org.apache.activemq.artemis.api.core.DiscoveryGroupConfiguration;
+import org.apache.activemq.artemis.api.core.TransportConfiguration;
+import org.apache.activemq.artemis.core.cluster.DiscoveryGroup;
+import org.apache.activemq.artemis.core.config.balancing.BrokerBalancerConfiguration;
+import org.apache.activemq.artemis.core.config.balancing.PolicyConfiguration;
+import org.apache.activemq.artemis.core.config.balancing.PoolConfiguration;
+import org.apache.activemq.artemis.core.config.Configuration;
+import org.apache.activemq.artemis.core.server.ActiveMQComponent;
+import org.apache.activemq.artemis.core.server.ActiveMQServer;
+import org.apache.activemq.artemis.core.server.balancing.policies.Policy;
+import org.apache.activemq.artemis.core.server.balancing.policies.PolicyFactory;
+import org.apache.activemq.artemis.core.server.balancing.policies.PolicyFactoryResolver;
+import org.apache.activemq.artemis.core.server.balancing.pools.ClusterPool;
+import org.apache.activemq.artemis.core.server.balancing.pools.DiscoveryGroupService;
+import org.apache.activemq.artemis.core.server.balancing.pools.DiscoveryPool;
+import org.apache.activemq.artemis.core.server.balancing.pools.DiscoveryService;
+import org.apache.activemq.artemis.core.server.balancing.pools.Pool;
+import org.apache.activemq.artemis.core.server.balancing.pools.StaticPool;
+import org.apache.activemq.artemis.core.server.balancing.targets.ActiveMQTargetFactory;
+import org.apache.activemq.artemis.core.server.balancing.targets.LocalTarget;
+import org.apache.activemq.artemis.core.server.balancing.targets.Target;
+import org.apache.activemq.artemis.core.server.balancing.targets.TargetFactory;
+import org.apache.activemq.artemis.core.server.cluster.ClusterConnection;
+import org.jboss.logging.Logger;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
+
+public final class BrokerBalancerManager implements ActiveMQComponent {
+   private static final Logger logger = Logger.getLogger(BrokerBalancerManager.class);
+
+
+   private final Configuration config;
+
+   private final ActiveMQServer server;
+
+   private final ScheduledExecutorService scheduledExecutor;
+
+   private volatile boolean started = false;
+
+   private Map<String, BrokerBalancer> balancerControllers = new HashMap<>();
+
+
+   @Override
+   public boolean isStarted() {
+      return started;
+   }
+
+
+   public BrokerBalancerManager(final Configuration config, final ActiveMQServer server, ScheduledExecutorService scheduledExecutor) {
+      this.config = config;
+      this.server = server;
+      this.scheduledExecutor = scheduledExecutor;
+   }
+
+   public void deploy() throws Exception {
+      for (BrokerBalancerConfiguration balancerConfig : config.getBalancerConfigurations()) {
+         deployBrokerBalancer(balancerConfig);
+      }
+   }
+
+   public void deployBrokerBalancer(BrokerBalancerConfiguration config) throws Exception {
+      if (logger.isDebugEnabled()) {
+         logger.debugf("Deploying BrokerBalancer " + config.getName());
+      }
+
+      Target localTarget = new LocalTarget(null, server);
+
+      Pool pool = deployPool(config.getPoolConfiguration(), localTarget);
+
+      Policy policy = deployPolicy(config.getPolicyConfiguration(), pool);
+
+      BrokerBalancer balancer = new BrokerBalancer(config.getName(), config.getTargetKey(), config.getTargetKeyFilter(),
+         localTarget, config.getLocalTargetFilter(), pool, policy, config.getCacheTimeout());
+
+      balancerControllers.put(balancer.getName(), balancer);
+
+      server.getManagementService().registerBrokerBalancer(balancer);
+   }
+
+   private Pool deployPool(PoolConfiguration config, Target localTarget) throws Exception {
+      Pool pool;
+      TargetFactory targetFactory = new ActiveMQTargetFactory();
+
+      targetFactory.setUsername(config.getUsername());
+      targetFactory.setPassword(config.getPassword());
+
+      if (config.getClusterConnection() != null) {
+         ClusterConnection clusterConnection = server.getClusterManager()
+            .getClusterConnection(config.getClusterConnection());
+
+         pool = new ClusterPool(targetFactory, scheduledExecutor, config.getCheckPeriod(), clusterConnection);
+      } else if (config.getDiscoveryGroupName() != null) {
+         DiscoveryGroupConfiguration discoveryGroupConfiguration = server.getConfiguration().
+            getDiscoveryGroupConfigurations().get(config.getDiscoveryGroupName());
+
+         DiscoveryService discoveryService = new DiscoveryGroupService(new DiscoveryGroup(server.getNodeID().toString(), config.getDiscoveryGroupName(),
+            discoveryGroupConfiguration.getRefreshTimeout(), discoveryGroupConfiguration.getBroadcastEndpointFactory(), null));
+
+         pool = new DiscoveryPool(targetFactory, scheduledExecutor, config.getCheckPeriod(), discoveryService);
+      } else if (config.getStaticConnectors() != null) {
+         Map<String, TransportConfiguration> connectorConfigurations =
+            server.getConfiguration().getConnectorConfigurations();
+
+         List<TransportConfiguration> staticConnectors = new ArrayList<>();
+         for (String staticConnector : config.getStaticConnectors()) {
+            TransportConfiguration connector = connectorConfigurations.get(staticConnector);
+
+            if (connector != null) {
+               staticConnectors.add(connector);
+            } else {
+               logger.warn("Static connector not found: " + config.isLocalTargetEnabled());
+            }
+         }
+
+         pool = new StaticPool(targetFactory, scheduledExecutor, config.getCheckPeriod(), staticConnectors);
+      } else {
+         throw new IllegalStateException("Pool configuration not valid");
+      }
+
+      pool.setUsername(config.getUsername());
+      pool.setPassword(config.getPassword());
+      pool.setQuorumSize(config.getQuorumSize());
+      pool.setQuorumTimeout(config.getQuorumTimeout());
+
+      if (config.isLocalTargetEnabled()) {
+         pool.addTarget(localTarget);
+      }
+
+      return pool;
+   }
+
+   private Policy deployPolicy(PolicyConfiguration policyConfig, Pool pool) throws ClassNotFoundException {
+      PolicyFactory policyFactory = PolicyFactoryResolver.getInstance().resolve(policyConfig.getName());
+
+      Policy policy = policyFactory.createPolicy(policyConfig.getName());
+
+      policy.init(policyConfig.getProperties());
+
+      if (policy.getTargetProbe() != null) {
+         pool.addTargetProbe(policy.getTargetProbe());
+      }
+
+      return policy;
+   }
+
+   public BrokerBalancer getBalancer(String name) {
+      return balancerControllers.get(name);
+   }
+
+   @Override
+   public void start() throws Exception {
+      for (BrokerBalancer brokerBalancer : balancerControllers.values()) {
+         brokerBalancer.start();
+      }
+
+      started = true;
+   }
+
+   @Override
+   public void stop() throws Exception {
+      started = false;
+
+      for (BrokerBalancer balancer : balancerControllers.values()) {
+         balancer.stop();
+         server.getManagementService().unregisterBrokerBalancer(balancer.getName());
+      }
+   }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/RedirectContext.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/RedirectContext.java
new file mode 100644
index 0000000..76a2a54
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/RedirectContext.java
@@ -0,0 +1,57 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing;
+
+import org.apache.activemq.artemis.core.server.balancing.targets.Target;
+import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
+
+public class RedirectContext {
+   private final RemotingConnection connection;
+
+   private final String clientID;
+
+   private final String username;
+
+   private Target target;
+
+   public RemotingConnection getConnection() {
+      return connection;
+   }
+
+   public String getClientID() {
+      return clientID;
+   }
+
+   public String getUsername() {
+      return username;
+   }
+
+   public Target getTarget() {
+      return target;
+   }
+
+   public void setTarget(Target target) {
+      this.target = target;
+   }
+
+   public RedirectContext(RemotingConnection connection, String clientID, String username) {
+      this.connection = connection;
+      this.clientID = clientID;
+      this.username = username;
+   }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/RedirectHandler.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/RedirectHandler.java
new file mode 100644
index 0000000..89ace13
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/RedirectHandler.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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing;
+
+import org.apache.activemq.artemis.core.server.ActiveMQServer;
+import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
+import org.apache.activemq.artemis.spi.core.remoting.Connection;
+
+public abstract class RedirectHandler<T extends RedirectContext> {
+   private final ActiveMQServer server;
+
+
+   public ActiveMQServer getServer() {
+      return server;
+   }
+
+
+   protected RedirectHandler(ActiveMQServer server) {
+      this.server = server;
+   }
+
+   protected abstract void cannotRedirect(T context) throws Exception;
+
+   protected abstract void redirectTo(T context) throws Exception;
+
+   protected boolean redirect(T context) throws Exception {
+      Connection transportConnection = context.getConnection().getTransportConnection();
+
+      BrokerBalancer brokerBalancer = getServer().getBalancerManager().getBalancer(transportConnection.getRedirectTo());
+
+      if (brokerBalancer == null) {
+         ActiveMQServerLogger.LOGGER.brokerBalancerNotFound(transportConnection.getRedirectTo());
+
+         cannotRedirect(context);
+
+         return true;
+      }
+
+      context.setTarget(brokerBalancer.getTarget(transportConnection, context.getClientID(), context.getUsername()));
+
+      if (context.getTarget() == null) {
+         ActiveMQServerLogger.LOGGER.cannotRedirectClientConnection(transportConnection);
+
+         cannotRedirect(context);
+
+         return true;
+      }
+
+      ActiveMQServerLogger.LOGGER.redirectClientConnection(transportConnection, context.getTarget());
+
+      if (!context.getTarget().isLocal()) {
+         redirectTo(context);
+
+         return true;
+      }
+
+      return false;
+   }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/AbstractPolicy.java
similarity index 50%
copy from artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
copy to artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/AbstractPolicy.java
index f9e1b3d..8cb7a92 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/AbstractPolicy.java
@@ -1,34 +1,52 @@
-/*
+/**
  * Licensed to the Apache Software Foundation (ASF) under one or more
  * contributor license agreements. See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
  * The ASF licenses this file to You under the Apache License, Version 2.0
  * (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
  * 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;
+
+package org.apache.activemq.artemis.core.server.balancing.policies;
+
+import org.apache.activemq.artemis.core.server.balancing.targets.TargetProbe;
 
 import java.util.Map;
 
-import io.netty.channel.Channel;
-import org.apache.activemq.artemis.spi.core.remoting.ServerConnectionLifeCycleListener;
+public abstract class AbstractPolicy implements Policy {
+   private final String name;
 
-public class NettyServerConnection extends NettyConnection {
+   private Map<String, String> properties;
 
-   public NettyServerConnection(Map<String, Object> configuration,
-                                Channel channel,
-                                ServerConnectionLifeCycleListener listener,
-                                boolean batchingEnabled,
-                                boolean directDeliver) {
-      super(configuration, channel, listener, batchingEnabled, directDeliver);
+   @Override
+   public String getName() {
+      return name;
    }
 
+   @Override
+   public Map<String, String> getProperties() {
+      return properties;
+   }
+
+   @Override
+   public TargetProbe getTargetProbe() {
+      return null;
+   }
+
+   @Override
+   public void init(Map<String, String> properties) {
+      this.properties = properties;
+   }
+
+   public AbstractPolicy(final String name) {
+      this.name = name;
+   }
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/ConsistentHashPolicy.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/ConsistentHashPolicy.java
new file mode 100644
index 0000000..77d4076
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/ConsistentHashPolicy.java
@@ -0,0 +1,75 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.policies;
+
+import org.apache.activemq.artemis.core.server.balancing.targets.Target;
+
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.TreeMap;
+
+public class ConsistentHashPolicy extends AbstractPolicy {
+   public static final String NAME = "CONSISTENT_HASH";
+
+   public ConsistentHashPolicy() {
+      super(NAME);
+   }
+
+   protected ConsistentHashPolicy(String name) {
+      super(name);
+   }
+
+   @Override
+   public Target selectTarget(List<Target> targets, String key) {
+      if (targets.size() > 1) {
+         NavigableMap<Integer, Target> consistentTargets = new TreeMap<>();
+
+         for (Target target : targets) {
+            consistentTargets.put(getHash(target.getNodeID()), target);
+         }
+
+         if (consistentTargets.size() > 0) {
+            Map.Entry<Integer, Target> consistentEntry = consistentTargets.floorEntry(getHash(key));
+
+            if (consistentEntry == null) {
+               consistentEntry = consistentTargets.firstEntry();
+            }
+
+            return consistentEntry.getValue();
+         }
+      } else if (targets.size() > 0) {
+         return targets.get(0);
+      }
+
+      return null;
+   }
+
+   private int getHash(String str) {
+      final int FNV_INIT = 0x811c9dc5;
+      final int FNV_PRIME = 0x01000193;
+
+      int hash = FNV_INIT;
+
+      for (int i = 0; i < str.length(); i++) {
+         hash = (hash ^ str.charAt(i)) * FNV_PRIME;
+      }
+
+      return hash;
+   }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/DefaultPolicyFactory.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/DefaultPolicyFactory.java
new file mode 100644
index 0000000..aa39787
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/DefaultPolicyFactory.java
@@ -0,0 +1,49 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.policies;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Supplier;
+
+public class DefaultPolicyFactory extends PolicyFactory {
+   private static final Map<String, Supplier<AbstractPolicy>> supportedPolicies = new HashMap<>();
+
+   static {
+      supportedPolicies.put(ConsistentHashPolicy.NAME, () -> new ConsistentHashPolicy());
+      supportedPolicies.put(FirstElementPolicy.NAME, () -> new FirstElementPolicy());
+      supportedPolicies.put(LeastConnectionsPolicy.NAME, () -> new LeastConnectionsPolicy());
+      supportedPolicies.put(RoundRobinPolicy.NAME, () -> new RoundRobinPolicy());
+   }
+
+   @Override
+   public String[] getSupportedPolicies() {
+      return supportedPolicies.keySet().toArray(new String[supportedPolicies.size()]);
+   }
+
+   @Override
+   public AbstractPolicy createPolicy(String policyName) {
+      Supplier<AbstractPolicy> policySupplier = supportedPolicies.get(policyName);
+
+      if (policySupplier == null) {
+         throw new IllegalArgumentException("Policy not supported: " + policyName);
+      }
+
+      return policySupplier.get();
+   }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/FirstElementPolicy.java
similarity index 51%
copy from artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
copy to artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/FirstElementPolicy.java
index f9e1b3d..de8bf5b 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/FirstElementPolicy.java
@@ -1,34 +1,43 @@
-/*
+/**
  * 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
- *
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
  * 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.util.Map;
+package org.apache.activemq.artemis.core.server.balancing.policies;
 
-import io.netty.channel.Channel;
-import org.apache.activemq.artemis.spi.core.remoting.ServerConnectionLifeCycleListener;
+import org.apache.activemq.artemis.core.server.balancing.targets.Target;
 
-public class NettyServerConnection extends NettyConnection {
+import java.util.List;
 
-   public NettyServerConnection(Map<String, Object> configuration,
-                                Channel channel,
-                                ServerConnectionLifeCycleListener listener,
-                                boolean batchingEnabled,
-                                boolean directDeliver) {
-      super(configuration, channel, listener, batchingEnabled, directDeliver);
+public class FirstElementPolicy extends AbstractPolicy {
+   public static final String NAME = "FIRST_ELEMENT";
+
+   public FirstElementPolicy() {
+      super(NAME);
+   }
+
+   protected FirstElementPolicy(String name) {
+      super(name);
    }
 
+   @Override
+   public Target selectTarget(List<Target> targets, String key) {
+      if (targets.size() > 0) {
+         return targets.get(0);
+      }
+
+      return null;
+   }
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/LeastConnectionsPolicy.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/LeastConnectionsPolicy.java
new file mode 100644
index 0000000..184cfc9
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/LeastConnectionsPolicy.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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.policies;
+
+import org.apache.activemq.artemis.core.server.balancing.targets.Target;
+import org.apache.activemq.artemis.core.server.balancing.targets.TargetProbe;
+import org.jboss.logging.Logger;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class LeastConnectionsPolicy extends RoundRobinPolicy {
+   private static final Logger logger = Logger.getLogger(LeastConnectionsPolicy.class);
+
+   public static final String NAME = "LEAST_CONNECTIONS";
+
+   public static final String UPDATE_CONNECTION_COUNT_PROBE_NAME = "UPDATE_CONNECTION_COUNT_PROBE";
+
+   public static final String CONNECTION_COUNT_THRESHOLD = "CONNECTION_COUNT_THRESHOLD";
+
+
+   private final Map<Target, Integer> connectionCountCache = new ConcurrentHashMap<>();
+
+
+   private int connectionCountThreshold = 0;
+
+
+   private final TargetProbe targetProbe = new TargetProbe(UPDATE_CONNECTION_COUNT_PROBE_NAME) {
+      @Override
+      public boolean check(Target target) {
+         try {
+            Integer connectionCount = target.getAttribute("broker", "ConnectionCount", Integer.class, 3000);
+
+            if (connectionCount < connectionCountThreshold) {
+               if (logger.isDebugEnabled()) {
+                  logger.debug("Updating the connection count to 0/" + connectionCount + " for the target " + target);
+               }
+
+               connectionCount = 0;
+            } else if (logger.isDebugEnabled()) {
+               logger.debug("Updating the connection count to 0/" + connectionCount + " for the target " + target);
+            }
+
+            connectionCountCache.put(target, connectionCount);
+
+            return true;
+         } catch (Exception e) {
+            logger.warn("Error on updating the connectionCount for the target " + target, e);
+
+            return false;
+         }
+      }
+   };
+
+   @Override
+   public TargetProbe getTargetProbe() {
+      return targetProbe;
+   }
+
+   public LeastConnectionsPolicy() {
+      super(NAME);
+   }
+
+   @Override
+   public void init(Map<String, String> properties) {
+      super.init(properties);
+
+      if (properties != null) {
+         if (properties.containsKey(CONNECTION_COUNT_THRESHOLD)) {
+            connectionCountThreshold = Integer.valueOf(properties.get(CONNECTION_COUNT_THRESHOLD));
+         }
+      }
+   }
+
+   @Override
+   public Target selectTarget(List<Target> targets, String key) {
+      if (targets.size() > 1) {
+         NavigableMap<Integer, List<Target>> sortedTargets = new TreeMap<>();
+
+         for (Target target : targets) {
+            Integer connectionCount = connectionCountCache.get(target);
+
+            if (connectionCount == null) {
+               connectionCount = Integer.MAX_VALUE;
+            }
+
+            List<Target> leastTargets = sortedTargets.get(connectionCount);
+
+            if (leastTargets == null) {
+               leastTargets = new ArrayList<>();
+               sortedTargets.put(connectionCount, leastTargets);
+            }
+
+            leastTargets.add(target);
+         }
+
+         if (logger.isDebugEnabled()) {
+            logger.debug("LeastConnectionsPolicy.sortedTargets: " + sortedTargets);
+         }
+
+         List<Target> selectedTargets = sortedTargets.firstEntry().getValue();
+
+         if (selectedTargets.size() > 1) {
+            return super.selectTarget(selectedTargets, key);
+         } else {
+            return selectedTargets.get(0);
+         }
+      } else if (targets.size() > 0) {
+         return targets.get(0);
+      }
+
+      return null;
+   }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/Policy.java
similarity index 52%
copy from artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
copy to artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/Policy.java
index f9e1b3d..e74f2ea 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/Policy.java
@@ -1,34 +1,36 @@
-/*
+/**
  * 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
- *
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
  * 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;
 
+package org.apache.activemq.artemis.core.server.balancing.policies;
+
+import org.apache.activemq.artemis.core.server.balancing.targets.Target;
+import org.apache.activemq.artemis.core.server.balancing.targets.TargetProbe;
+
+import java.util.List;
 import java.util.Map;
 
-import io.netty.channel.Channel;
-import org.apache.activemq.artemis.spi.core.remoting.ServerConnectionLifeCycleListener;
+public interface Policy {
+   String getName();
+
+   TargetProbe getTargetProbe();
 
-public class NettyServerConnection extends NettyConnection {
+   Map<String, String> getProperties();
 
-   public NettyServerConnection(Map<String, Object> configuration,
-                                Channel channel,
-                                ServerConnectionLifeCycleListener listener,
-                                boolean batchingEnabled,
-                                boolean directDeliver) {
-      super(configuration, channel, listener, batchingEnabled, directDeliver);
-   }
+   void init(Map<String, String> properties);
 
+   Target selectTarget(List<Target> targets, String key);
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/PolicyFactory.java
similarity index 50%
copy from artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
copy to artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/PolicyFactory.java
index f9e1b3d..4c745ee 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/PolicyFactory.java
@@ -1,34 +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
- *
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
  * 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.util.Map;
+package org.apache.activemq.artemis.core.server.balancing.policies;
 
-import io.netty.channel.Channel;
-import org.apache.activemq.artemis.spi.core.remoting.ServerConnectionLifeCycleListener;
-
-public class NettyServerConnection extends NettyConnection {
-
-   public NettyServerConnection(Map<String, Object> configuration,
-                                Channel channel,
-                                ServerConnectionLifeCycleListener listener,
-                                boolean batchingEnabled,
-                                boolean directDeliver) {
-      super(configuration, channel, listener, batchingEnabled, directDeliver);
-   }
+public abstract class PolicyFactory {
+   public abstract String[] getSupportedPolicies();
 
+   public abstract Policy createPolicy(String policyName);
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/PolicyFactoryResolver.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/PolicyFactoryResolver.java
new file mode 100644
index 0000000..dab4f93
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/PolicyFactoryResolver.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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.policies;
+
+import org.apache.activemq.artemis.core.server.balancing.BrokerBalancer;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+public class PolicyFactoryResolver {
+   private static PolicyFactoryResolver instance;
+
+   public static PolicyFactoryResolver getInstance() {
+      if (instance == null) {
+         instance = new PolicyFactoryResolver();
+      }
+      return instance;
+   }
+
+   private final Map<String, PolicyFactory> policyFactories = new HashMap<>();
+
+   private PolicyFactoryResolver() {
+      registerPolicyFactory(new DefaultPolicyFactory());
+
+      loadPolicyFactories();
+   }
+
+   public PolicyFactory resolve(String policyName) throws ClassNotFoundException {
+      PolicyFactory policyFactory = policyFactories.get(policyName);
+
+      if (policyFactory == null) {
+         throw new ClassNotFoundException("No PolicyFactory found for the policy " + policyName);
+      }
+
+      return policyFactory;
+   }
+
+   private void loadPolicyFactories() {
+      ServiceLoader<PolicyFactory> serviceLoader = ServiceLoader.load(
+         PolicyFactory.class, BrokerBalancer.class.getClassLoader());
+
+      for (PolicyFactory policyFactory : serviceLoader) {
+         registerPolicyFactory(policyFactory);
+      }
+   }
+
+   public void registerPolicyFactory(PolicyFactory policyFactory) {
+      for (String policyName : policyFactory.getSupportedPolicies()) {
+         policyFactories.put(policyName, policyFactory);
+      }
+   }
+
+   public void unregisterPolicyFactory(PolicyFactory policyFactory) {
+      for (String policyName : policyFactory.getSupportedPolicies()) {
+         policyFactories.remove(policyName, policyFactory);
+      }
+   }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/RoundRobinPolicy.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/RoundRobinPolicy.java
new file mode 100644
index 0000000..7698d5e
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/policies/RoundRobinPolicy.java
@@ -0,0 +1,47 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.policies;
+
+import org.apache.activemq.artemis.core.server.balancing.targets.Target;
+import org.apache.activemq.artemis.utils.RandomUtil;
+
+import java.util.List;
+
+public class RoundRobinPolicy extends AbstractPolicy {
+   public static final String NAME = "ROUND_ROBIN";
+
+   private int pos = RandomUtil.randomInterval(0, Integer.MAX_VALUE);
+
+   public RoundRobinPolicy() {
+      super(NAME);
+   }
+
+   protected RoundRobinPolicy(String name) {
+      super(name);
+   }
+
+   @Override
+   public Target selectTarget(List<Target> targets, String key) {
+      if (targets.size() > 0) {
+         pos = pos % targets.size();
+         return targets.get(pos++);
+      }
+
+      return null;
+   }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/AbstractPool.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/AbstractPool.java
new file mode 100644
index 0000000..5ab6145
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/AbstractPool.java
@@ -0,0 +1,243 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.pools;
+
+import org.apache.activemq.artemis.api.core.TransportConfiguration;
+import org.apache.activemq.artemis.core.server.balancing.targets.Target;
+import org.apache.activemq.artemis.core.server.balancing.targets.TargetFactory;
+import org.apache.activemq.artemis.core.server.balancing.targets.TargetMonitor;
+import org.apache.activemq.artemis.core.server.balancing.targets.TargetProbe;
+import org.jboss.logging.Logger;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.LockSupport;
+import java.util.stream.Collectors;
+
+public abstract class AbstractPool implements Pool {
+   private static final Logger logger = Logger.getLogger(AbstractPool.class);
+
+   private final TargetFactory targetFactory;
+
+   private final ScheduledExecutorService scheduledExecutor;
+
+   private final int checkPeriod;
+
+   private final List<TargetProbe> targetProbes = new ArrayList<>();
+
+   private final Map<Target, TargetMonitor> targets = new ConcurrentHashMap<>();
+
+   private final List<TargetMonitor> targetMonitors = new CopyOnWriteArrayList<>();
+
+   private String username;
+
+   private String password;
+
+   private int quorumSize;
+
+   private int quorumTimeout;
+
+   private long quorumTimeoutNanos;
+
+   private final long quorumParkNanos = TimeUnit.MILLISECONDS.toNanos(100);
+
+   private volatile boolean started = false;
+
+
+   @Override
+   public String getUsername() {
+      return username;
+   }
+
+   @Override
+   public void setUsername(String username) {
+      this.username = username;
+   }
+
+   @Override
+   public String getPassword() {
+      return password;
+   }
+
+   @Override
+   public void setPassword(String password) {
+      this.password = password;
+   }
+
+   @Override
+   public int getCheckPeriod() {
+      return checkPeriod;
+   }
+
+   @Override
+   public int getQuorumSize() {
+      return quorumSize;
+   }
+
+   @Override
+   public int getQuorumTimeout() {
+      return quorumTimeout;
+   }
+
+   @Override
+   public void setQuorumTimeout(int quorumTimeout) {
+      this.quorumTimeout = quorumTimeout;
+      this.quorumTimeoutNanos = TimeUnit.MILLISECONDS.toNanos(quorumTimeout);
+   }
+
+   @Override
+   public void setQuorumSize(int quorumSize) {
+      this.quorumSize = quorumSize;
+   }
+
+   @Override
+   public List<Target> getAllTargets() {
+      return targetMonitors.stream().map(targetMonitor -> targetMonitor.getTarget()).collect(Collectors.toList());
+   }
+
+   @Override
+   public List<Target> getTargets() {
+      List<Target> targets = targetMonitors.stream().filter(targetMonitor -> targetMonitor.isTargetReady())
+         .map(targetMonitor -> targetMonitor.getTarget()).collect(Collectors.toList());
+
+      if (quorumTimeout > 0 && targets.size() < quorumSize) {
+         final long deadline = System.nanoTime() + quorumTimeoutNanos;
+         while (targets.size() < quorumSize && (System.nanoTime() - deadline) < 0) {
+            targets = targetMonitors.stream().filter(targetMonitor -> targetMonitor.isTargetReady())
+               .map(targetMonitor -> targetMonitor.getTarget()).collect(Collectors.toList());
+
+            LockSupport.parkNanos(quorumParkNanos);
+         }
+      }
+
+      if (logger.isDebugEnabled()) {
+         logger.debugf("Ready targets are " + targets + " / " + targetMonitors + " and quorumSize is " + quorumSize);
+      }
+
+      return targets.size() < quorumSize ? Collections.emptyList() : targets;
+   }
+
+   @Override
+   public List<TargetProbe> getTargetProbes() {
+      return targetProbes;
+   }
+
+   @Override
+   public boolean isStarted() {
+      return started;
+   }
+
+
+   public AbstractPool(TargetFactory targetFactory, ScheduledExecutorService scheduledExecutor, int checkPeriod) {
+      this.targetFactory = targetFactory;
+
+      this.scheduledExecutor = scheduledExecutor;
+
+      this.checkPeriod = checkPeriod;
+   }
+
+   @Override
+   public Target getTarget(String nodeId) {
+      for (TargetMonitor targetMonitor : targetMonitors) {
+         if (nodeId.equals(targetMonitor.getTarget().getNodeID())) {
+            return targetMonitor.getTarget();
+         }
+      }
+
+      return null;
+   }
+
+   @Override
+   public boolean isTargetReady(Target target) {
+      TargetMonitor targetMonitor = targets.get(target);
+
+      return targetMonitor != null ? targetMonitor.isTargetReady() : false;
+   }
+
+   @Override
+   public void addTargetProbe(TargetProbe probe) {
+      targetProbes.add(probe);
+   }
+
+   @Override
+   public void removeTargetProbe(TargetProbe probe) {
+      targetProbes.remove(probe);
+   }
+
+   @Override
+   public void start() throws Exception {
+      started = true;
+
+      for (TargetMonitor targetMonitor : targetMonitors) {
+         targetMonitor.start();
+      }
+   }
+
+   @Override
+   public void stop() throws Exception {
+      started = false;
+
+      List<TargetMonitor> targetMonitors = new ArrayList<>(this.targetMonitors);
+
+      for (TargetMonitor targetMonitor : targetMonitors) {
+         removeTarget(targetMonitor.getTarget());
+      }
+   }
+
+   protected void addTarget(TransportConfiguration connector, String nodeID) {
+      addTarget(targetFactory.createTarget(connector, nodeID));
+   }
+
+   @Override
+   public boolean addTarget(Target target) {
+      TargetMonitor targetMonitor = new TargetMonitor(scheduledExecutor, checkPeriod, target, targetProbes);
+
+      if (targets.putIfAbsent(target, targetMonitor) != null) {
+         return false;
+      }
+
+      targetMonitors.add(targetMonitor);
+
+      if (started) {
+         targetMonitor.start();
+      }
+
+      return true;
+   }
+
+   @Override
+   public boolean removeTarget(Target target) {
+      TargetMonitor targetMonitor = targets.remove(target);
+
+      if (targetMonitor == null) {
+         return false;
+      }
+
+      targetMonitors.remove(targetMonitor);
+
+      targetMonitor.stop();
+
+      return true;
+   }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/ClusterPool.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/ClusterPool.java
new file mode 100644
index 0000000..e2a7a28
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/ClusterPool.java
@@ -0,0 +1,69 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.pools;
+
+import org.apache.activemq.artemis.api.core.client.ClusterTopologyListener;
+import org.apache.activemq.artemis.api.core.client.TopologyMember;
+import org.apache.activemq.artemis.core.server.balancing.targets.TargetFactory;
+import org.apache.activemq.artemis.core.server.cluster.ClusterConnection;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledExecutorService;
+
+public class ClusterPool extends AbstractPool implements ClusterTopologyListener {
+   private final ClusterConnection clusterConnection;
+
+   private final Map<String, TopologyMember> clusterMembers = new ConcurrentHashMap<>();
+
+   public ClusterPool(TargetFactory targetFactory, ScheduledExecutorService scheduledExecutor,
+                      int checkPeriod, ClusterConnection clusterConnection) {
+      super(targetFactory, scheduledExecutor, checkPeriod);
+
+      this.clusterConnection = clusterConnection;
+   }
+
+   @Override
+   public void start() throws Exception {
+      super.start();
+
+      clusterConnection.addClusterTopologyListener(this);
+   }
+
+   @Override
+   public void stop() throws Exception {
+      clusterConnection.removeClusterTopologyListener(this);
+
+      super.stop();
+   }
+
+   @Override
+   public void nodeUP(TopologyMember member, boolean last) {
+      if (!clusterConnection.getNodeID().equals(member.getNodeId()) &&
+         clusterMembers.putIfAbsent(member.getNodeId(), member) == null) {
+         addTarget(member.getLive(), member.getNodeId());
+      }
+   }
+
+   @Override
+   public void nodeDown(long eventUID, String nodeID) {
+      if (clusterMembers.remove(nodeID) != null) {
+         removeTarget(getTarget(nodeID));
+      }
+   }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryGroupService.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryGroupService.java
new file mode 100644
index 0000000..47fbc6e
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryGroupService.java
@@ -0,0 +1,87 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.pools;
+
+import org.apache.activemq.artemis.core.cluster.DiscoveryEntry;
+import org.apache.activemq.artemis.core.cluster.DiscoveryGroup;
+import org.apache.activemq.artemis.core.cluster.DiscoveryListener;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class DiscoveryGroupService extends DiscoveryService implements DiscoveryListener {
+   private final DiscoveryGroup discoveryGroup;
+
+   private final Map<String, Entry> entries = new ConcurrentHashMap<>();
+
+   public DiscoveryGroupService(DiscoveryGroup discoveryGroup) {
+      this.discoveryGroup = discoveryGroup;
+   }
+
+   @Override
+   public void start() throws Exception {
+      discoveryGroup.registerListener(this);
+
+      discoveryGroup.start();
+   }
+
+   @Override
+   public void stop() throws Exception {
+      discoveryGroup.unregisterListener(this);
+
+      discoveryGroup.stop();
+
+      entries.clear();
+   }
+
+   @Override
+   public boolean isStarted() {
+      return discoveryGroup.isStarted();
+   }
+
+   @Override
+   public void connectorsChanged(List<DiscoveryEntry> newEntries) {
+      Map<String, Entry> oldEntries = new HashMap<>(entries);
+
+      for (DiscoveryEntry newEntry : newEntries) {
+         Entry oldEntry = oldEntries.remove(newEntry.getNodeID());
+
+         if (oldEntry == null) {
+            Entry addingEntry = new Entry(newEntry.getNodeID(), newEntry.getConnector());
+
+            entries.put(addingEntry.getNodeID(), addingEntry);
+
+            fireEntryAddedEvent(addingEntry);
+         } else if (!newEntry.getConnector().equals(oldEntry.getConnector())) {
+            Entry updatingEntry = new Entry(newEntry.getNodeID(), newEntry.getConnector());
+
+            entries.put(updatingEntry.getNodeID(), updatingEntry);
+
+            fireEntryUpdatedEvent(oldEntry, updatingEntry);
+         }
+      }
+
+      oldEntries.forEach((nodeID, entry) -> {
+         entries.remove(nodeID);
+
+         fireEntryRemovedEvent(entry);
+      });
+   }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryPool.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryPool.java
new file mode 100644
index 0000000..03c3a8c
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryPool.java
@@ -0,0 +1,70 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.pools;
+
+import org.apache.activemq.artemis.core.server.balancing.targets.TargetFactory;
+
+import java.util.concurrent.ScheduledExecutorService;
+
+public class DiscoveryPool extends AbstractPool implements DiscoveryService.Listener {
+   private final DiscoveryService discoveryService;
+
+   public DiscoveryPool(TargetFactory targetFactory, ScheduledExecutorService scheduledExecutor,
+                        int checkPeriod, DiscoveryService discoveryService) {
+      super(targetFactory, scheduledExecutor, checkPeriod);
+
+      this.discoveryService = discoveryService;
+   }
+
+   @Override
+   public void start() throws Exception {
+      super.start();
+
+      discoveryService.setListener(this);
+
+      discoveryService.start();
+   }
+
+   @Override
+   public void stop() throws Exception {
+      super.stop();
+
+      if (discoveryService != null) {
+         discoveryService.setListener(null);
+
+         discoveryService.stop();
+      }
+   }
+
+   @Override
+   public void entryAdded(DiscoveryService.Entry entry) {
+      addTarget(entry.getConnector(), entry.getNodeID());
+   }
+
+   @Override
+   public void entryRemoved(DiscoveryService.Entry entry) {
+      removeTarget(getTarget(entry.getNodeID()));
+   }
+
+   @Override
+   public void entryUpdated(DiscoveryService.Entry oldEntry, DiscoveryService.Entry newEntry) {
+      removeTarget(getTarget(oldEntry.getNodeID()));
+
+      addTarget(newEntry.getConnector(), newEntry.getNodeID());
+   }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryService.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryService.java
new file mode 100644
index 0000000..a45fede
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryService.java
@@ -0,0 +1,88 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.pools;
+
+import org.apache.activemq.artemis.api.core.TransportConfiguration;
+import org.apache.activemq.artemis.core.server.ActiveMQComponent;
+
+public abstract class DiscoveryService implements ActiveMQComponent {
+
+   private Listener listener;
+
+   public Listener getListener() {
+      return listener;
+   }
+
+   public void setListener(Listener listener) {
+      this.listener = listener;
+   }
+
+   protected void fireEntryAddedEvent(Entry entry) {
+      if (listener != null) {
+         this.listener.entryAdded(entry);
+      }
+   }
+
+   protected void fireEntryRemovedEvent(Entry entry) {
+      if (listener != null) {
+         this.listener.entryRemoved(entry);
+      }
+   }
+
+   protected void fireEntryUpdatedEvent(Entry oldEntry, Entry newEntry) {
+      if (listener != null) {
+         this.listener.entryUpdated(oldEntry, newEntry);
+      }
+   }
+
+
+   public interface Listener {
+      void entryAdded(Entry entry);
+
+      void entryRemoved(Entry entry);
+
+      void entryUpdated(Entry oldEntry, Entry newEntry);
+   }
+
+   public class Entry {
+      private final String nodeID;
+      private final TransportConfiguration connector;
+
+      public String getNodeID() {
+         return nodeID;
+      }
+
+      public TransportConfiguration getConnector() {
+         return connector;
+      }
+
+      public Entry(String nodeID, TransportConfiguration connector) {
+         this.nodeID = nodeID;
+         this.connector = connector;
+      }
+
+      @Override
+      public String toString() {
+         StringBuilder stringBuilder = new StringBuilder(Entry.class.getSimpleName());
+         stringBuilder.append("(nodeID=" + nodeID);
+         stringBuilder.append(", connector=" + connector);
+         stringBuilder.append(") ");
+         return stringBuilder.toString();
+      }
+   }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/Pool.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/Pool.java
new file mode 100644
index 0000000..db6b733
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/Pool.java
@@ -0,0 +1,65 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.pools;
+
+import org.apache.activemq.artemis.core.server.ActiveMQComponent;
+import org.apache.activemq.artemis.core.server.balancing.targets.Target;
+import org.apache.activemq.artemis.core.server.balancing.targets.TargetProbe;
+
+import java.util.List;
+
+public interface Pool extends ActiveMQComponent {
+   String getUsername();
+
+   void setUsername(String username);
+
+   String getPassword();
+
+   void setPassword(String password);
+
+   int getQuorumSize();
+
+   void setQuorumSize(int quorumSize);
+
+   int getQuorumTimeout();
+
+   void setQuorumTimeout(int quorumTimeout);
+
+   int getCheckPeriod();
+
+
+
+   Target getTarget(String nodeId);
+
+   boolean isTargetReady(Target target);
+
+   List<Target> getTargets();
+
+   List<Target> getAllTargets();
+
+   boolean addTarget(Target target);
+
+   boolean removeTarget(Target target);
+
+
+   List<TargetProbe> getTargetProbes();
+
+   void addTargetProbe(TargetProbe probe);
+
+   void removeTargetProbe(TargetProbe probe);
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/StaticPool.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/StaticPool.java
new file mode 100644
index 0000000..c652d4a
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/pools/StaticPool.java
@@ -0,0 +1,44 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.pools;
+
+import org.apache.activemq.artemis.api.core.TransportConfiguration;
+import org.apache.activemq.artemis.core.server.balancing.targets.TargetFactory;
+
+import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
+
+public class StaticPool extends AbstractPool {
+   private final List<TransportConfiguration> staticConnectors;
+
+   public StaticPool(TargetFactory targetFactory, ScheduledExecutorService scheduledExecutor,
+                     int checkPeriod, List<TransportConfiguration> staticConnectors) {
+      super(targetFactory, scheduledExecutor, checkPeriod);
+
+      this.staticConnectors = staticConnectors;
+   }
+
+   @Override
+   public void start() throws Exception {
+      super.start();
+
+      for (TransportConfiguration staticConnector : staticConnectors) {
+         addTarget(staticConnector, null);
+      }
+   }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/AbstractTarget.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/AbstractTarget.java
new file mode 100644
index 0000000..3f123e5
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/AbstractTarget.java
@@ -0,0 +1,112 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.targets;
+
+import org.apache.activemq.artemis.api.core.TransportConfiguration;
+
+public abstract class AbstractTarget implements Target {
+   private final TransportConfiguration connector;
+
+   private String nodeID;
+
+   private String username;
+
+   private String password;
+
+   private int checkPeriod;
+
+   private TargetListener listener;
+
+   @Override
+   public String getNodeID() {
+      return nodeID;
+   }
+
+   protected void setNodeID(String nodeID) {
+      this.nodeID = nodeID;
+   }
+
+   @Override
+   public String getUsername() {
+      return username;
+   }
+
+   @Override
+   public void setUsername(String username) {
+      this.username = username;
+   }
+
+   @Override
+   public String getPassword() {
+      return password;
+   }
+
+   @Override
+   public void setPassword(String password) {
+      this.password = password;
+   }
+
+   @Override
+   public int getCheckPeriod() {
+      return checkPeriod;
+   }
+
+   @Override
+   public void setCheckPeriod(int checkPeriod) {
+      this.checkPeriod = checkPeriod;
+   }
+
+   @Override
+   public TargetListener getListener() {
+      return listener;
+   }
+
+   @Override
+   public void setListener(TargetListener listener) {
+      this.listener = listener;
+   }
+
+   @Override
+   public TransportConfiguration getConnector() {
+      return connector;
+   }
+
+
+   public AbstractTarget(TransportConfiguration connector, String nodeID) {
+      this.connector = connector;
+      this.nodeID = nodeID;
+   }
+
+
+   protected void fireConnectedEvent() {
+      if (listener != null) {
+         listener.targetConnected();
+      }
+   }
+
+   protected void fireDisconnectedEvent() {
+      if (listener != null) {
+         listener.targetDisconnected();
+      }
+   }
+
+   @Override
+   public String toString() {
+      return this.getClass().getSimpleName() + " [connector=" + connector + ", nodeID=" + nodeID + "]";
+   }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/AbstractTargetFactory.java
similarity index 51%
copy from artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
copy to artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/AbstractTargetFactory.java
index f9e1b3d..9ca15f3 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/AbstractTargetFactory.java
@@ -1,34 +1,45 @@
-/*
+/**
  * 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
- *
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
  * 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.util.Map;
+package org.apache.activemq.artemis.core.server.balancing.targets;
 
-import io.netty.channel.Channel;
-import org.apache.activemq.artemis.spi.core.remoting.ServerConnectionLifeCycleListener;
+public abstract class AbstractTargetFactory implements TargetFactory {
 
-public class NettyServerConnection extends NettyConnection {
+   private String username;
 
-   public NettyServerConnection(Map<String, Object> configuration,
-                                Channel channel,
-                                ServerConnectionLifeCycleListener listener,
-                                boolean batchingEnabled,
-                                boolean directDeliver) {
-      super(configuration, channel, listener, batchingEnabled, directDeliver);
+   private String password;
+
+   @Override
+   public String getUsername() {
+      return username;
+   }
+
+   @Override
+   public void setUsername(String username) {
+      this.username = username;
    }
 
+   @Override
+   public String getPassword() {
+      return password;
+   }
+
+   @Override
+   public void setPassword(String password) {
+      this.password = password;
+   }
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/ActiveMQTarget.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/ActiveMQTarget.java
new file mode 100644
index 0000000..0fc1cad
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/ActiveMQTarget.java
@@ -0,0 +1,132 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.targets;
+
+import org.apache.activemq.artemis.api.core.ActiveMQException;
+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.ClientSessionFactory;
+import org.apache.activemq.artemis.api.core.client.ServerLocator;
+import org.apache.activemq.artemis.api.core.management.ActiveMQManagementProxy;
+import org.apache.activemq.artemis.api.core.management.ResourceNames;
+import org.apache.activemq.artemis.core.remoting.FailureListener;
+import org.apache.activemq.artemis.core.server.balancing.BrokerBalancer;
+import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
+import org.apache.activemq.artemis.utils.UUIDGenerator;
+import org.jboss.logging.Logger;
+
+public class ActiveMQTarget extends AbstractTarget implements FailureListener {
+   private static final Logger logger = Logger.getLogger(ActiveMQTarget.class);
+
+   private boolean connected = false;
+
+   private final ServerLocator serverLocator;
+
+   private ClientSessionFactory sessionFactory;
+   private RemotingConnection remotingConnection;
+   private ActiveMQManagementProxy managementProxy;
+
+   @Override
+   public boolean isLocal() {
+      return false;
+   }
+
+   @Override
+   public boolean isConnected() {
+      return connected;
+   }
+
+   public ActiveMQTarget(TransportConfiguration connector, String nodeID) {
+      super(connector, nodeID);
+
+      serverLocator = ActiveMQClient.createServerLocatorWithoutHA(connector);
+   }
+
+
+   @Override
+   public void connect() throws Exception {
+      sessionFactory = serverLocator.createSessionFactory();
+
+      remotingConnection = sessionFactory.getConnection();
+      remotingConnection.addFailureListener(this);
+
+      managementProxy = new ActiveMQManagementProxy(sessionFactory.createSession(getUsername(), getPassword(),
+         false, true, true, false, ActiveMQClient.DEFAULT_ACK_BATCH_SIZE,
+         BrokerBalancer.CLIENT_ID_PREFIX + UUIDGenerator.getInstance().generateStringUUID()).start());
+
+      connected = true;
+
+      fireConnectedEvent();
+   }
+
+   @Override
+   public void disconnect() throws Exception {
+      if (connected) {
+         connected = false;
+
+         managementProxy.close();
+
+         remotingConnection.removeFailureListener(this);
+
+         sessionFactory.close();
+
+         fireDisconnectedEvent();
+      }
+   }
+
+   @Override
+   public boolean checkReadiness() {
+      try {
+         if (getNodeID() == null) {
+            setNodeID(getAttribute(ResourceNames.BROKER, "NodeID", String.class, 3000));
+         }
+
+         return getAttribute(ResourceNames.BROKER, "Active", Boolean.class, 3000);
+      } catch (Exception e) {
+         logger.warn("Error on check readiness", e);
+      }
+
+      return false;
+   }
+
+   @Override
+   public <T> T getAttribute(String resourceName, String attributeName, Class<T> attributeClass, int timeout) throws Exception {
+      return managementProxy.getAttribute(resourceName, attributeName, attributeClass, timeout);
+   }
+
+   @Override
+   public <T> T invokeOperation(String resourceName, String operationName, Object[] operationParams, Class<T> operationClass, int timeout) throws Exception {
+      return managementProxy.invokeOperation(resourceName, operationName, operationParams, operationClass, timeout);
+   }
+
+   @Override
+   public void connectionFailed(ActiveMQException exception, boolean failedOver) {
+      connectionFailed(exception, failedOver, null);
+   }
+
+   @Override
+   public void connectionFailed(ActiveMQException exception, boolean failedOver, String scaleDownTargetNodeID) {
+      try {
+         if (connected) {
+            disconnect();
+         }
+      } catch (Exception e) {
+         logger.debug("Exception on disconnecting: ", e);
+      }
+   }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/ActiveMQTargetFactory.java
similarity index 51%
copy from artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
copy to artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/ActiveMQTargetFactory.java
index f9e1b3d..b7cb9f9 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/ActiveMQTargetFactory.java
@@ -1,34 +1,32 @@
-/*
+/**
  * 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
- *
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
  * 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.util.Map;
+package org.apache.activemq.artemis.core.server.balancing.targets;
 
-import io.netty.channel.Channel;
-import org.apache.activemq.artemis.spi.core.remoting.ServerConnectionLifeCycleListener;
+import org.apache.activemq.artemis.api.core.TransportConfiguration;
 
-public class NettyServerConnection extends NettyConnection {
+public class ActiveMQTargetFactory extends AbstractTargetFactory {
+   @Override
+   public Target createTarget(TransportConfiguration connector, String nodeID) {
+      Target target = new ActiveMQTarget(connector, nodeID);
 
-   public NettyServerConnection(Map<String, Object> configuration,
-                                Channel channel,
-                                ServerConnectionLifeCycleListener listener,
-                                boolean batchingEnabled,
-                                boolean directDeliver) {
-      super(configuration, channel, listener, batchingEnabled, directDeliver);
-   }
+      target.setUsername(getUsername());
+      target.setPassword(getPassword());
 
+      return target;
+   }
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/LocalTarget.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/LocalTarget.java
new file mode 100644
index 0000000..86043fe
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/LocalTarget.java
@@ -0,0 +1,69 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.targets;
+
+import org.apache.activemq.artemis.api.core.TransportConfiguration;
+import org.apache.activemq.artemis.core.server.ActiveMQServer;
+import org.apache.activemq.artemis.core.server.management.ManagementService;
+
+public class LocalTarget extends AbstractTarget {
+   private final ActiveMQServer server;
+   private final ManagementService managementService;
+
+   public LocalTarget(TransportConfiguration connector, ActiveMQServer server) {
+      super(connector, server.getNodeID().toString());
+
+      this.server = server;
+      this.managementService = server.getManagementService();
+   }
+
+   @Override
+   public boolean isLocal() {
+      return true;
+   }
+
+   @Override
+   public boolean isConnected() {
+      return true;
+   }
+
+   @Override
+   public void connect() throws Exception {
+
+   }
+
+   @Override
+   public void disconnect() throws Exception {
+
+   }
+
+   @Override
+   public boolean checkReadiness() {
+      return true;
+   }
+
+   @Override
+   public <T> T getAttribute(String resourceName, String attributeName, Class<T> attributeClass, int timeout) throws Exception {
+      return (T)managementService.getAttribute(resourceName, attributeName);
+   }
+
+   @Override
+   public <T> T invokeOperation(String resourceName, String operationName, Object[] operationParams, Class<T> operationClass, int timeout) throws Exception {
+      return (T)managementService.invokeOperation(resourceName, operationName, operationParams);
+   }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/Target.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/Target.java
new file mode 100644
index 0000000..f82331b
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/Target.java
@@ -0,0 +1,59 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.targets;
+
+import org.apache.activemq.artemis.api.core.TransportConfiguration;
+
+public interface Target {
+
+   boolean isLocal();
+
+   String getNodeID();
+
+   TransportConfiguration getConnector();
+
+   String getUsername();
+
+   void setUsername(String username);
+
+   String getPassword();
+
+   void setPassword(String password);
+
+   int getCheckPeriod();
+
+   void setCheckPeriod(int checkPeriod);
+
+   TargetListener getListener();
+
+   void setListener(TargetListener listener);
+
+   boolean isConnected();
+
+   void connect() throws Exception;
+
+   void disconnect() throws Exception;
+
+
+   boolean checkReadiness();
+
+
+   <T> T getAttribute(String resourceName, String attributeName, Class<T> attributeClass, int timeout) throws Exception;
+
+   <T> T invokeOperation(String resourceName, String operationName, Object[] operationParams, Class<T> operationClass, int timeout) throws Exception;
+}
\ No newline at end of file
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetFactory.java
similarity index 51%
copy from artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
copy to artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetFactory.java
index f9e1b3d..508be40 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetFactory.java
@@ -1,34 +1,32 @@
-/*
+/**
  * 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
- *
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
  * 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.util.Map;
+package org.apache.activemq.artemis.core.server.balancing.targets;
 
-import io.netty.channel.Channel;
-import org.apache.activemq.artemis.spi.core.remoting.ServerConnectionLifeCycleListener;
+import org.apache.activemq.artemis.api.core.TransportConfiguration;
 
-public class NettyServerConnection extends NettyConnection {
+public interface TargetFactory {
+   String getUsername();
 
-   public NettyServerConnection(Map<String, Object> configuration,
-                                Channel channel,
-                                ServerConnectionLifeCycleListener listener,
-                                boolean batchingEnabled,
-                                boolean directDeliver) {
-      super(configuration, channel, listener, batchingEnabled, directDeliver);
-   }
+   void setUsername(String username);
 
+   String getPassword();
+
+   void setPassword(String password);
+
+   Target createTarget(TransportConfiguration connector, String nodeID);
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetKey.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetKey.java
new file mode 100644
index 0000000..d01b932
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetKey.java
@@ -0,0 +1,53 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.targets;
+
+public enum TargetKey {
+   CLIENT_ID, SNI_HOST, SOURCE_IP, USER_NAME;
+
+   public static final String validValues;
+
+   static {
+      StringBuffer stringBuffer = new StringBuffer();
+      for (TargetKey type : TargetKey.values()) {
+
+         if (stringBuffer.length() != 0) {
+            stringBuffer.append(",");
+         }
+
+         stringBuffer.append(type.name());
+      }
+
+      validValues = stringBuffer.toString();
+   }
+
+   public static TargetKey getType(String type) {
+      switch (type) {
+         case "CLIENT_ID":
+            return CLIENT_ID;
+         case "SNI_HOST":
+            return SNI_HOST;
+         case "SOURCE_IP":
+            return SOURCE_IP;
+         case "USER_NAME":
+            return USER_NAME;
+         default:
+            throw new IllegalStateException("Invalid RedirectKey:" + type + " valid Types: " + validValues);
+      }
+   }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetKeyResolver.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetKeyResolver.java
new file mode 100644
index 0000000..dac82de
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetKeyResolver.java
@@ -0,0 +1,108 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.targets;
+
+import org.apache.activemq.artemis.spi.core.remoting.Connection;
+import org.jboss.logging.Logger;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class TargetKeyResolver {
+   public static final String DEFAULT_KEY_VALUE = "DEFAULT";
+
+
+   private static final Logger logger = Logger.getLogger(TargetKeyResolver.class);
+
+   private static final char SOCKET_ADDRESS_DELIMITER = ':';
+   private static final String SOCKET_ADDRESS_PREFIX = "/";
+
+
+   private final TargetKey key;
+
+   private final Pattern keyFilter;
+
+
+   public TargetKey getKey() {
+      return key;
+   }
+
+   public String getKeyFilter() {
+      return keyFilter != null ? keyFilter.pattern() : null;
+   }
+
+   public TargetKeyResolver(TargetKey key, String keyFilter) {
+      this.key = key;
+
+      this.keyFilter = keyFilter != null ? Pattern.compile(keyFilter) : null;
+   }
+
+   public String resolve(Connection connection, String clientID, String username) {
+      String keyValue = null;
+
+      switch (key) {
+         case CLIENT_ID:
+            keyValue = clientID;
+            break;
+         case SNI_HOST:
+            if (connection != null) {
+               keyValue = connection.getSNIHostName();
+            }
+            break;
+         case SOURCE_IP:
+            if (connection != null &&  connection.getRemoteAddress() != null) {
+               keyValue = connection.getRemoteAddress();
+
+               boolean hasPrefix = keyValue.startsWith(SOCKET_ADDRESS_PREFIX);
+               int delimiterIndex = keyValue.lastIndexOf(SOCKET_ADDRESS_DELIMITER);
+
+               if (hasPrefix || delimiterIndex > 0) {
+                  keyValue = keyValue.substring(hasPrefix ? SOCKET_ADDRESS_PREFIX.length() : 0,
+                     delimiterIndex > 0 ? delimiterIndex : keyValue.length());
+               }
+            }
+            break;
+         case USER_NAME:
+            keyValue = username;
+            break;
+         default:
+            throw new IllegalStateException("Unexpected value: " + key);
+      }
+
+      if (logger.isDebugEnabled()) {
+         logger.debugf("keyValue for %s: %s", key, keyValue);
+      }
+
+      if (keyValue == null) {
+         keyValue = DEFAULT_KEY_VALUE;
+      } else if (keyFilter != null) {
+         Matcher keyMatcher = keyFilter.matcher(keyValue);
+
+         if (keyMatcher.find()) {
+            keyValue = keyMatcher.group();
+
+            if (logger.isDebugEnabled()) {
+               logger.debugf("keyValue with filter %s: %s", keyFilter, keyValue);
+            }
+         }
+      }
+
+
+      return keyValue;
+   }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetListener.java
similarity index 50%
copy from artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
copy to artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetListener.java
index f9e1b3d..266d736 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetListener.java
@@ -1,34 +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
- *
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
  * 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.util.Map;
+package org.apache.activemq.artemis.core.server.balancing.targets;
 
-import io.netty.channel.Channel;
-import org.apache.activemq.artemis.spi.core.remoting.ServerConnectionLifeCycleListener;
-
-public class NettyServerConnection extends NettyConnection {
-
-   public NettyServerConnection(Map<String, Object> configuration,
-                                Channel channel,
-                                ServerConnectionLifeCycleListener listener,
-                                boolean batchingEnabled,
-                                boolean directDeliver) {
-      super(configuration, channel, listener, batchingEnabled, directDeliver);
-   }
+public interface TargetListener {
+   void targetConnected();
 
+   void targetDisconnected();
 }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetMonitor.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetMonitor.java
new file mode 100644
index 0000000..b1865da
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetMonitor.java
@@ -0,0 +1,129 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.targets;
+
+import org.apache.activemq.artemis.core.server.ActiveMQScheduledComponent;
+import org.jboss.logging.Logger;
+
+import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public class TargetMonitor extends ActiveMQScheduledComponent implements TargetListener {
+   private static final Logger logger = Logger.getLogger(TargetMonitor.class);
+
+
+   private final Target target;
+
+   private final List<TargetProbe> targetProbes;
+
+   private volatile boolean targetReady = false;
+
+
+   public Target getTarget() {
+      return target;
+   }
+
+   public boolean isTargetReady() {
+      return targetReady;
+   }
+
+
+   public TargetMonitor(ScheduledExecutorService scheduledExecutorService, int checkPeriod, Target target, List<TargetProbe> targetProbes) {
+      super(scheduledExecutorService, 0, checkPeriod, TimeUnit.MILLISECONDS, false);
+
+      this.target = target;
+      this.targetProbes = targetProbes;
+   }
+
+   @Override
+   public synchronized void start() {
+      target.setListener(this);
+
+      super.start();
+   }
+
+   @Override
+   public synchronized void stop() {
+      super.stop();
+
+      targetReady = false;
+
+      target.setListener(null);
+
+      try {
+         target.disconnect();
+      } catch (Exception e) {
+         logger.debug("Error on disconnecting target " + target, e);
+      }
+   }
+
+   @Override
+   public void run() {
+      try {
+         if (!target.isConnected()) {
+            if (logger.isDebugEnabled()) {
+               logger.debug("Connecting to " + target);
+            }
+
+            target.connect();
+         }
+
+         targetReady = target.checkReadiness() &&  checkTargetProbes();
+
+         if (logger.isDebugEnabled()) {
+            if (targetReady) {
+               logger.debug(target + " is ready");
+            } else {
+               logger.debug(target + " is not ready");
+            }
+         }
+      } catch (Exception e) {
+         logger.warn("Error monitoring " + target, e);
+
+         targetReady = false;
+      }
+   }
+
+   private boolean checkTargetProbes() {
+      for (TargetProbe targetProbe : targetProbes) {
+         if (!targetProbe.check(target)) {
+            logger.info(targetProbe.getName() + " has failed on " + target);
+            return false;
+         }
+      }
+
+      return true;
+   }
+
+   @Override
+   public void targetConnected() {
+
+   }
+
+   @Override
+   public void targetDisconnected() {
+      targetReady = false;
+   }
+
+
+   @Override
+   public String toString() {
+      return this.getClass().getSimpleName() + " [target=" + target + ", targetReady=" + targetReady + "]";
+   }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetProbe.java
similarity index 51%
copy from artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
copy to artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetProbe.java
index f9e1b3d..1d80067 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyServerConnection.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetProbe.java
@@ -1,34 +1,32 @@
-/*
+/**
  * 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
- *
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
  * 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.util.Map;
+package org.apache.activemq.artemis.core.server.balancing.targets;
 
-import io.netty.channel.Channel;
-import org.apache.activemq.artemis.spi.core.remoting.ServerConnectionLifeCycleListener;
+public abstract class TargetProbe {
+   private final String name;
 
-public class NettyServerConnection extends NettyConnection {
+   public String getName() {
+      return name;
+   }
 
-   public NettyServerConnection(Map<String, Object> configuration,
-                                Channel channel,
-                                ServerConnectionLifeCycleListener listener,
-                                boolean batchingEnabled,
-                                boolean directDeliver) {
-      super(configuration, channel, listener, batchingEnabled, directDeliver);
+   public TargetProbe(String name) {
+      this.name = name;
    }
 
+   public abstract boolean check(Target target);
 }
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 dac6cde..8417446 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
@@ -172,6 +172,7 @@ import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerMessagePlugi
 import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerQueuePlugin;
 import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerResourcePlugin;
 import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerSessionPlugin;
+import org.apache.activemq.artemis.core.server.balancing.BrokerBalancerManager;
 import org.apache.activemq.artemis.core.server.reload.ReloadManager;
 import org.apache.activemq.artemis.core.server.reload.ReloadManagerImpl;
 import org.apache.activemq.artemis.core.server.transformer.Transformer;
@@ -289,6 +290,8 @@ public class ActiveMQServerImpl implements ActiveMQServer {
 
    private volatile RemotingService remotingService;
 
+   private volatile BrokerBalancerManager balancerManager;
+
    private final List<ProtocolManagerFactory> protocolManagerFactories = new ArrayList<>();
 
    private final List<ActiveMQComponent> protocolServices = new ArrayList<>();
@@ -1194,6 +1197,8 @@ public class ActiveMQServerImpl implements ActiveMQServer {
             }
          }
 
+         stopComponent(balancerManager);
+
          stopComponent(connectorsService);
 
          // we stop the groupingHandler before we stop the cluster manager so binding mappings
@@ -1632,6 +1637,11 @@ public class ActiveMQServerImpl implements ActiveMQServer {
       return clusterManager;
    }
 
+   @Override
+   public BrokerBalancerManager getBalancerManager() {
+      return balancerManager;
+   }
+
    public BackupManager getBackupManager() {
       return backupManager;
    }
@@ -3107,6 +3117,10 @@ public class ActiveMQServerImpl implements ActiveMQServer {
 
       federationManager.deploy();
 
+      balancerManager = new BrokerBalancerManager(configuration, this, scheduledPool);
+
+      balancerManager.deploy();
+
       remotingService = new RemotingServiceImpl(clusterManager, configuration, this, managementService, scheduledPool, protocolManagerFactories, executorFactory.getExecutor(), serviceRegistry);
 
       messagingServerControl = managementService.registerServer(postOffice, securityStore, storageManager, configuration, addressSettingsRepository, securityRepository, resourceManager, remotingService, this, queueFactory, scheduledPool, pagingManager, haPolicy.isBackup());
@@ -3270,6 +3284,8 @@ public class ActiveMQServerImpl implements ActiveMQServer {
             federationManager.start();
          }
 
+         balancerManager.start();
+
          startProtocolServices();
 
          if (nodeManager.getNodeId() == null) {
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ManagementService.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ManagementService.java
index c47804a..a3bbe62 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ManagementService.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ManagementService.java
@@ -44,6 +44,7 @@ import org.apache.activemq.artemis.core.server.ActiveMQServer;
 import org.apache.activemq.artemis.core.server.Divert;
 import org.apache.activemq.artemis.core.server.Queue;
 import org.apache.activemq.artemis.core.server.QueueFactory;
+import org.apache.activemq.artemis.core.server.balancing.BrokerBalancer;
 import org.apache.activemq.artemis.core.server.cluster.Bridge;
 import org.apache.activemq.artemis.core.server.cluster.BroadcastGroup;
 import org.apache.activemq.artemis.core.server.cluster.ClusterConnection;
@@ -127,6 +128,10 @@ public interface ManagementService extends NotificationService, ActiveMQComponen
 
    void unregisterCluster(String name) throws Exception;
 
+   void registerBrokerBalancer(BrokerBalancer balancer) throws Exception;
+
+   void unregisterBrokerBalancer(String name) throws Exception;
+
    Object getResource(String resourceName);
 
    Object[] getResources(Class<?> resourceType);
@@ -136,4 +141,8 @@ public interface ManagementService extends NotificationService, ActiveMQComponen
    void registerHawtioSecurity(ArtemisMBeanServerGuard securityMBean) throws Exception;
 
    void unregisterHawtioSecurity() throws Exception;
+
+   Object getAttribute(String resourceName, String attribute);
+
+   Object invokeOperation(String resourceName, String operation, Object[] params) throws Exception;
 }
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 5d9115b..60c268c 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
@@ -51,6 +51,7 @@ import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl;
 import org.apache.activemq.artemis.api.core.management.AddressControl;
 import org.apache.activemq.artemis.api.core.management.BaseBroadcastGroupControl;
 import org.apache.activemq.artemis.api.core.management.BridgeControl;
+import org.apache.activemq.artemis.api.core.management.BrokerBalancerControl;
 import org.apache.activemq.artemis.api.core.management.ClusterConnectionControl;
 import org.apache.activemq.artemis.api.core.management.DivertControl;
 import org.apache.activemq.artemis.api.core.management.ManagementHelper;
@@ -66,6 +67,7 @@ import org.apache.activemq.artemis.core.management.impl.AddressControlImpl;
 import org.apache.activemq.artemis.core.management.impl.BaseBroadcastGroupControlImpl;
 import org.apache.activemq.artemis.core.management.impl.BridgeControlImpl;
 import org.apache.activemq.artemis.core.management.impl.BroadcastGroupControlImpl;
+import org.apache.activemq.artemis.core.management.impl.BrokerBalancerControlImpl;
 import org.apache.activemq.artemis.core.management.impl.ClusterConnectionControlImpl;
 import org.apache.activemq.artemis.core.management.impl.DivertControlImpl;
 import org.apache.activemq.artemis.core.management.impl.JGroupsChannelBroadcastGroupControlImpl;
@@ -88,6 +90,7 @@ import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
 import org.apache.activemq.artemis.core.server.Divert;
 import org.apache.activemq.artemis.core.server.Queue;
 import org.apache.activemq.artemis.core.server.QueueFactory;
+import org.apache.activemq.artemis.core.server.balancing.BrokerBalancer;
 import org.apache.activemq.artemis.core.server.cluster.Bridge;
 import org.apache.activemq.artemis.core.server.cluster.BroadcastGroup;
 import org.apache.activemq.artemis.core.server.cluster.ClusterConnection;
@@ -494,6 +497,25 @@ public class ManagementServiceImpl implements ManagementService {
    }
 
    @Override
+   public synchronized void registerBrokerBalancer(final BrokerBalancer balancer) throws Exception {
+      ObjectName objectName = objectNameBuilder.getBrokerBalancerObjectName(balancer.getName());
+      BrokerBalancerControl brokerBalancerControl = new BrokerBalancerControlImpl(balancer, storageManager);
+      registerInJMX(objectName, brokerBalancerControl);
+      registerInRegistry(ResourceNames.BROKER_BALANCER + balancer.getName(), brokerBalancerControl);
+
+      if (logger.isDebugEnabled()) {
+         logger.debug("registered broker balancer " + objectName);
+      }
+   }
+
+   @Override
+   public synchronized void unregisterBrokerBalancer(final String name) throws Exception {
+      ObjectName objectName = objectNameBuilder.getBrokerBalancerObjectName(name);
+      unregisterFromJMX(objectName);
+      unregisterFromRegistry(ResourceNames.BROKER_BALANCER + name);
+   }
+
+   @Override
    public void registerHawtioSecurity(ArtemisMBeanServerGuard mBeanServerGuard) throws Exception {
       ObjectName objectName = objectNameBuilder.getManagementContextObjectName();
       HawtioSecurityControl control = new HawtioSecurityControlImpl(mBeanServerGuard, storageManager);
@@ -831,6 +853,7 @@ public class ManagementServiceImpl implements ManagementService {
       notificationsEnabled = enabled;
    }
 
+   @Override
    public Object getAttribute(final String resourceName, final String attribute) {
       try {
          Object resource = registry.get(resourceName);
@@ -857,7 +880,8 @@ public class ManagementServiceImpl implements ManagementService {
       }
    }
 
-   private Object invokeOperation(final String resourceName,
+   @Override
+   public Object invokeOperation(final String resourceName,
                                   final String operation,
                                   final Object[] params) throws Exception {
       Object resource = registry.get(resourceName);
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/AbstractProtocolManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/AbstractProtocolManager.java
index 7a4d3b1..7d540b7 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/AbstractProtocolManager.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/AbstractProtocolManager.java
@@ -27,8 +27,9 @@ import org.apache.activemq.artemis.api.core.BaseInterceptor;
 import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
 import org.apache.activemq.artemis.api.core.RoutingType;
+import org.apache.activemq.artemis.core.server.balancing.RedirectHandler;
 
-public abstract class AbstractProtocolManager<P, I extends BaseInterceptor<P>, C extends RemotingConnection> implements ProtocolManager<I> {
+public abstract class AbstractProtocolManager<P, I extends BaseInterceptor<P>, C extends RemotingConnection, R extends RedirectHandler> implements ProtocolManager<I, R> {
 
    private final Map<SimpleString, RoutingType> prefixes = new HashMap<>();
 
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/ProtocolManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/ProtocolManager.java
index 770034d..ee6ec22 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/ProtocolManager.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/ProtocolManager.java
@@ -25,12 +25,13 @@ import org.apache.activemq.artemis.api.core.BaseInterceptor;
 import org.apache.activemq.artemis.api.core.RoutingType;
 import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.core.remoting.impl.netty.NettyServerConnection;
+import org.apache.activemq.artemis.core.server.balancing.RedirectHandler;
 import org.apache.activemq.artemis.spi.core.remoting.Acceptor;
 import org.apache.activemq.artemis.spi.core.remoting.Connection;
 
 /**
  * Info: ProtocolManager is loaded by {@link org.apache.activemq.artemis.core.remoting.server.impl.RemotingServiceImpl#loadProtocolManagerFactories(Iterable)} */
-public interface ProtocolManager<P extends BaseInterceptor> {
+public interface ProtocolManager<P extends BaseInterceptor, R extends RedirectHandler> {
 
    ProtocolManagerFactory<P> getFactory();
 
@@ -78,4 +79,6 @@ public interface ProtocolManager<P extends BaseInterceptor> {
    void setSecurityDomain(String securityDomain);
 
    String getSecurityDomain();
+
+   R getRedirectHandler();
 }
diff --git a/artemis-server/src/main/resources/schema/artemis-configuration.xsd b/artemis-server/src/main/resources/schema/artemis-configuration.xsd
index 69e06cd..e35e1f3 100644
--- a/artemis-server/src/main/resources/schema/artemis-configuration.xsd
+++ b/artemis-server/src/main/resources/schema/artemis-configuration.xsd
@@ -619,6 +619,20 @@
             </xsd:annotation>
          </xsd:element>
 
+         <xsd:element name="broker-balancers" maxOccurs="1" minOccurs="0">
+            <xsd:annotation>
+               <xsd:documentation>
+                  A list of balancers
+               </xsd:documentation>
+            </xsd:annotation>
+            <xsd:complexType>
+               <xsd:sequence>
+                  <xsd:element name="broker-balancer" type="brokerBalancerType" maxOccurs="unbounded" minOccurs="0"/>
+               </xsd:sequence>
+               <xsd:attributeGroup ref="xml:specialAttrs"/>
+            </xsd:complexType>
+         </xsd:element>
+
          <xsd:element name="grouping-handler" type="groupingHandlerType" maxOccurs="1" minOccurs="0">
             <xsd:annotation>
                <xsd:documentation>
@@ -2078,6 +2092,167 @@
       </xsd:sequence>
    </xsd:complexType>
 
+   <xsd:complexType name="brokerBalancerType">
+      <xsd:sequence maxOccurs="unbounded">
+         <xsd:element name="target-key" type="brokerBalancerTargetKeyType" maxOccurs="1" minOccurs="0">
+            <xsd:annotation>
+               <xsd:documentation>
+                  the optional target key
+               </xsd:documentation>
+            </xsd:annotation>
+         </xsd:element>
+         <xsd:element name="target-key-filter" type="xsd:string" maxOccurs="1" minOccurs="0">
+            <xsd:annotation>
+               <xsd:documentation>
+                  the filter for the target key
+               </xsd:documentation>
+            </xsd:annotation>
+         </xsd:element>
+         <xsd:element name="local-target-filter" type="xsd:string" maxOccurs="1" minOccurs="0">
+            <xsd:annotation>
+               <xsd:documentation>
+                  the filter to get the local target
+               </xsd:documentation>
+            </xsd:annotation>
+         </xsd:element>
+         <xsd:element name="cache-timeout" type="xsd:long" default="-1" maxOccurs="1" minOccurs="0">
+            <xsd:annotation>
+               <xsd:documentation>
+                  the time period for a cache entry to remain active
+               </xsd:documentation>
+            </xsd:annotation>
+         </xsd:element>
+         <xsd:element name="policy" type="brokerBalancerPolicyType" maxOccurs="1" minOccurs="1">
+            <xsd:annotation>
+               <xsd:documentation>
+                  the policy configuration
+               </xsd:documentation>
+            </xsd:annotation>
+         </xsd:element>
+         <xsd:element name="pool" type="brokerBalancerPoolType" maxOccurs="1" minOccurs="1">
+            <xsd:annotation>
+               <xsd:documentation>
+                  the pool configuration
+               </xsd:documentation>
+            </xsd:annotation>
+         </xsd:element>
+      </xsd:sequence>
+      <xsd:attribute name="name" type="xsd:string" use="required">
+         <xsd:annotation>
+            <xsd:documentation>
+               a unique name for the broker balancer
+            </xsd:documentation>
+         </xsd:annotation>
+      </xsd:attribute>
+      <xsd:attributeGroup ref="xml:specialAttrs"/>
+   </xsd:complexType>
+
+   <xsd:simpleType name="brokerBalancerTargetKeyType">
+      <xsd:restriction base="xsd:string">
+         <xsd:enumeration value="CLIENT_ID"/>
+         <xsd:enumeration value="SNI_HOST"/>
+         <xsd:enumeration value="SOURCE_IP"/>
+         <xsd:enumeration value="USER_NAME"/>
+      </xsd:restriction>
+   </xsd:simpleType>
+
+   <xsd:complexType name="brokerBalancerPolicyType">
+      <xsd:sequence>
+         <xsd:element ref="property" maxOccurs="unbounded" minOccurs="0">
+            <xsd:annotation>
+               <xsd:documentation>
+                  properties to configure a policy
+               </xsd:documentation>
+            </xsd:annotation>
+         </xsd:element>
+      </xsd:sequence>
+      <xsd:attribute name="name" type="xsd:ID" use="required">
+         <xsd:annotation>
+            <xsd:documentation>
+               the name of the policy
+            </xsd:documentation>
+         </xsd:annotation>
+      </xsd:attribute>
+      <xsd:attributeGroup ref="xml:specialAttrs"/>
+   </xsd:complexType>
+
+   <xsd:complexType name="brokerBalancerPoolType">
+      <xsd:sequence maxOccurs="unbounded">
+         <xsd:element name="username" type="xsd:string" maxOccurs="1" minOccurs="0">
+            <xsd:annotation>
+               <xsd:documentation>
+                  the username to access the targets
+               </xsd:documentation>
+            </xsd:annotation>
+         </xsd:element>
+         <xsd:element name="password" type="xsd:string" maxOccurs="1" minOccurs="0">
+            <xsd:annotation>
+               <xsd:documentation>
+                  the password to access the targets
+               </xsd:documentation>
+            </xsd:annotation>
+         </xsd:element>
+         <xsd:element name="check-period" type="xsd:long" default="5000" maxOccurs="1" minOccurs="0">
+            <xsd:annotation>
+               <xsd:documentation>
+                  the period (in milliseconds) used to check if a target is ready
+               </xsd:documentation>
+            </xsd:annotation>
+         </xsd:element>
+         <xsd:element name="quorum-size" type="xsd:long" default="1" maxOccurs="1" minOccurs="0">
+            <xsd:annotation>
+               <xsd:documentation>
+                  the minimum number of ready targets
+               </xsd:documentation>
+            </xsd:annotation>
+         </xsd:element>
+         <xsd:element name="quorum-timeout" type="xsd:long" default="3000" maxOccurs="1" minOccurs="0">
+            <xsd:annotation>
+               <xsd:documentation>
+                  the timeout (in milliseconds) used to get the minimum number of ready targets
+               </xsd:documentation>
+            </xsd:annotation>
+         </xsd:element>
+         <xsd:element name="local-target-enabled" type="xsd:boolean" default="false" maxOccurs="1" minOccurs="0">
+            <xsd:annotation>
+               <xsd:documentation>
+                  true means that the local target is enabled
+               </xsd:documentation>
+            </xsd:annotation>
+         </xsd:element>
+         <xsd:choice>
+            <xsd:element name="cluster-connection" type="xsd:string" maxOccurs="1" minOccurs="1">
+               <xsd:annotation>
+                  <xsd:documentation>
+                     the name of a cluster connection
+                  </xsd:documentation>
+               </xsd:annotation>
+            </xsd:element>
+            <xsd:element name="static-connectors" maxOccurs="1" minOccurs="1">
+               <xsd:complexType>
+                  <xsd:sequence>
+                     <xsd:element name="connector-ref" type="xsd:string" maxOccurs="unbounded" minOccurs="1"/>
+                  </xsd:sequence>
+                  <xsd:attributeGroup ref="xml:specialAttrs"/>
+               </xsd:complexType>
+            </xsd:element>
+            <xsd:element name="discovery-group-ref" maxOccurs="1" minOccurs="1">
+               <xsd:complexType>
+                  <xsd:attribute name="discovery-group-name" type="xsd:IDREF" use="required">
+                     <xsd:annotation>
+                        <xsd:documentation>
+                           name of discovery group used by this bridge
+                        </xsd:documentation>
+                     </xsd:annotation>
+                  </xsd:attribute>
+                  <xsd:attributeGroup ref="xml:specialAttrs"/>
+               </xsd:complexType>
+            </xsd:element>
+         </xsd:choice>
+      </xsd:sequence>
+      <xsd:attributeGroup ref="xml:specialAttrs"/>
+   </xsd:complexType>
+
    <xsd:complexType name="amqp-connectionUriType">
       <xsd:sequence>
          <xsd:choice maxOccurs="unbounded">
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java
index 07f11ab..b7027b4 100644
--- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java
@@ -46,6 +46,7 @@ import org.apache.activemq.artemis.core.config.DivertConfiguration;
 import org.apache.activemq.artemis.core.config.FileDeploymentManager;
 import org.apache.activemq.artemis.core.config.HAPolicyConfiguration;
 import org.apache.activemq.artemis.core.config.MetricsConfiguration;
+import org.apache.activemq.artemis.core.config.balancing.BrokerBalancerConfiguration;
 import org.apache.activemq.artemis.core.config.ha.LiveOnlyPolicyConfiguration;
 import org.apache.activemq.artemis.core.journal.impl.JournalImpl;
 import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants;
@@ -54,6 +55,10 @@ import org.apache.activemq.artemis.core.server.ComponentConfigurationRoutingType
 import org.apache.activemq.artemis.core.server.JournalType;
 import org.apache.activemq.artemis.core.server.Queue;
 import org.apache.activemq.artemis.core.server.SecuritySettingPlugin;
+import org.apache.activemq.artemis.core.server.balancing.policies.ConsistentHashPolicy;
+import org.apache.activemq.artemis.core.server.balancing.policies.FirstElementPolicy;
+import org.apache.activemq.artemis.core.server.balancing.policies.LeastConnectionsPolicy;
+import org.apache.activemq.artemis.core.server.balancing.targets.TargetKey;
 import org.apache.activemq.artemis.core.server.cluster.impl.MessageLoadBalancingType;
 import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl;
 import org.apache.activemq.artemis.core.server.impl.LegacyLDAPSecuritySettingPlugin;
@@ -260,6 +265,38 @@ public class FileConfigurationTest extends ConfigurationImplTest {
          }
       }
 
+      Assert.assertEquals(3, conf.getBalancerConfigurations().size());
+      for (BrokerBalancerConfiguration bc : conf.getBalancerConfigurations()) {
+         if (bc.getName().equals("simple-balancer")) {
+            Assert.assertEquals(bc.getTargetKey(), TargetKey.USER_NAME);
+            Assert.assertNull(bc.getLocalTargetFilter());
+            Assert.assertEquals(bc.getPolicyConfiguration().getName(), FirstElementPolicy.NAME);
+            Assert.assertEquals(false, bc.getPoolConfiguration().isLocalTargetEnabled());
+            Assert.assertEquals("connector1", bc.getPoolConfiguration().getStaticConnectors().get(0));
+            Assert.assertEquals(null, bc.getPoolConfiguration().getDiscoveryGroupName());
+         } else if (bc.getName().equals("consistent-hash-balancer")) {
+            Assert.assertEquals(bc.getTargetKey(), TargetKey.SNI_HOST);
+            Assert.assertEquals(bc.getTargetKeyFilter(), "^[^.]+");
+            Assert.assertEquals(bc.getLocalTargetFilter(), "DEFAULT");
+            Assert.assertEquals(bc.getPolicyConfiguration().getName(), ConsistentHashPolicy.NAME);
+            Assert.assertEquals(1000, bc.getPoolConfiguration().getCheckPeriod());
+            Assert.assertEquals(true, bc.getPoolConfiguration().isLocalTargetEnabled());
+            Assert.assertEquals(Collections.emptyList(), bc.getPoolConfiguration().getStaticConnectors());
+            Assert.assertEquals("dg1", bc.getPoolConfiguration().getDiscoveryGroupName());
+         } else {
+            Assert.assertEquals(bc.getTargetKey(), TargetKey.SOURCE_IP);
+            Assert.assertEquals("least-connections-balancer", bc.getName());
+            Assert.assertEquals(60000, bc.getCacheTimeout());
+            Assert.assertEquals(bc.getPolicyConfiguration().getName(), LeastConnectionsPolicy.NAME);
+            Assert.assertEquals(3000, bc.getPoolConfiguration().getCheckPeriod());
+            Assert.assertEquals(2, bc.getPoolConfiguration().getQuorumSize());
+            Assert.assertEquals(1000, bc.getPoolConfiguration().getQuorumTimeout());
+            Assert.assertEquals(false, bc.getPoolConfiguration().isLocalTargetEnabled());
+            Assert.assertEquals(Collections.emptyList(), bc.getPoolConfiguration().getStaticConnectors());
+            Assert.assertEquals("dg2", bc.getPoolConfiguration().getDiscoveryGroupName());
+         }
+      }
+
       Assert.assertEquals(4, conf.getBridgeConfigurations().size());
       for (BridgeConfiguration bc : conf.getBridgeConfigurations()) {
          if (bc.getName().equals("bridge1")) {
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/ConsistentHashPolicyTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/ConsistentHashPolicyTest.java
new file mode 100644
index 0000000..d81677e
--- /dev/null
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/ConsistentHashPolicyTest.java
@@ -0,0 +1,55 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.policies;
+
+import org.apache.activemq.artemis.core.server.balancing.targets.MockTarget;
+import org.apache.activemq.artemis.core.server.balancing.targets.Target;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.ArrayList;
+
+public class ConsistentHashPolicyTest extends PolicyTestBase {
+
+   @Override
+   protected AbstractPolicy createPolicy() {
+      return new ConsistentHashPolicy();
+   }
+
+   @Test
+   public void testPolicyWithMultipleTargets() {
+      AbstractPolicy policy = createPolicy();
+      Target selectedTarget;
+      Target previousTarget;
+
+      ArrayList<Target> targets = new ArrayList<>();
+      for (int i = 0; i < MULTIPLE_TARGETS; i++) {
+         targets.add(new MockTarget());
+      }
+
+      selectedTarget = policy.selectTarget(targets, "test");
+      previousTarget = selectedTarget;
+
+      selectedTarget = policy.selectTarget(targets, "test");
+      Assert.assertEquals(previousTarget, selectedTarget);
+
+      targets.remove(previousTarget);
+      selectedTarget = policy.selectTarget(targets, "test");
+      Assert.assertNotEquals(previousTarget, selectedTarget);
+   }
+}
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/FirstElementPolicyTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/FirstElementPolicyTest.java
new file mode 100644
index 0000000..b84c8e0
--- /dev/null
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/FirstElementPolicyTest.java
@@ -0,0 +1,47 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.policies;
+
+import org.apache.activemq.artemis.core.server.balancing.targets.MockTarget;
+import org.apache.activemq.artemis.core.server.balancing.targets.Target;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.ArrayList;
+
+public class FirstElementPolicyTest extends PolicyTestBase {
+
+   @Override
+   protected AbstractPolicy createPolicy() {
+      return new FirstElementPolicy();
+   }
+
+   @Test
+   public void testPolicyWithMultipleTargets() {
+      AbstractPolicy policy = createPolicy();
+
+      ArrayList<Target> targets = new ArrayList<>();
+      for (int i = 0; i < MULTIPLE_TARGETS; i++) {
+         targets.add(new MockTarget());
+      }
+
+      Target selectedTarget = policy.selectTarget(targets, "test");
+
+      Assert.assertEquals(selectedTarget, targets.get(0));
+   }
+}
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/LeastConnectionsPolicyTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/LeastConnectionsPolicyTest.java
new file mode 100644
index 0000000..b272f49
--- /dev/null
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/LeastConnectionsPolicyTest.java
@@ -0,0 +1,95 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.policies;
+
+import org.apache.activemq.artemis.core.server.balancing.targets.MockTarget;
+import org.apache.activemq.artemis.core.server.balancing.targets.Target;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+public class LeastConnectionsPolicyTest extends PolicyTestBase {
+
+   @Override
+   protected AbstractPolicy createPolicy() {
+      return new LeastConnectionsPolicy();
+   }
+
+   @Test
+   public void testPolicyWithMultipleTargets() {
+      AbstractPolicy policy = createPolicy();
+      Target selectedTarget = null;
+      Set<Target> selectedTargets;
+
+
+      ArrayList<Target> targets = new ArrayList<>();
+      for (int i = 0; i < MULTIPLE_TARGETS; i++) {
+         targets.add(new MockTarget().setConnected(true).setReady(true));
+      }
+
+
+      selectedTargets = new HashSet<>();
+      for (int i = 0; i < MULTIPLE_TARGETS; i++) {
+         selectedTarget = policy.selectTarget(targets, "test");
+         selectedTargets.add(selectedTarget);
+      }
+      Assert.assertEquals(MULTIPLE_TARGETS, selectedTargets.size());
+
+
+      targets.forEach(target -> {
+         ((MockTarget)target).setAttributeValue("broker", "ConnectionCount", 3);
+         policy.getTargetProbe().check(target);
+      });
+
+      selectedTargets = new HashSet<>();
+      for (int i = 0; i < MULTIPLE_TARGETS; i++) {
+         selectedTarget = policy.selectTarget(targets, "test");
+         selectedTargets.add(selectedTarget);
+      }
+      Assert.assertEquals(MULTIPLE_TARGETS, selectedTargets.size());
+
+
+      ((MockTarget)targets.get(0)).setAttributeValue("broker", "ConnectionCount", 2);
+      targets.forEach(target -> policy.getTargetProbe().check(target));
+
+      selectedTargets = new HashSet<>();
+      for (int i = 0; i < MULTIPLE_TARGETS; i++) {
+         selectedTarget = policy.selectTarget(targets, "test");
+         selectedTargets.add(selectedTarget);
+      }
+      Assert.assertEquals(1, selectedTargets.size());
+      Assert.assertTrue(selectedTargets.contains(targets.get(0)));
+
+
+      ((MockTarget)targets.get(1)).setAttributeValue("broker", "ConnectionCount", 1);
+      ((MockTarget)targets.get(2)).setAttributeValue("broker", "ConnectionCount", 1);
+      targets.forEach(target -> policy.getTargetProbe().check(target));
+
+      selectedTargets = new HashSet<>();
+      for (int i = 0; i < MULTIPLE_TARGETS; i++) {
+         selectedTarget = policy.selectTarget(targets, "test");
+         selectedTargets.add(selectedTarget);
+      }
+      Assert.assertEquals(2, selectedTargets.size());
+      Assert.assertTrue(selectedTargets.contains(targets.get(1)));
+      Assert.assertTrue(selectedTargets.contains(targets.get(2)));
+   }
+}
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/PolicyTestBase.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/PolicyTestBase.java
new file mode 100644
index 0000000..962010b
--- /dev/null
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/PolicyTestBase.java
@@ -0,0 +1,52 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.policies;
+
+import org.apache.activemq.artemis.core.server.balancing.targets.MockTarget;
+import org.apache.activemq.artemis.core.server.balancing.targets.Target;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+public abstract class PolicyTestBase {
+   public static final int MULTIPLE_TARGETS = 10;
+
+   protected abstract AbstractPolicy createPolicy();
+
+   @Test
+   public void testPolicyWithNoTarget() {
+      AbstractPolicy policy = createPolicy();
+
+      Target selectedTarget = policy.selectTarget(Collections.emptyList(), "test");
+
+      Assert.assertNull(selectedTarget);
+   }
+
+   @Test
+   public void testPolicyWithSingleTarget() {
+      AbstractPolicy policy = createPolicy();
+
+      ArrayList<Target> targets = new ArrayList<>();
+      targets.add(new MockTarget());
+
+      Target selectedTarget = policy.selectTarget(targets, "test");
+      Assert.assertEquals(selectedTarget, targets.get(0));
+   }
+}
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/RoundRobinPolicyTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/RoundRobinPolicyTest.java
new file mode 100644
index 0000000..70b7b74
--- /dev/null
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/policies/RoundRobinPolicyTest.java
@@ -0,0 +1,58 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.policies;
+
+import org.apache.activemq.artemis.core.server.balancing.targets.MockTarget;
+import org.apache.activemq.artemis.core.server.balancing.targets.Target;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class RoundRobinPolicyTest extends PolicyTestBase {
+
+   @Override
+   protected AbstractPolicy createPolicy() {
+      return new RoundRobinPolicy();
+   }
+
+   @Test
+   public void testPolicyWithMultipleTargets() {
+      AbstractPolicy policy = createPolicy();
+      Target selectedTarget = null;
+      Set<Target> selectedTargets = new HashSet<>();
+      List<Target> previousTargets = new ArrayList<>();
+
+      ArrayList<Target> targets = new ArrayList<>();
+      for (int i = 0; i < MULTIPLE_TARGETS; i++) {
+         targets.add(new MockTarget());
+      }
+
+      selectedTargets = new HashSet<>();
+      for (int i = 0; i < MULTIPLE_TARGETS; i++) {
+         selectedTarget = policy.selectTarget(targets, "test");
+         selectedTargets.add(selectedTarget);
+         Assert.assertTrue("Iteration failed: " + i, !previousTargets.contains(selectedTarget));
+         previousTargets.add(selectedTarget);
+      }
+      Assert.assertEquals(MULTIPLE_TARGETS, selectedTargets.size());
+   }
+}
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryPoolTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryPoolTest.java
new file mode 100644
index 0000000..b637ebe
--- /dev/null
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/DiscoveryPoolTest.java
@@ -0,0 +1,174 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.pools;
+
+import org.apache.activemq.artemis.core.server.balancing.targets.MockTargetFactory;
+import org.apache.activemq.artemis.core.server.balancing.targets.MockTargetProbe;
+import org.apache.activemq.artemis.core.server.balancing.targets.TargetFactory;
+import org.apache.activemq.artemis.utils.Wait;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.stream.Stream;
+
+public class DiscoveryPoolTest extends PoolTestBase {
+
+   @Test
+   public void testPoolAddingRemovingAllEntries() throws Exception {
+      testPoolChangingEntries(5, 10, 10);
+   }
+
+   @Test
+   public void testPoolAddingRemovingPartialEntries() throws Exception {
+      testPoolChangingEntries(5, 10, 5);
+   }
+
+   @Test
+   public void testPoolAddingRemovingAllEntriesAfterStart() throws Exception {
+      testPoolChangingEntries(0, 10, 10);
+   }
+
+   @Test
+   public void testPoolAddingRemovingPartialEntriesAfterStart() throws Exception {
+      testPoolChangingEntries(0, 10, 5);
+   }
+
+   private void testPoolChangingEntries(int initialEntries, int addingEntries, int removingEntries) throws Exception {
+      MockTargetFactory targetFactory = new MockTargetFactory();
+      MockTargetProbe targetProbe = new MockTargetProbe("TEST", true);
+      MockDiscoveryService discoveryService = new MockDiscoveryService();
+
+      targetProbe.setChecked(true);
+
+      // Simulate initial entries.
+      List<String> initialNodeIDs = new ArrayList<>();
+      for (int i = 0; i < initialEntries; i++) {
+         initialNodeIDs.add(discoveryService.addEntry().getNodeID());
+      }
+
+      Pool pool = createDiscoveryPool(targetFactory, discoveryService);
+
+      pool.addTargetProbe(targetProbe);
+
+      pool.start();
+
+      try {
+         targetFactory.getCreatedTargets().forEach(mockTarget -> mockTarget.setConnectable(true));
+         targetFactory.getCreatedTargets().forEach(mockTarget -> mockTarget.setReady(true));
+
+         Wait.assertEquals(initialEntries, () -> pool.getTargets().size(), CHECK_TIMEOUT);
+         Assert.assertEquals(initialEntries, pool.getAllTargets().size());
+         Assert.assertEquals(initialEntries, targetFactory.getCreatedTargets().size());
+         initialNodeIDs.forEach(nodeID -> Assert.assertTrue(pool.isTargetReady(pool.getTarget(nodeID))));
+
+         // Simulate adding entries.
+         List<String> addedNodeIDs = new ArrayList<>();
+         for (int i = 0; i < addingEntries; i++) {
+            addedNodeIDs.add(discoveryService.addEntry().getNodeID());
+         }
+
+         Assert.assertEquals(initialEntries, pool.getTargets().size());
+         Assert.assertEquals(initialEntries + addingEntries, pool.getAllTargets().size());
+         Assert.assertEquals(initialEntries + addingEntries, targetFactory.getCreatedTargets().size());
+         initialNodeIDs.forEach(nodeID -> {
+            Assert.assertTrue(pool.isTargetReady(pool.getTarget(nodeID)));
+            Assert.assertTrue(targetProbe.getTargetExecutions(pool.getTarget(nodeID)) > 0);
+         });
+         addedNodeIDs.forEach(nodeID -> {
+            Assert.assertFalse(pool.isTargetReady(pool.getTarget(nodeID)));
+            Assert.assertEquals(0, targetProbe.getTargetExecutions(pool.getTarget(nodeID)));
+         });
+
+
+         targetFactory.getCreatedTargets().forEach(mockTarget -> mockTarget.setConnectable(true));
+
+         Assert.assertEquals(initialEntries, pool.getTargets().size());
+         Assert.assertEquals(initialEntries + addingEntries, pool.getAllTargets().size());
+         Assert.assertEquals(initialEntries + addingEntries, targetFactory.getCreatedTargets().size());
+         initialNodeIDs.forEach(nodeID -> {
+            Assert.assertTrue(pool.isTargetReady(pool.getTarget(nodeID)));
+            Assert.assertTrue(targetProbe.getTargetExecutions(pool.getTarget(nodeID)) > 0);
+         });
+         addedNodeIDs.forEach(nodeID -> {
+            Assert.assertFalse(pool.isTargetReady(pool.getTarget(nodeID)));
+            Assert.assertEquals(0, targetProbe.getTargetExecutions(pool.getTarget(nodeID)));
+         });
+
+         targetFactory.getCreatedTargets().forEach(mockTarget -> mockTarget.setReady(true));
+
+         Wait.assertEquals(initialEntries + addingEntries, () -> pool.getTargets().size(), CHECK_TIMEOUT);
+         Assert.assertEquals(initialEntries + addingEntries, pool.getAllTargets().size());
+         Assert.assertEquals(initialEntries + addingEntries, targetFactory.getCreatedTargets().size());
+         Stream.concat(initialNodeIDs.stream(), addedNodeIDs.stream()).forEach(nodeID -> {
+            Assert.assertTrue(pool.isTargetReady(pool.getTarget(nodeID)));
+            Assert.assertTrue(targetProbe.getTargetExecutions(pool.getTarget(nodeID)) > 0);
+         });
+
+         if (removingEntries > 0) {
+            // Simulate removing entries.
+            List<String> removingNodeIDs = new ArrayList<>();
+            for (int i = 0; i < removingEntries; i++) {
+               removingNodeIDs.add(discoveryService.removeEntry(targetFactory.
+                  getCreatedTargets().get(i).getNodeID()).getNodeID());
+            }
+
+            Assert.assertEquals(initialEntries + addingEntries - removingEntries, pool.getTargets().size());
+            Assert.assertEquals(initialEntries + addingEntries - removingEntries, pool.getAllTargets().size());
+            Assert.assertEquals(initialEntries + addingEntries, targetFactory.getCreatedTargets().size());
+            Stream.concat(initialNodeIDs.stream(), addedNodeIDs.stream()).forEach(nodeID -> {
+               if (removingNodeIDs.contains(nodeID)) {
+                  Assert.assertNull(pool.getTarget(nodeID));
+                  Assert.assertEquals(0, targetProbe.getTargetExecutions(pool.getTarget(nodeID)));
+               } else {
+                  Assert.assertTrue(pool.isTargetReady(pool.getTarget(nodeID)));
+                  Assert.assertTrue(targetProbe.getTargetExecutions(pool.getTarget(nodeID)) > 0);
+               }
+            });
+         } else {
+            Assert.assertEquals(initialEntries + addingEntries, pool.getTargets().size());
+            Assert.assertEquals(initialEntries + addingEntries, pool.getAllTargets().size());
+            Assert.assertEquals(initialEntries + addingEntries, targetFactory.getCreatedTargets().size());
+            Stream.concat(initialNodeIDs.stream(), addedNodeIDs.stream()).forEach(nodeID -> {
+               Assert.assertTrue(pool.isTargetReady(pool.getTarget(nodeID)));
+               Assert.assertTrue(targetProbe.getTargetExecutions(pool.getTarget(nodeID)) > 0);
+            });
+         }
+      } finally {
+         pool.stop();
+      }
+   }
+
+
+   @Override
+   protected Pool createPool(TargetFactory targetFactory, int targets) {
+      MockDiscoveryService discoveryService = new MockDiscoveryService();
+
+      for (int i = 0; i < targets; i++) {
+         discoveryService.addEntry();
+      }
+
+      return createDiscoveryPool(targetFactory, discoveryService);
+   }
+
+   private DiscoveryPool createDiscoveryPool(TargetFactory targetFactory, DiscoveryService discoveryService) {
+      return new DiscoveryPool(targetFactory, new ScheduledThreadPoolExecutor(0), CHECK_PERIOD, discoveryService);
+   }
+}
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/MockDiscoveryService.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/MockDiscoveryService.java
new file mode 100644
index 0000000..f9d1982
--- /dev/null
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/MockDiscoveryService.java
@@ -0,0 +1,87 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.pools;
+
+import org.apache.activemq.artemis.api.core.TransportConfiguration;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+public class MockDiscoveryService extends DiscoveryService {
+   private final Map<String, Entry> entries = new HashMap<>();
+
+   private final Map<String, Entry> pendingEntries = new HashMap<>();
+
+   private volatile boolean started;
+
+
+   public Map<String, Entry> getEntries() {
+      return entries;
+   }
+
+   public Map<String, Entry> getPendingEntries() {
+      return pendingEntries;
+   }
+
+   @Override
+   public boolean isStarted() {
+      return started;
+   }
+
+   public Entry addEntry() {
+      return addEntry(new Entry(UUID.randomUUID().toString(), new TransportConfiguration()));
+   }
+
+   public Entry addEntry(Entry entry) {
+      if (started) {
+         entries.put(entry.getNodeID(), entry);
+         fireEntryAddedEvent(entry);
+      } else {
+         pendingEntries.put(entry.getNodeID(), entry);
+      }
+
+      return entry;
+   }
+
+   public Entry removeEntry(String nodeID) {
+      if (started) {
+         Entry removedEntry = entries.remove(nodeID);
+         fireEntryRemovedEvent(removedEntry);
+         return removedEntry;
+      } else {
+         return pendingEntries.remove(nodeID);
+      }
+   }
+
+
+   @Override
+   public void start() throws Exception {
+      started = true;
+
+      pendingEntries.forEach((nodeID, entry) -> {
+         entries.put(nodeID, entry);
+         fireEntryAddedEvent(entry);
+      });
+   }
+
+   @Override
+   public void stop() throws Exception {
+      started = false;
+   }
+}
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/PoolTestBase.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/PoolTestBase.java
new file mode 100644
index 0000000..1f7505e
--- /dev/null
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/PoolTestBase.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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.pools;
+
+import org.apache.activemq.artemis.core.server.balancing.targets.MockTargetFactory;
+import org.apache.activemq.artemis.core.server.balancing.targets.MockTargetProbe;
+import org.apache.activemq.artemis.core.server.balancing.targets.TargetFactory;
+import org.apache.activemq.artemis.utils.Wait;
+import org.junit.Assert;
+import org.junit.Test;
+
+public abstract class PoolTestBase {
+   public static final int MULTIPLE_TARGETS = 10;
+
+   public static final int CHECK_PERIOD = 100;
+   public static final int CHECK_TIMEOUT = 2 * CHECK_PERIOD;
+
+
+   protected abstract Pool createPool(TargetFactory targetFactory, int targets);
+
+
+   @Test
+   public void testPoolWithNoTargets() throws Exception {
+      testPoolTargets(0);
+   }
+
+   @Test
+   public void testPoolWithSingleTarget() throws Exception {
+      testPoolTargets(1);
+   }
+
+   @Test
+   public void testPoolWithMultipleTargets() throws Exception {
+      testPoolTargets(MULTIPLE_TARGETS);
+   }
+
+   @Test
+   public void testPoolQuorumWithMultipleTargets() throws Exception {
+      final int targets = MULTIPLE_TARGETS;
+      final int quorumSize = 2;
+
+      Assert.assertTrue(targets - quorumSize > 2);
+
+      MockTargetFactory targetFactory = new MockTargetFactory().setConnectable(true).setReady(true);
+      Pool pool = createPool(targetFactory, targets);
+
+      pool.setQuorumSize(quorumSize);
+
+      Assert.assertEquals(0, pool.getTargets().size());
+
+      pool.start();
+
+      Wait.assertEquals(targets, () -> pool.getTargets().size(), CHECK_TIMEOUT);
+
+      targetFactory.getCreatedTargets().stream().limit(targets - quorumSize + 1)
+         .forEach(mockTarget -> mockTarget.setReady(false));
+
+      Wait.assertEquals(0, () -> pool.getTargets().size(), CHECK_TIMEOUT);
+
+      targetFactory.getCreatedTargets().get(0).setReady(true);
+
+      Wait.assertEquals(quorumSize, () -> pool.getTargets().size(), CHECK_TIMEOUT);
+
+      pool.setQuorumSize(quorumSize + 1);
+
+      Wait.assertEquals(0, () -> pool.getTargets().size(), CHECK_TIMEOUT);
+
+      targetFactory.getCreatedTargets().get(1).setReady(true);
+
+      Wait.assertEquals(quorumSize + 1, () -> pool.getTargets().size(), CHECK_TIMEOUT);
+   }
+
+
+   private void testPoolTargets(int targets) throws Exception {
+      MockTargetFactory targetFactory = new MockTargetFactory();
+      MockTargetProbe targetProbe = new MockTargetProbe("TEST", false);
+      Pool pool = createPool(targetFactory, targets);
+
+      pool.addTargetProbe(targetProbe);
+
+      Assert.assertEquals(0, pool.getTargets().size());
+      Assert.assertEquals(0, pool.getAllTargets().size());
+      Assert.assertEquals(0, targetFactory.getCreatedTargets().size());
+      targetFactory.getCreatedTargets().forEach(mockTarget -> {
+         Assert.assertFalse(pool.isTargetReady(mockTarget));
+         Assert.assertEquals(0, targetProbe.getTargetExecutions(mockTarget));
+      });
+
+      pool.start();
+
+      try {
+         Assert.assertEquals(0, pool.getTargets().size());
+         Assert.assertEquals(targets, pool.getAllTargets().size());
+         Assert.assertEquals(targets, targetFactory.getCreatedTargets().size());
+         targetFactory.getCreatedTargets().forEach(mockTarget -> {
+            Assert.assertFalse(pool.isTargetReady(mockTarget));
+            Assert.assertEquals(0, targetProbe.getTargetExecutions(mockTarget));
+         });
+
+         if (targets > 0) {
+            targetFactory.getCreatedTargets().forEach(mockTarget -> mockTarget.setConnectable(true));
+
+            Assert.assertEquals(0, pool.getTargets().size());
+            Assert.assertEquals(targets, pool.getAllTargets().size());
+            Assert.assertEquals(targets, targetFactory.getCreatedTargets().size());
+            targetFactory.getCreatedTargets().forEach(mockTarget -> {
+               Assert.assertFalse(pool.isTargetReady(mockTarget));
+               Assert.assertEquals(0, targetProbe.getTargetExecutions(mockTarget));
+            });
+
+            targetFactory.getCreatedTargets().forEach(mockTarget -> mockTarget.setReady(true));
+
+            Assert.assertEquals(0, pool.getTargets().size());
+            Assert.assertEquals(targets, pool.getAllTargets().size());
+            Assert.assertEquals(targets, targetFactory.getCreatedTargets().size());
+            targetFactory.getCreatedTargets().forEach(mockTarget -> {
+               Assert.assertFalse(pool.isTargetReady(mockTarget));
+               Wait.assertTrue(() -> targetProbe.getTargetExecutions(mockTarget) > 0, CHECK_TIMEOUT);
+            });
+
+            targetProbe.setChecked(true);
+
+            Wait.assertEquals(targets, () -> pool.getTargets().size(), CHECK_TIMEOUT);
+            Assert.assertEquals(targets, pool.getAllTargets().size());
+            Assert.assertEquals(targets, targetFactory.getCreatedTargets().size());
+            targetFactory.getCreatedTargets().forEach(mockTarget -> {
+               Assert.assertTrue(pool.isTargetReady(mockTarget));
+               Assert.assertTrue(targetProbe.getTargetExecutions(mockTarget) > 0);
+            });
+
+            targetFactory.getCreatedTargets().forEach(mockTarget -> {
+               mockTarget.setConnectable(false);
+               try {
+                  mockTarget.disconnect();
+               } catch (Exception ignore) {
+               }
+            });
+
+            Wait.assertEquals(0, () -> pool.getTargets().size(), CHECK_TIMEOUT);
+            Assert.assertEquals(targets, pool.getAllTargets().size());
+            Assert.assertEquals(targets, targetFactory.getCreatedTargets().size());
+            targetFactory.getCreatedTargets().forEach(mockTarget -> {
+               Assert.assertFalse(pool.isTargetReady(mockTarget));
+               Assert.assertTrue(targetProbe.getTargetExecutions(mockTarget) > 0);
+            });
+
+            targetProbe.clearTargetExecutions();
+
+            targetFactory.getCreatedTargets().forEach(mockTarget -> mockTarget.setConnectable(true));
+
+            Wait.assertEquals(targets, () -> pool.getTargets().size(), CHECK_TIMEOUT);
+            Assert.assertEquals(targets, pool.getAllTargets().size());
+            Assert.assertEquals(targets, targetFactory.getCreatedTargets().size());
+            targetFactory.getCreatedTargets().forEach(mockTarget -> {
+               Assert.assertTrue(pool.isTargetReady(mockTarget));
+               Assert.assertTrue(targetProbe.getTargetExecutions(mockTarget) > 0);
+            });
+
+            targetProbe.clearTargetExecutions();
+
+            targetProbe.setChecked(false);
+
+            Wait.assertEquals(0, () -> pool.getTargets().size(), CHECK_TIMEOUT);
+            Assert.assertEquals(targets, pool.getAllTargets().size());
+            Assert.assertEquals(targets, targetFactory.getCreatedTargets().size());
+            targetFactory.getCreatedTargets().forEach(mockTarget -> {
+               Assert.assertFalse(pool.isTargetReady(mockTarget));
+               Assert.assertTrue(targetProbe.getTargetExecutions(mockTarget) > 0);
+            });
+         }
+      } finally {
+         pool.stop();
+      }
+   }
+}
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/StaticPoolTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/StaticPoolTest.java
new file mode 100644
index 0000000..6a48907
--- /dev/null
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/pools/StaticPoolTest.java
@@ -0,0 +1,39 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.pools;
+
+import org.apache.activemq.artemis.api.core.TransportConfiguration;
+import org.apache.activemq.artemis.core.server.balancing.targets.TargetFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+
+public class StaticPoolTest extends PoolTestBase {
+
+   @Override
+   protected Pool createPool(TargetFactory targetFactory, int targets) {
+      List<TransportConfiguration> staticConnectors = new ArrayList<>();
+
+      for (int i = 0; i < targets; i++) {
+         staticConnectors.add(new TransportConfiguration());
+      }
+
+      return new StaticPool(targetFactory, new ScheduledThreadPoolExecutor(0), CHECK_PERIOD, staticConnectors);
+   }
+}
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/MockTarget.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/MockTarget.java
new file mode 100644
index 0000000..3c31d6f
--- /dev/null
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/MockTarget.java
@@ -0,0 +1,156 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.targets;
+
+import org.apache.activemq.artemis.api.core.TransportConfiguration;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+public class MockTarget extends AbstractTarget {
+   private boolean local = false;
+
+   private boolean connected = false;
+
+   private boolean connectable = false;
+
+   private boolean ready = false;
+
+   private Map<String, Object> attributeValues = new HashMap<>();
+
+   private Map<String, Object> operationReturnValues = new HashMap<>();
+
+
+   @Override
+   public boolean isLocal() {
+      return false;
+   }
+
+   public MockTarget setLocal(boolean local) {
+      this.local = local;
+      return this;
+   }
+
+   @Override
+   public boolean isConnected() {
+      return connected;
+   }
+
+   public boolean isConnectable() {
+      return connectable;
+   }
+
+   public MockTarget setConnected(boolean connected) {
+      this.connected = connected;
+      return this;
+   }
+
+   public MockTarget setConnectable(boolean connectable) {
+      this.connectable = connectable;
+      return this;
+   }
+
+   public boolean isReady() {
+      return ready;
+   }
+
+   public MockTarget setReady(boolean ready) {
+      this.ready = ready;
+      return this;
+   }
+
+   public Map<String, Object> getAttributeValues() {
+      return attributeValues;
+   }
+
+   public void setAttributeValues(Map<String, Object> attributeValues) {
+      this.attributeValues = attributeValues;
+   }
+
+   public Map<String, Object> getOperationReturnValues() {
+      return operationReturnValues;
+   }
+
+   public void setOperationReturnValues(Map<String, Object> operationReturnValues) {
+      this.operationReturnValues = operationReturnValues;
+   }
+
+   public MockTarget() {
+      this(new TransportConfiguration(), UUID.randomUUID().toString());
+   }
+
+   public MockTarget(TransportConfiguration connector, String nodeID) {
+      super(connector, nodeID);
+   }
+
+   @Override
+   public void connect() throws Exception {
+      if (!connectable) {
+         throw new IllegalStateException("Target not connectable");
+      }
+
+      if (getNodeID() == null) {
+         setNodeID(UUID.randomUUID().toString());
+      }
+
+      connected = true;
+
+      fireConnectedEvent();
+   }
+
+   @Override
+   public void disconnect() throws Exception {
+      connected = false;
+
+      fireDisconnectedEvent();
+   }
+
+   @Override
+   public boolean checkReadiness() {
+      return ready;
+   }
+
+   @Override
+   public <T> T getAttribute(String resourceName, String attributeName, Class<T> attributeClass, int timeout) throws Exception {
+      checkConnection();
+
+      return (T)attributeValues.get(resourceName + attributeName);
+   }
+
+   @Override
+   public <T> T invokeOperation(String resourceName, String operationName, Object[] operationParams, Class<T> operationClass, int timeout) throws Exception {
+      checkConnection();
+
+      return (T)operationReturnValues.get(resourceName + operationName);
+   }
+
+   public void setAttributeValue(String resourceName, String attributeName, Object value) {
+      attributeValues.put(resourceName + attributeName, value);
+   }
+
+   public void setOperationReturnValue(String resourceName, String attributeName, Object value) {
+      operationReturnValues.put(resourceName + attributeName, value);
+   }
+
+   private void checkConnection() {
+      if (!connected) {
+         throw new IllegalStateException("Target not connected");
+      }
+   }
+}
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/MockTargetFactory.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/MockTargetFactory.java
new file mode 100644
index 0000000..e0e0933
--- /dev/null
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/MockTargetFactory.java
@@ -0,0 +1,105 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.targets;
+
+import org.apache.activemq.artemis.api.core.TransportConfiguration;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class MockTargetFactory extends AbstractTargetFactory {
+
+   private final List<MockTarget> createdTargets = new ArrayList<>();
+
+   private Boolean connectable = null;
+
+   private Boolean ready = null;
+
+   private Map<String, Object> attributeValues = null;
+
+   private Map<String, Object> operationReturnValues = null;
+
+   public Boolean getConnectable() {
+      return connectable;
+   }
+
+   public MockTargetFactory setConnectable(Boolean connectable) {
+      this.connectable = connectable;
+      return this;
+   }
+
+   public Boolean getReady() {
+      return ready;
+   }
+
+   public MockTargetFactory setReady(Boolean ready) {
+      this.ready = ready;
+      return this;
+   }
+
+   public Map<String, Object> getAttributeValues() {
+      return attributeValues;
+   }
+
+   public MockTargetFactory setAttributeValues(Map<String, Object> attributeValues) {
+      this.attributeValues = attributeValues;
+      return this;
+   }
+
+   public Map<String, Object> getOperationReturnValues() {
+      return operationReturnValues;
+   }
+
+   public MockTargetFactory setOperationReturnValues(Map<String, Object> operationReturnValues) {
+      this.operationReturnValues = operationReturnValues;
+      return this;
+   }
+
+   public List<MockTarget> getCreatedTargets() {
+      return createdTargets;
+   }
+
+   @Override
+   public Target createTarget(TransportConfiguration connector, String nodeID) {
+      MockTarget target = new MockTarget(connector, nodeID);
+
+      target.setUsername(getUsername());
+      target.setPassword(getPassword());
+
+      createdTargets.add(target);
+
+      if (connectable != null) {
+         target.setConnectable(connectable);
+      }
+
+      if (ready != null) {
+         target.setReady(ready);
+      }
+
+      if (attributeValues != null) {
+         target.setAttributeValues(attributeValues);
+      }
+
+      if (operationReturnValues != null) {
+         target.setOperationReturnValues(operationReturnValues);
+      }
+
+      return target;
+   }
+}
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/MockTargetProbe.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/MockTargetProbe.java
new file mode 100644
index 0000000..d8c3aa2
--- /dev/null
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/MockTargetProbe.java
@@ -0,0 +1,61 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.targets;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class MockTargetProbe extends TargetProbe {
+   private final Map<Target, Integer> targetExecutions = new HashMap<>();
+
+   private boolean checked;
+
+   public boolean isChecked() {
+      return checked;
+   }
+
+   public void setChecked(boolean checked) {
+      this.checked = checked;
+   }
+
+   public MockTargetProbe(String name, boolean checked) {
+      super(name);
+
+      this.checked = checked;
+   }
+
+   public int getTargetExecutions(Target target) {
+      Integer executions = targetExecutions.get(target);
+      return executions != null ? executions : 0;
+   }
+
+   public int setTargetExecutions(Target target, int executions) {
+      return targetExecutions.put(target, executions);
+   }
+
+   public void clearTargetExecutions() {
+      targetExecutions.clear();
+   }
+
+   @Override
+   public boolean check(Target target) {
+      targetExecutions.compute(target, (t, e) -> e == null ? 1 : e++);
+
+      return checked;
+   }
+}
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetKeyResolverTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetKeyResolverTest.java
new file mode 100644
index 0000000..5933696
--- /dev/null
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/balancing/targets/TargetKeyResolverTest.java
@@ -0,0 +1,110 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.server.balancing.targets;
+
+import org.apache.activemq.artemis.spi.core.remoting.Connection;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class TargetKeyResolverTest {
+
+   @Test
+   public void testClientIDKey() {
+      testClientIDKey("TEST", "TEST", null);
+   }
+
+   @Test
+   public void testClientIDKeyWithFilter() {
+      testClientIDKey("TEST", "TEST1234", "^.{4}");
+   }
+
+   private void testClientIDKey(String expected, String clientID, String filter) {
+      TargetKeyResolver targetKeyResolver = new TargetKeyResolver(TargetKey.CLIENT_ID, filter);
+
+      Assert.assertEquals(expected, targetKeyResolver.resolve(null, clientID, null));
+
+      Assert.assertEquals(TargetKeyResolver.DEFAULT_KEY_VALUE, targetKeyResolver.resolve(null, null, null));
+   }
+
+   @Test
+   public void testSNIHostKey() {
+      testSNIHostKey("TEST", "TEST", null);
+   }
+
+   @Test
+   public void testSNIHostKeyWithFilter() {
+      testSNIHostKey("TEST", "TEST1234", "^.{4}");
+   }
+
+   private void testSNIHostKey(String expected, String sniHost, String filter) {
+      Connection connection = Mockito.mock(Connection.class);
+
+      TargetKeyResolver targetKeyResolver = new TargetKeyResolver(TargetKey.SNI_HOST, filter);
+
+      Mockito.when(connection.getSNIHostName()).thenReturn(sniHost);
+      Assert.assertEquals(expected, targetKeyResolver.resolve(connection, null, null));
+
+      Assert.assertEquals(TargetKeyResolver.DEFAULT_KEY_VALUE, targetKeyResolver.resolve(null, null, null));
+
+      Mockito.when(connection.getSNIHostName()).thenReturn(null);
+      Assert.assertEquals(TargetKeyResolver.DEFAULT_KEY_VALUE, targetKeyResolver.resolve(null, null, null));
+   }
+
+   @Test
+   public void testSourceIPKey() {
+      testSourceIPKey("10.0.0.1", "10.0.0.1:12345", null);
+   }
+
+   @Test
+   public void testSourceIPKeyWithFilter() {
+      testSourceIPKey("10", "10.0.0.1:12345", "^[^.]+");
+   }
+
+   private void testSourceIPKey(String expected, String remoteAddress, String filter) {
+      Connection connection = Mockito.mock(Connection.class);
+
+      TargetKeyResolver targetKeyResolver = new TargetKeyResolver(TargetKey.SOURCE_IP, filter);
+
+      Mockito.when(connection.getRemoteAddress()).thenReturn(remoteAddress);
+      Assert.assertEquals(expected, targetKeyResolver.resolve(connection, null, null));
+
+      Assert.assertEquals(TargetKeyResolver.DEFAULT_KEY_VALUE, targetKeyResolver.resolve(null, null, null));
+
+      Mockito.when(connection.getRemoteAddress()).thenReturn(null);
+      Assert.assertEquals(TargetKeyResolver.DEFAULT_KEY_VALUE, targetKeyResolver.resolve(null, null, null));
+   }
+
+   @Test
+   public void testUserNameKey() {
+      testUserNameKey("TEST", "TEST", null);
+   }
+
+   @Test
+   public void testUserNameKeyWithFilter() {
+      testUserNameKey("TEST", "TEST1234", "^.{4}");
+   }
+
+   private void testUserNameKey(String expected, String username, String filter) {
+      TargetKeyResolver targetKeyResolver = new TargetKeyResolver(TargetKey.USER_NAME, filter);
+
+      Assert.assertEquals(expected, targetKeyResolver.resolve(null, null, username));
+
+      Assert.assertEquals(TargetKeyResolver.DEFAULT_KEY_VALUE, targetKeyResolver.resolve(null, null, null));
+   }
+}
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/group/impl/ClusteredResetMockTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/group/impl/ClusteredResetMockTest.java
index 9b6fce2..1234a08 100644
--- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/group/impl/ClusteredResetMockTest.java
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/group/impl/ClusteredResetMockTest.java
@@ -46,6 +46,7 @@ import org.apache.activemq.artemis.core.server.ActiveMQServer;
 import org.apache.activemq.artemis.core.server.Divert;
 import org.apache.activemq.artemis.core.server.Queue;
 import org.apache.activemq.artemis.core.server.QueueFactory;
+import org.apache.activemq.artemis.core.server.balancing.BrokerBalancer;
 import org.apache.activemq.artemis.core.server.cluster.Bridge;
 import org.apache.activemq.artemis.core.server.cluster.BroadcastGroup;
 import org.apache.activemq.artemis.core.server.cluster.ClusterConnection;
@@ -326,6 +327,16 @@ public class ClusteredResetMockTest extends ActiveMQTestBase {
       }
 
       @Override
+      public void registerBrokerBalancer(BrokerBalancer balancer) throws Exception {
+
+      }
+
+      @Override
+      public void unregisterBrokerBalancer(String name) throws Exception {
+
+      }
+
+      @Override
       public Object getResource(String resourceName) {
          return null;
       }
@@ -351,6 +362,16 @@ public class ClusteredResetMockTest extends ActiveMQTestBase {
       }
 
       @Override
+      public Object getAttribute(String resourceName, String attribute) {
+         return null;
+      }
+
+      @Override
+      public Object invokeOperation(String resourceName, String operation, Object[] params) throws Exception {
+         return null;
+      }
+
+      @Override
       public void start() throws Exception {
 
       }
diff --git a/artemis-server/src/test/resources/ConfigurationTest-full-config.xml b/artemis-server/src/test/resources/ConfigurationTest-full-config.xml
index 75efb9d..c9e9232 100644
--- a/artemis-server/src/test/resources/ConfigurationTest-full-config.xml
+++ b/artemis-server/src/test/resources/ConfigurationTest-full-config.xml
@@ -152,6 +152,38 @@
             <exclusive>false</exclusive>
          </divert>
       </diverts>
+      <broker-balancers>
+         <broker-balancer name="simple-balancer">
+            <target-key>USER_NAME</target-key>
+            <policy name="FIRST_ELEMENT"/>
+            <pool>
+               <static-connectors>
+                  <connector-ref>connector1</connector-ref>
+               </static-connectors>
+            </pool>
+         </broker-balancer>
+         <broker-balancer name="consistent-hash-balancer">
+            <target-key>SNI_HOST</target-key>
+            <target-key-filter>^[^.]+</target-key-filter>
+            <local-target-filter>DEFAULT</local-target-filter>
+            <policy name="CONSISTENT_HASH"/>
+            <pool>
+               <check-period>1000</check-period>
+               <local-target-enabled>true</local-target-enabled>
+               <discovery-group-ref discovery-group-name="dg1"/>
+            </pool>
+         </broker-balancer>
+         <broker-balancer name="least-connections-balancer">
+            <cache-timeout>60000</cache-timeout>
+            <policy name="LEAST_CONNECTIONS"/>
+            <pool>
+               <check-period>3000</check-period>
+               <quorum-size>2</quorum-size>
+               <quorum-timeout>1000</quorum-timeout>
+               <discovery-group-ref discovery-group-name="dg2"/>
+            </pool>
+         </broker-balancer>
+      </broker-balancers>
       <amqp-use-core-subscription-naming>true</amqp-use-core-subscription-naming>
       <queues>
          <queue name="queue1">
diff --git a/artemis-server/src/test/resources/ConfigurationTest-xinclude-config.xml b/artemis-server/src/test/resources/ConfigurationTest-xinclude-config.xml
index 540973e..0a55f84 100644
--- a/artemis-server/src/test/resources/ConfigurationTest-xinclude-config.xml
+++ b/artemis-server/src/test/resources/ConfigurationTest-xinclude-config.xml
@@ -143,6 +143,38 @@
             <exclusive>false</exclusive>
          </divert>
       </diverts>
+      <broker-balancers>
+         <broker-balancer name="simple-balancer">
+            <target-key>USER_NAME</target-key>
+            <policy name="FIRST_ELEMENT"/>
+            <pool>
+               <static-connectors>
+                  <connector-ref>connector1</connector-ref>
+               </static-connectors>
+            </pool>
+         </broker-balancer>
+         <broker-balancer name="consistent-hash-balancer">
+            <target-key>SNI_HOST</target-key>
+            <target-key-filter>^[^.]+</target-key-filter>
+            <local-target-filter>DEFAULT</local-target-filter>
+            <policy name="CONSISTENT_HASH"/>
+            <pool>
+               <check-period>1000</check-period>
+               <local-target-enabled>true</local-target-enabled>
+               <discovery-group-ref discovery-group-name="dg1"/>
+            </pool>
+         </broker-balancer>
+         <broker-balancer name="least-connections-balancer">
+            <cache-timeout>60000</cache-timeout>
+            <policy name="LEAST_CONNECTIONS"/>
+            <pool>
+               <check-period>3000</check-period>
+               <quorum-size>2</quorum-size>
+               <quorum-timeout>1000</quorum-timeout>
+               <discovery-group-ref discovery-group-name="dg2"/>
+            </pool>
+         </broker-balancer>
+      </broker-balancers>
       <amqp-use-core-subscription-naming>true</amqp-use-core-subscription-naming>
       <queues>
          <queue name="queue1">
diff --git a/docs/user-manual/en/SUMMARY.md b/docs/user-manual/en/SUMMARY.md
index b5f2dd3..d269a73 100644
--- a/docs/user-manual/en/SUMMARY.md
+++ b/docs/user-manual/en/SUMMARY.md
@@ -64,6 +64,7 @@
     * [Address Federation](federation-address.md)
     * [Queue Federation](federation-queue.md)
 * [High Availability and Failover](ha.md)
+* [Broker Balancers](broker-balancers.md)
 * [Graceful Server Shutdown](graceful-shutdown.md)
 * [Libaio Native Libraries](libaio.md)
 * [Thread management](thread-pooling.md)
diff --git a/docs/user-manual/en/broker-balancers.md b/docs/user-manual/en/broker-balancers.md
new file mode 100644
index 0000000..36a7907
--- /dev/null
+++ b/docs/user-manual/en/broker-balancers.md
@@ -0,0 +1,191 @@
+# Broker Balancers
+Apache ActiveMQ Artemis broker balancers allow incoming client connections to be distributed across multiple [target brokers](target-brokers).
+The target brokers are grouped in [pools](#pools) and the broker balancers use a [target key](#target-key)
+to select a target broker from a pool of brokers according to a [policy](#policies).
+
+### This feature is still **EXPERIMENTAL** and not meant to be run in production yet. Furthermore, its configuration can change until declared as **officially stable**.
+
+## Target Broker
+Target broker is a broker that can accept incoming client connections and is local or remote.
+The local target is a special target that represents the same broker hosting the broker balancer.
+The remote target is another reachable broker.
+
+## Target Key
+The broker balancer uses a target key to select a target broker.
+It is a string retrieved from an incoming client connection, the supported values are:
+* `CLIENT_ID` is the JMS client ID;
+* `SNI_HOST` is the hostname indicated by the client in the SNI extension of the TLS protocol;
+* `SOURCE_IP` is the source IP address of the client;
+* `USER_NAME` is the username indicated by the client.
+
+## Pools
+The pool is a group of target brokers and checks periodically their state.
+It provides a list of ready target brokers to distribute incoming client connections only when it is active.
+A pool becomes active when the minimum number of ready target brokers defined by the `quorum-size` parameter is reached.
+When it is not active, it doesn't provide any target avoiding weird distribution at startup or after a restart.
+Including the local broker in the target pool allows broker hosting the balancer to accept incoming client connections as well.
+By default, a pool doesn't include the local broker, to include it as a target the `local-target-enabled` parameter must be `true`.
+There are two pool types: [discovery pool](#discovery-pool) and [static pool](#static-pool).
+
+### Cluster Pool
+The cluster pool uses a [cluster connection](clusters.md#configuring-cluster-connections) to get the target brokers to add.
+Let's take a look at a cluster pool example from broker.xml that uses a cluster connection:
+```xml
+<pool>
+  <cluster-connection>cluster1</cluster-connection>
+</pool>
+```
+
+### Discovery Pool
+The discovery pool uses a [discovery group](clusters.md#discovery-groups) to discover the target brokers to add.
+Let's take a look at a discovery pool example from broker.xml that uses a discovery group:
+```xml
+<pool>
+    <discovery-group-ref discovery-group-name="dg1"/>
+</pool>
+```
+
+### Static Pool
+The static pool uses a list of static connectors to define the target brokers to add.
+Let's take a look at a static pool example from broker.xml that uses a list of static connectors:
+```xml
+<pool>
+    <static-connectors>
+        <connector-ref>connector1</connector-ref>
+        <connector-ref>connector2</connector-ref>
+        <connector-ref>connector3</connector-ref>
+    </static-connectors>
+</pool>
+```
+
+### Defining pools
+A pool is defined by the `pool` element that includes the following items:
+* the `username` element defines the username to connect to the target broker;
+* the `password` element defines the password to connect to the target broker;
+* the `check-period` element defines how often to check the target broker, measured in milliseconds, default is `5000`;
+* the `quorum-size` element defines the minimum number of ready targets to activate the pool, default is `1`;
+* the `quorum-timeout` element defines the timeout to get the minimum number of ready targets, measured in milliseconds, default is `3000`;
+* the `local-target-enabled` element defines whether the pool has to include a local target, default is `false`;
+* the `cluster-connection` element defines the [cluster connection](clusters.md#configuring-cluster-connections) used by the [cluster pool](#cluster-pool).
+* the `static-connectors` element defines a list of static connectors used by the [static pool](#static-pool);
+* the `discovery-group` element defines the [discovery group](clusters.md#discovery-groups) used by the [discovery pool](#discovery-pool).
+
+Let's take a look at a pool example from broker.xml:
+```xml
+<pool>
+    <quorum-size>2</quorum-size>
+    <check-period>1000</check-period>
+    <local-target-enabled>true</local-target-enabled>
+    <static-connectors>
+        <connector-ref>connector1</connector-ref>
+        <connector-ref>connector2</connector-ref>
+        <connector-ref>connector3</connector-ref>
+    </static-connectors>
+</pool>
+```
+
+## Policies
+The policy define how to select a broker from a pool. The included policies are:
+* `FIRST_ELEMENT` to select the first target broker from the pool which is ready. It is useful to select the ready target brokers
+  according to the priority defined with their sequence order, ie supposing there are 2 target brokers
+  this policy selects the second target broker only when the first target broker isn't ready.
+* `ROUND_ROBIN` to select a target sequentially from a pool, this policy is useful to evenly distribute;
+* `CONSISTENT_HASH` to select a target by a key. This policy always selects the same target broker for the same key until it is removed from the pool.
+* `LEAST_CONNECTIONS` to select the targets with the fewest active connections. This policy helps you maintain an equal distribution of active connections with the target brokers.
+
+A policy is defined by the `policy` element. Let's take a look at a policy example from broker.xml:
+```xml
+<policy name="FIRST_ELEMENT"/>
+```
+
+## Cache
+The broker balancer provides a cache with a timeout to improve the stickiness of the target broker selected,
+returning the same target broker for a target key as long as it is present in the cache and is ready.
+So a broker balancer with the cache enabled doesn't strictly follow the configured policy.
+By default, the cache is enabled, to disable the cache the `cache-timeout` parameter must be `0`.
+
+## Defining broker balancers
+A broker balancer is defined by `broker-balancer` element, it includes the following items:
+* the `name` attribute defines the name of the broker balancer;
+* the `target-key` element defines what key to select a target broker, the supported values are: `CLIENT_ID`, `SNI_HOST`, `SOURCE_IP`, `USER_NAME`, default is `SOURCE_IP`, see [target key](#target-key) for further details;
+* the `target-key-filter` element defines a regular expression to filter the resolved keys;
+* the `local-target-filter` element defines a regular expression to match the keys that have to return a local target;
+* the `cache-timeout` element is the time period for a target broker to remain in the cache, measured in milliseconds, setting `0` will disable the cache, default is `-1`, meaning no expiration;
+* the `pool` element defines the pool to group the target brokers, see [pools](#pools).
+* the `policy` element defines the policy used to select the target brokers, see [policies](#policies);
+
+Let's take a look at some broker balancer examples from broker.xml:
+```xml
+<broker-balancers>
+    <broker-balancer name="simple-balancer">
+        <policy name="FIRST_ELEMENT"/>
+        <pool>
+            <static-connectors>
+                <connector-ref>connector1</connector-ref>
+                <connector-ref>connector2</connector-ref>
+                <connector-ref>connector3</connector-ref>
+            </static-connectors>
+        </pool>
+    </broker-balancer>
+    <broker-balancer name="consistent-hash-balancer">
+        <target-key>USER_NAME</target-key>
+        <local-target-filter>admin</local-target-filter>
+        <policy name="CONSISTENT_HASH"/>
+        <pool>
+            <local-target-enabled>true</local-target-enabled>
+            <discovery-group-ref discovery-group-name="dg1"/>
+        </pool>
+    <policy name="CONSISTENT_HASH"/>
+    </broker-balancer>
+    <broker-balancer name="evenly-balancer">
+      <target-key>CLIENT_ID</target-key>
+      <target-key-filter>^.{3}</target-key-filter>
+      <policy name="LEAST_CONNECTIONS"/>
+      <pool>
+        <username>guest</username>
+        <password>guest</password>
+        <discovery-group-ref discovery-group-name="dg2"/>
+      </pool>
+    </broker-balancer>
+</broker-balancers>
+```
+
+## Broker Balancer Workflow
+The broker balancer workflow include the following steps:
+* Retrieve the target key from the incoming connection;
+* Return the local target broker if the target key matches the local filter;
+* Return the cached target broker if it is ready;
+* Get ready target brokers from the pool;
+* Select one target broker using the policy;
+* Add the selected broker in the cache;
+* Return the selected broker.
+
+Let's take a look at flowchart of the broker balancer workflow:
+![Broker Balancer Workflow](images/broker_balancer_workflow.png)
+
+
+## Redirection
+Apache ActiveMQ Artemis provides a native redirection for supported clients and a new management API for other clients.
+The native redirection can be enabled per acceptor and is supported only for AMQP, CORE and OPENWIRE clients.
+The acceptor with the `redirect-to` url parameter will redirect the incoming connections.
+The `redirect-to` url parameter specifies the name of the broker balancer to use,
+ie the following acceptor will redirect the incoming CORE client connections using the broker balancer with the name `simple-balancer`:
+
+```xml
+<acceptor name="artemis">tcp://0.0.0.0:61616?redirect-to=simple-balancer;protocols=CORE</acceptor>
+```
+### Native Redirect Sequence
+
+The clients supporting the native redirection connect to the acceptor with the redirection enabled.
+The acceptor sends to the client the target broker to redirect if it is ready and closes the connection.
+The client connects to the target broker if it has received one before getting disconnected
+otherwise it connected again to the acceptor with the redirection enabled.
+
+![Native Redirect Sequence](images/native_redirect_sequence.png)
+
+### Management API Redirect Sequence
+The clients not supporting the native redirection queries the management API of broker balancer
+to get the target broker to redirect. If the API returns a target broker the client connects to it
+otherwise the client queries again the API.
+
+![Management API Redirect Sequence](images/management_api_redirect_sequence.png)
diff --git a/docs/user-manual/en/images/broker_balancer_workflow.png b/docs/user-manual/en/images/broker_balancer_workflow.png
new file mode 100644
index 0000000..97560b6
Binary files /dev/null and b/docs/user-manual/en/images/broker_balancer_workflow.png differ
diff --git a/docs/user-manual/en/images/management_api_redirect_sequence.png b/docs/user-manual/en/images/management_api_redirect_sequence.png
new file mode 100644
index 0000000..371baa89
Binary files /dev/null and b/docs/user-manual/en/images/management_api_redirect_sequence.png differ
diff --git a/docs/user-manual/en/images/native_redirect_sequence.png b/docs/user-manual/en/images/native_redirect_sequence.png
new file mode 100644
index 0000000..d7466da
Binary files /dev/null and b/docs/user-manual/en/images/native_redirect_sequence.png differ
diff --git a/examples/features/broker-balancer/evenly-redirect/pom.xml b/examples/features/broker-balancer/evenly-redirect/pom.xml
new file mode 100644
index 0000000..d92571b
--- /dev/null
+++ b/examples/features/broker-balancer/evenly-redirect/pom.xml
@@ -0,0 +1,207 @@
+<?xml version='1.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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+   <modelVersion>4.0.0</modelVersion>
+
+   <parent>
+      <groupId>org.apache.activemq.examples</groupId>
+      <artifactId>broker-balancer</artifactId>
+      <version>2.18.0-SNAPSHOT</version>
+   </parent>
+
+   <artifactId>evenly-redirect</artifactId>
+   <packaging>jar</packaging>
+   <name>evenly-redirect</name>
+
+   <properties>
+      <activemq.basedir>${project.basedir}/../../../..</activemq.basedir>
+   </properties>
+
+   <dependencies>
+      <dependency>
+         <groupId>org.apache.activemq</groupId>
+         <artifactId>artemis-jms-client</artifactId>
+         <version>${project.version}</version>
+      </dependency>
+   </dependencies>
+
+   <build>
+      <plugins>
+         <plugin>
+            <groupId>org.apache.activemq</groupId>
+            <artifactId>artemis-maven-plugin</artifactId>
+            <executions>
+               <execution>
+                  <id>create0</id>
+                  <goals>
+                     <goal>create</goal>
+                  </goals>
+                  <configuration>
+                     <ignore>${noServer}</ignore>
+                     <instance>${basedir}/target/server0</instance>
+                     <allowAnonymous>true</allowAnonymous>
+                     <configuration>${basedir}/target/classes/activemq/server0</configuration>
+                     <!-- this makes it easier in certain envs -->
+                     <javaOptions>-Djava.net.preferIPv4Stack=true</javaOptions>
+                  </configuration>
+               </execution>
+               <execution>
+                  <id>create1</id>
+                  <goals>
+                     <goal>create</goal>
+                  </goals>
+                  <configuration>
+                     <ignore>${noServer}</ignore>
+                     <instance>${basedir}/target/server1</instance>
+                     <allowAnonymous>true</allowAnonymous>
+                     <configuration>${basedir}/target/classes/activemq/server1</configuration>
+                     <!-- this makes it easier in certain envs -->
+                     <javaOptions>-Djava.net.preferIPv4Stack=true</javaOptions>
+                  </configuration>
+               </execution>
+               <execution>
+                  <id>create2</id>
+                  <goals>
+                     <goal>create</goal>
+                  </goals>
+                  <configuration>
+                     <ignore>${noServer}</ignore>
+                     <instance>${basedir}/target/server2</instance>
+                     <allowAnonymous>true</allowAnonymous>
+                     <configuration>${basedir}/target/classes/activemq/server2</configuration>
+                     <!-- this makes it easier in certain envs -->
+                     <javaOptions>-Djava.net.preferIPv4Stack=true</javaOptions>
+                  </configuration>
+               </execution>
+               <execution>
+                  <id>start1</id>
+                  <goals>
+                     <goal>cli</goal>
+                  </goals>
+                  <configuration>
+                     <ignore>${noServer}</ignore>
+                     <spawn>true</spawn>
+                     <location>${basedir}/target/server1</location>
+                     <testURI>tcp://localhost:61617</testURI>
+                     <args>
+                        <param>run</param>
+                     </args>
+                     <name>server1</name>
+                  </configuration>
+               </execution>
+               <execution>
+                  <id>start2</id>
+                  <goals>
+                     <goal>cli</goal>
+                  </goals>
+                  <configuration>
+                     <ignore>${noServer}</ignore>
+                     <spawn>true</spawn>
+                     <location>${basedir}/target/server2</location>
+                     <testURI>tcp://localhost:61618</testURI>
+                     <args>
+                        <param>run</param>
+                     </args>
+                     <name>server1</name>
+                  </configuration>
+               </execution>
+               <execution>
+                  <id>start0</id>
+                  <goals>
+                     <goal>cli</goal>
+                  </goals>
+                  <configuration>
+                     <spawn>true</spawn>
+                     <ignore>${noServer}</ignore>
+                     <location>${basedir}/target/server0</location>
+                     <testURI>tcp://localhost:61616</testURI>
+                     <args>
+                        <param>run</param>
+                     </args>
+                     <name>server0</name>
+                  </configuration>
+               </execution>
+               <execution>
+                  <id>runClient</id>
+                  <goals>
+                     <goal>runClient</goal>
+                  </goals>
+                  <configuration>
+                     <!-- you may have to set export MAVEN_OPTS="-Djava.net.preferIPv4Stack=true"
+                          if you are on MacOS for instance -->
+                     <clientClass>org.apache.activemq.artemis.jms.example.EvenlyRedirectExample</clientClass>
+                  </configuration>
+               </execution>
+               <execution>
+                  <id>stop0</id>
+                  <goals>
+                     <goal>cli</goal>
+                  </goals>
+                  <configuration>
+                     <ignore>${noServer}</ignore>
+                     <location>${basedir}/target/server0</location>
+                     <args>
+                        <param>stop</param>
+                     </args>
+                  </configuration>
+               </execution>
+               <execution>
+                  <id>stop1</id>
+                  <goals>
+                     <goal>cli</goal>
+                  </goals>
+                  <configuration>
+                     <ignore>${noServer}</ignore>
+                     <location>${basedir}/target/server1</location>
+                     <args>
+                        <param>stop</param>
+                     </args>
+                  </configuration>
+               </execution>
+               <execution>
+                  <id>stop2</id>
+                  <goals>
+                     <goal>cli</goal>
+                  </goals>
+                  <configuration>
+                     <ignore>${noServer}</ignore>
+                     <location>${basedir}/target/server2</location>
+                     <args>
+                        <param>stop</param>
+                     </args>
+                  </configuration>
+               </execution>
+            </executions>
+            <dependencies>
+               <dependency>
+                  <groupId>org.apache.activemq.examples</groupId>
+                  <artifactId>evenly-redirect</artifactId>
+                  <version>${project.version}</version>
+               </dependency>
+            </dependencies>
+         </plugin>
+         <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-clean-plugin</artifactId>
+         </plugin>
+      </plugins>
+   </build>
+</project>
diff --git a/examples/features/broker-balancer/evenly-redirect/readme.md b/examples/features/broker-balancer/evenly-redirect/readme.md
new file mode 100644
index 0000000..05011d9
--- /dev/null
+++ b/examples/features/broker-balancer/evenly-redirect/readme.md
@@ -0,0 +1,8 @@
+# Evenly Redirect Example
+
+To run the example, simply type **mvn verify** from this directory, or **mvn -PnoServer verify** if you want to create and start the broker manually.
+
+This example demonstrates how incoming client connections are evenly redirected across two brokers
+using a third broker with a balancer to redirect incoming client connections,
+based on a least-connections policy and caching on a filtered prefix of the connection ClientID.
+
diff --git a/examples/features/broker-balancer/evenly-redirect/src/main/java/org/apache/activemq/artemis/jms/example/EvenlyRedirectExample.java b/examples/features/broker-balancer/evenly-redirect/src/main/java/org/apache/activemq/artemis/jms/example/EvenlyRedirectExample.java
new file mode 100644
index 0000000..40a8706
--- /dev/null
+++ b/examples/features/broker-balancer/evenly-redirect/src/main/java/org/apache/activemq/artemis/jms/example/EvenlyRedirectExample.java
@@ -0,0 +1,106 @@
+/*
+ * 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.jms.example;
+
+import javax.jms.Connection;
+import javax.jms.ConnectionFactory;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+
+import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;
+
+/**
+ * This example demonstrates how incoming client connections are evenly redirected across two brokers
+ * using a third broker with a broker balancer to redirect incoming client connections.
+ */
+public class EvenlyRedirectExample {
+
+   public static void main(final String[] args) throws Exception {
+
+      /**
+       * Step 1. Create a connection for producer0 and producer1, and send a few messages.
+       * the server0 will redirect the connection of each producer to a different target broker.
+       */
+      ConnectionFactory connectionFactoryProducer0 = new ActiveMQConnectionFactory("tcp://localhost:61616?ha=true&reconnectAttempts=30&clientID=FOO_PRODUCER");
+      ConnectionFactory connectionFactoryProducer1 = new ActiveMQConnectionFactory("tcp://localhost:61616?ha=true&reconnectAttempts=30&clientID=BAR_PRODUCER");
+
+      Connection connectionProducer0 = null;
+      Connection connectionProducer1 = null;
+
+      try {
+         connectionProducer0 = connectionFactoryProducer0.createConnection();
+         connectionProducer1 = connectionFactoryProducer1.createConnection();
+
+         for (Connection connectionProducer : new Connection[] {connectionProducer0, connectionProducer1}) {
+            Session session = connectionProducer.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+            Queue queue = session.createQueue("exampleQueue");
+            MessageProducer sender = session.createProducer(queue);
+            for (int i = 0; i < 100; i++) {
+               sender.send(session.createTextMessage("Hello world n" + i + " - " + connectionProducer.getClientID()));
+            }
+         }
+      } finally {
+         if (connectionProducer0 != null) {
+            connectionProducer0.close();
+         }
+
+         if (connectionProducer1 != null) {
+            connectionProducer1.close();
+         }
+      }
+
+      /**
+       * Step 2. create a connection for consumer0 and consumer1, and receive a few messages.
+       * the server0 will redirect the connection to the same target broker of the respective producer
+       * because the consumer and the producer connections have the same clientID prefix, which
+       * the balancer configuration filters the target key on and caches the target broker the policy selects.
+       */
+      ConnectionFactory connectionFactoryConsumer0 = new ActiveMQConnectionFactory("tcp://localhost:61616?ha=true&reconnectAttempts=30&clientID=BAR_CONSUMER");
+      ConnectionFactory connectionFactoryConsumer1 = new ActiveMQConnectionFactory("tcp://localhost:61616?ha=true&reconnectAttempts=30&clientID=FOO_CONSUMER");
+
+      Connection connectionConsumer0 = null;
+      Connection connectionConsumer1 = null;
+
+      try {
+         connectionConsumer0 = connectionFactoryConsumer0.createConnection();
+         connectionConsumer1 = connectionFactoryConsumer1.createConnection();
+
+         for (Connection connectionConsumer : new Connection[] {connectionConsumer0, connectionConsumer1}) {
+            connectionConsumer.start();
+            Session session = connectionConsumer.createSession(false, Session.AUTO_ACKNOWLEDGE);
+            Queue queue = session.createQueue("exampleQueue");
+            MessageConsumer consumer = session.createConsumer(queue);
+            for (int i = 0; i < 100; i++) {
+               TextMessage message = (TextMessage) consumer.receive(5000);
+               System.out.println("Received message " + message.getText() + "/" + connectionConsumer.getClientID());
+            }
+         }
+      } finally {
+         if (connectionConsumer0 != null) {
+            connectionConsumer0.close();
+         }
+
+         if (connectionConsumer1 != null) {
+            connectionConsumer1.close();
+         }
+      }
+   }
+}
diff --git a/examples/features/broker-balancer/evenly-redirect/src/main/resources/activemq/server0/broker.xml b/examples/features/broker-balancer/evenly-redirect/src/main/resources/activemq/server0/broker.xml
new file mode 100644
index 0000000..a303f42
--- /dev/null
+++ b/examples/features/broker-balancer/evenly-redirect/src/main/resources/activemq/server0/broker.xml
@@ -0,0 +1,135 @@
+<?xml version='1.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.
+-->
+
+<configuration xmlns="urn:activemq"
+               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+               xmlns:xi="http://www.w3.org/2001/XInclude"
+               xsi:schemaLocation="urn:activemq /schema/artemis-configuration.xsd">
+
+   <core xmlns="urn:activemq:core" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="urn:activemq:core ">
+
+      <name>0.0.0.0</name>
+
+
+      <persistence-enabled>false</persistence-enabled>
+
+      <journal-type>NIO</journal-type>
+
+      <!-- should the broker detect dead locks and other issues -->
+      <critical-analyzer>true</critical-analyzer>
+
+      <critical-analyzer-timeout>120000</critical-analyzer-timeout>
+
+      <critical-analyzer-check-period>60000</critical-analyzer-check-period>
+
+      <critical-analyzer-policy>HALT</critical-analyzer-policy>
+
+      <page-sync-timeout>60000</page-sync-timeout>
+
+      <acceptors>
+         <!-- Acceptor for every supported protocol -->
+         <acceptor name="artemis">tcp://0.0.0.0:61616?redirect-to=evenly-balancer;tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;amqpMinLargeMessageSize=102400;protocols=CORE,AMQP,STOMP,HORNETQ,MQTT,OPENWIRE;useEpoll=true;amqpCredits=1000;amqpLowCredits=300;amqpDuplicateDetection=true</acceptor>
+      </acceptors>
+
+      <connectors>
+         <connector name="server1">tcp://localhost:61617</connector>
+         <connector name="server2">tcp://localhost:61618</connector>
+      </connectors>
+
+      <broker-balancers>
+         <broker-balancer name="evenly-balancer">
+            <target-key>CLIENT_ID</target-key>
+            <target-key-filter>^.{3}</target-key-filter>
+            <local-target-filter>DEFAULT</local-target-filter>
+            <policy name="LEAST_CONNECTIONS"/>
+            <pool>
+               <username>guest</username>
+               <password>guest</password>
+               <static-connectors>
+                  <connector-ref>server1</connector-ref>
+                  <connector-ref>server2</connector-ref>
+               </static-connectors>
+            </pool>
+         </broker-balancer>
+      </broker-balancers>
+
+      <security-settings>
+         <security-setting match="#">
+            <permission type="createNonDurableQueue" roles="guest"/>
+            <permission type="deleteNonDurableQueue" roles="guest"/>
+            <permission type="createDurableQueue" roles="guest"/>
+            <permission type="deleteDurableQueue" roles="guest"/>
+            <permission type="createAddress" roles="guest"/>
+            <permission type="deleteAddress" roles="guest"/>
+            <permission type="consume" roles="guest"/>
+            <permission type="browse" roles="guest"/>
+            <permission type="send" roles="guest"/>
+            <permission type="manage" roles="guest"/>
+         </security-setting>
+      </security-settings>
+
+      <address-settings>
+         <!-- if you define auto-create on certain queues, management has to be auto-create -->
+         <address-setting match="activemq.management#">
+            <dead-letter-address>DLQ</dead-letter-address>
+            <expiry-address>ExpiryQueue</expiry-address>
+            <redelivery-delay>0</redelivery-delay>
+            <!-- with -1 only the global-max-size is in use for limiting -->
+            <max-size-bytes>-1</max-size-bytes>
+            <message-counter-history-day-limit>10</message-counter-history-day-limit>
+            <address-full-policy>PAGE</address-full-policy>
+            <auto-create-queues>true</auto-create-queues>
+            <auto-create-addresses>true</auto-create-addresses>
+            <auto-create-jms-queues>true</auto-create-jms-queues>
+            <auto-create-jms-topics>true</auto-create-jms-topics>
+         </address-setting>
+         <!--default for catch all-->
+         <address-setting match="#">
+            <dead-letter-address>DLQ</dead-letter-address>
+            <expiry-address>ExpiryQueue</expiry-address>
+            <redelivery-delay>0</redelivery-delay>
+            <!-- with -1 only the global-max-size is in use for limiting -->
+            <max-size-bytes>-1</max-size-bytes>
+            <message-counter-history-day-limit>10</message-counter-history-day-limit>
+            <address-full-policy>PAGE</address-full-policy>
+            <auto-create-queues>true</auto-create-queues>
+            <auto-create-addresses>true</auto-create-addresses>
+            <auto-create-jms-queues>true</auto-create-jms-queues>
+            <auto-create-jms-topics>true</auto-create-jms-topics>
+         </address-setting>
+      </address-settings>
+
+      <addresses>
+         <address name="DLQ">
+            <anycast>
+               <queue name="DLQ" />
+            </anycast>
+         </address>
+         <address name="ExpiryQueue">
+            <anycast>
+               <queue name="ExpiryQueue" />
+            </anycast>
+         </address>
+
+      </addresses>
+
+   </core>
+</configuration>
diff --git a/examples/features/broker-balancer/evenly-redirect/src/main/resources/activemq/server1/broker.xml b/examples/features/broker-balancer/evenly-redirect/src/main/resources/activemq/server1/broker.xml
new file mode 100644
index 0000000..a9800a8
--- /dev/null
+++ b/examples/features/broker-balancer/evenly-redirect/src/main/resources/activemq/server1/broker.xml
@@ -0,0 +1,113 @@
+<?xml version='1.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.
+-->
+
+<configuration xmlns="urn:activemq"
+               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+               xmlns:xi="http://www.w3.org/2001/XInclude"
+               xsi:schemaLocation="urn:activemq /schema/artemis-configuration.xsd">
+
+   <core xmlns="urn:activemq:core" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="urn:activemq:core ">
+
+      <name>0.0.0.0</name>
+
+
+      <persistence-enabled>false</persistence-enabled>
+
+      <journal-type>NIO</journal-type>
+
+      <!-- should the broker detect dead locks and other issues -->
+      <critical-analyzer>true</critical-analyzer>
+
+      <critical-analyzer-timeout>120000</critical-analyzer-timeout>
+
+      <critical-analyzer-check-period>60000</critical-analyzer-check-period>
+
+      <critical-analyzer-policy>HALT</critical-analyzer-policy>
+
+      <page-sync-timeout>60000</page-sync-timeout>
+
+      <acceptors>
+         <!-- Acceptor for every supported protocol -->
+         <acceptor name="artemis">tcp://0.0.0.0:61617?tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;amqpMinLargeMessageSize=102400;protocols=CORE,AMQP,STOMP,HORNETQ,MQTT,OPENWIRE;useEpoll=true;amqpCredits=1000;amqpLowCredits=300;amqpDuplicateDetection=true</acceptor>
+      </acceptors>
+
+      <security-settings>
+         <security-setting match="#">
+            <permission type="createNonDurableQueue" roles="guest"/>
+            <permission type="deleteNonDurableQueue" roles="guest"/>
+            <permission type="createDurableQueue" roles="guest"/>
+            <permission type="deleteDurableQueue" roles="guest"/>
+            <permission type="createAddress" roles="guest"/>
+            <permission type="deleteAddress" roles="guest"/>
+            <permission type="consume" roles="guest"/>
+            <permission type="browse" roles="guest"/>
+            <permission type="send" roles="guest"/>
+            <permission type="manage" roles="guest"/>
+         </security-setting>
+      </security-settings>
+
+      <address-settings>
+         <!-- if you define auto-create on certain queues, management has to be auto-create -->
+         <address-setting match="activemq.management#">
+            <dead-letter-address>DLQ</dead-letter-address>
+            <expiry-address>ExpiryQueue</expiry-address>
+            <redelivery-delay>0</redelivery-delay>
+            <!-- with -1 only the global-max-size is in use for limiting -->
+            <max-size-bytes>-1</max-size-bytes>
+            <message-counter-history-day-limit>10</message-counter-history-day-limit>
+            <address-full-policy>PAGE</address-full-policy>
+            <auto-create-queues>true</auto-create-queues>
+            <auto-create-addresses>true</auto-create-addresses>
+            <auto-create-jms-queues>true</auto-create-jms-queues>
+            <auto-create-jms-topics>true</auto-create-jms-topics>
+         </address-setting>
+         <!--default for catch all-->
+         <address-setting match="#">
+            <dead-letter-address>DLQ</dead-letter-address>
+            <expiry-address>ExpiryQueue</expiry-address>
+            <redelivery-delay>0</redelivery-delay>
+            <!-- with -1 only the global-max-size is in use for limiting -->
+            <max-size-bytes>-1</max-size-bytes>
+            <message-counter-history-day-limit>10</message-counter-history-day-limit>
+            <address-full-policy>PAGE</address-full-policy>
+            <auto-create-queues>true</auto-create-queues>
+            <auto-create-addresses>true</auto-create-addresses>
+            <auto-create-jms-queues>true</auto-create-jms-queues>
+            <auto-create-jms-topics>true</auto-create-jms-topics>
+         </address-setting>
+      </address-settings>
+
+      <addresses>
+         <address name="DLQ">
+            <anycast>
+               <queue name="DLQ" />
+            </anycast>
+         </address>
+         <address name="ExpiryQueue">
+            <anycast>
+               <queue name="ExpiryQueue" />
+            </anycast>
+         </address>
+
+      </addresses>
+
+   </core>
+</configuration>
diff --git a/examples/features/broker-balancer/evenly-redirect/src/main/resources/activemq/server2/broker.xml b/examples/features/broker-balancer/evenly-redirect/src/main/resources/activemq/server2/broker.xml
new file mode 100644
index 0000000..ed92197
--- /dev/null
+++ b/examples/features/broker-balancer/evenly-redirect/src/main/resources/activemq/server2/broker.xml
@@ -0,0 +1,113 @@
+<?xml version='1.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.
+-->
+
+<configuration xmlns="urn:activemq"
+               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+               xmlns:xi="http://www.w3.org/2001/XInclude"
+               xsi:schemaLocation="urn:activemq /schema/artemis-configuration.xsd">
+
+   <core xmlns="urn:activemq:core" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="urn:activemq:core ">
+
+      <name>0.0.0.0</name>
+
+
+      <persistence-enabled>false</persistence-enabled>
+
+      <journal-type>NIO</journal-type>
+
+      <!-- should the broker detect dead locks and other issues -->
+      <critical-analyzer>true</critical-analyzer>
+
+      <critical-analyzer-timeout>120000</critical-analyzer-timeout>
+
+      <critical-analyzer-check-period>60000</critical-analyzer-check-period>
+
+      <critical-analyzer-policy>HALT</critical-analyzer-policy>
+
+      <page-sync-timeout>60000</page-sync-timeout>
+
+      <acceptors>
+         <!-- Acceptor for every supported protocol -->
+         <acceptor name="artemis">tcp://0.0.0.0:61618?tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;amqpMinLargeMessageSize=102400;protocols=CORE,AMQP,STOMP,HORNETQ,MQTT,OPENWIRE;useEpoll=true;amqpCredits=1000;amqpLowCredits=300;amqpDuplicateDetection=true</acceptor>
+      </acceptors>
+
+      <security-settings>
+         <security-setting match="#">
+            <permission type="createNonDurableQueue" roles="guest"/>
+            <permission type="deleteNonDurableQueue" roles="guest"/>
+            <permission type="createDurableQueue" roles="guest"/>
+            <permission type="deleteDurableQueue" roles="guest"/>
+            <permission type="createAddress" roles="guest"/>
+            <permission type="deleteAddress" roles="guest"/>
+            <permission type="consume" roles="guest"/>
+            <permission type="browse" roles="guest"/>
+            <permission type="send" roles="guest"/>
+            <permission type="manage" roles="guest"/>
+         </security-setting>
+      </security-settings>
+
+      <address-settings>
+         <!-- if you define auto-create on certain queues, management has to be auto-create -->
+         <address-setting match="activemq.management#">
+            <dead-letter-address>DLQ</dead-letter-address>
+            <expiry-address>ExpiryQueue</expiry-address>
+            <redelivery-delay>0</redelivery-delay>
+            <!-- with -1 only the global-max-size is in use for limiting -->
+            <max-size-bytes>-1</max-size-bytes>
+            <message-counter-history-day-limit>10</message-counter-history-day-limit>
+            <address-full-policy>PAGE</address-full-policy>
+            <auto-create-queues>true</auto-create-queues>
+            <auto-create-addresses>true</auto-create-addresses>
+            <auto-create-jms-queues>true</auto-create-jms-queues>
+            <auto-create-jms-topics>true</auto-create-jms-topics>
+         </address-setting>
+         <!--default for catch all-->
+         <address-setting match="#">
+            <dead-letter-address>DLQ</dead-letter-address>
+            <expiry-address>ExpiryQueue</expiry-address>
+            <redelivery-delay>0</redelivery-delay>
+            <!-- with -1 only the global-max-size is in use for limiting -->
+            <max-size-bytes>-1</max-size-bytes>
+            <message-counter-history-day-limit>10</message-counter-history-day-limit>
+            <address-full-policy>PAGE</address-full-policy>
+            <auto-create-queues>true</auto-create-queues>
+            <auto-create-addresses>true</auto-create-addresses>
+            <auto-create-jms-queues>true</auto-create-jms-queues>
+            <auto-create-jms-topics>true</auto-create-jms-topics>
+         </address-setting>
+      </address-settings>
+
+      <addresses>
+         <address name="DLQ">
+            <anycast>
+               <queue name="DLQ" />
+            </anycast>
+         </address>
+         <address name="ExpiryQueue">
+            <anycast>
+               <queue name="ExpiryQueue" />
+            </anycast>
+         </address>
+
+      </addresses>
+
+   </core>
+</configuration>
diff --git a/examples/features/broker-balancer/pom.xml b/examples/features/broker-balancer/pom.xml
new file mode 100644
index 0000000..682307b
--- /dev/null
+++ b/examples/features/broker-balancer/pom.xml
@@ -0,0 +1,56 @@
+<?xml version='1.0'?>
... 2297 lines suppressed ...

[activemq-artemis] 01/03: ARTEMIS-3275 Lock CORE client communication during failover retries

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/activemq-artemis.git

commit 6d2b96c79e75fb230ec0fd9f34d7910d7644593f
Author: Domenico Francesco Bruscino <br...@apache.org>
AuthorDate: Tue Jun 22 09:52:48 2021 +0200

    ARTEMIS-3275 Lock CORE client communication during failover retries
---
 .../core/client/impl/ClientSessionFactoryImpl.java | 38 +++++++++++-------
 .../core/client/impl/ClientSessionImpl.java        | 46 ++++++++++++++--------
 .../core/client/impl/ClientSessionInternal.java    |  2 +
 .../artemis/core/protocol/core/Channel.java        |  7 ++++
 .../protocol/core/impl/ActiveMQSessionContext.java |  2 +-
 .../core/protocol/core/impl/ChannelImpl.java       |  5 +++
 .../integration/cluster/failover/FailoverTest.java | 41 +++++++++++++++++++
 .../integration/cluster/util/BackupSyncDelay.java  |  5 +++
 8 files changed, 114 insertions(+), 32 deletions(-)

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 40fb156..92236f4 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
@@ -632,10 +632,19 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
 
                connector = null;
 
-               boolean allSessionReconnected;
+               HashSet<ClientSessionInternal> sessionsToFailover;
+               synchronized (sessions) {
+                  sessionsToFailover = new HashSet<>(sessions);
+               }
+
+               for (ClientSessionInternal session : sessionsToFailover) {
+                  session.preHandleFailover(connection);
+               }
+
+               boolean allSessionReconnected = false;
                int failedReconnectSessionsCounter = 0;
                do {
-                  allSessionReconnected = reconnectSessions(oldConnection, reconnectAttempts, me);
+                  allSessionReconnected = reconnectSessions(sessionsToFailover, oldConnection, reconnectAttempts, me);
                   if (oldConnection != null) {
                      oldConnection.destroy();
                   }
@@ -644,10 +653,19 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
                      failedReconnectSessionsCounter++;
                      oldConnection = connection;
                      connection = null;
+
+                     // Wait for retry when the connection is established but not all session are reconnected.
+                     if ((reconnectAttempts == -1 || failedReconnectSessionsCounter < reconnectAttempts) && oldConnection != null) {
+                        waitForRetry(retryInterval);
+                     }
                   }
                }
                while ((reconnectAttempts == -1 || failedReconnectSessionsCounter < reconnectAttempts) && !allSessionReconnected);
 
+               for (ClientSessionInternal session : sessionsToFailover) {
+                  session.postHandleFailover(connection, allSessionReconnected);
+               }
+
                if (oldConnection != null) {
                   oldConnection.destroy();
                }
@@ -764,18 +782,10 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
    /*
     * Re-attach sessions all pre-existing sessions to the new remoting connection
     */
-   private boolean reconnectSessions(final RemotingConnection oldConnection,
-                                  final int reconnectAttempts,
-                                  final ActiveMQException cause) {
-      HashSet<ClientSessionInternal> sessionsToFailover;
-      synchronized (sessions) {
-         sessionsToFailover = new HashSet<>(sessions);
-      }
-
-      for (ClientSessionInternal session : sessionsToFailover) {
-         session.preHandleFailover(connection);
-      }
-
+   private boolean reconnectSessions(final Set<ClientSessionInternal> sessionsToFailover,
+                                     final RemotingConnection oldConnection,
+                                     final int reconnectAttempts,
+                                     final ActiveMQException cause) {
       getConnectionWithRetry(reconnectAttempts, oldConnection);
 
       if (connection == null) {
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 d3a66a5..33e5a77 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
@@ -141,6 +141,8 @@ public final class ClientSessionImpl implements ClientSessionInternal, FailureLi
 
    private volatile boolean mayAttemptToFailover = true;
 
+   private volatile boolean resetCreditManager = false;
+
    /**
     * Current XID. this will be used in case of failover
     */
@@ -1387,8 +1389,6 @@ public final class ClientSessionImpl implements ClientSessionInternal, FailureLi
             return true;
          }
 
-         boolean resetCreditManager = false;
-
          try {
 
             // TODO remove this and encapsulate it
@@ -1463,30 +1463,42 @@ public final class ClientSessionImpl implements ClientSessionInternal, FailureLi
          } catch (Throwable t) {
             ActiveMQClientLogger.LOGGER.failedToHandleFailover(t);
             suc = false;
-         } finally {
-            sessionContext.releaseCommunications();
          }
+      }
+
+      return suc;
+   }
 
-         if (resetCreditManager) {
-            synchronized (producerCreditManager) {
-               producerCreditManager.reset();
+   @Override
+   public void postHandleFailover(RemotingConnection connection, boolean successful) {
+      sessionContext.releaseCommunications();
+
+      if (successful) {
+         synchronized (this) {
+            if (closed) {
+               return;
             }
 
-            // Also need to send more credits for consumers, otherwise the system could hand with the server
-            // not having any credits to send
-         }
-      }
+            if (resetCreditManager) {
+               synchronized (producerCreditManager) {
+                  producerCreditManager.reset();
+               }
 
-      HashMap<String, String> metaDataToSend;
+               resetCreditManager = false;
 
-      synchronized (metadata) {
-         metaDataToSend = new HashMap<>(metadata);
-      }
+               // Also need to send more credits for consumers, otherwise the system could hand with the server
+               // not having any credits to send
+            }
+         }
 
-      sessionContext.resetMetadata(metaDataToSend);
+         HashMap<String, String> metaDataToSend;
 
-      return suc;
+         synchronized (metadata) {
+            metaDataToSend = new HashMap<>(metadata);
+         }
 
+         sessionContext.resetMetadata(metaDataToSend);
+      }
    }
 
    @Override
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientSessionInternal.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientSessionInternal.java
index a3700b2..173087d 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientSessionInternal.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientSessionInternal.java
@@ -68,6 +68,8 @@ public interface ClientSessionInternal extends ClientSession {
 
    boolean handleFailover(RemotingConnection backupConnection, ActiveMQException cause);
 
+   void postHandleFailover(RemotingConnection connection, boolean successful);
+
    RemotingConnection getConnection();
 
    void cleanUp(boolean failingOver) throws ActiveMQException;
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 372cad4..1281729 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
@@ -185,6 +185,13 @@ public interface Channel {
    int getLastConfirmedCommandID();
 
    /**
+    * queries if this channel is locked. This method is designed for use in monitoring of the system state, not for synchronization control.
+    *
+    * @return true it the channel is locked and false otherwise
+    */
+   boolean isLocked();
+
+   /**
     * locks the channel.
     * <p>
     * While locked no packets can be sent or received
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 4e122a9..811ebef 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
@@ -139,7 +139,7 @@ public class ActiveMQSessionContext extends SessionContext {
    private String name;
    private boolean killed;
 
-   protected Channel getSessionChannel() {
+   public Channel getSessionChannel() {
       return sessionChannel;
    }
 
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 4ecd2c6..18bb08c 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
@@ -690,6 +690,11 @@ public final class ChannelImpl implements Channel {
    }
 
    @Override
+   public boolean isLocked() {
+      return failingOver;
+   }
+
+   @Override
    public void lock() {
       if (logger.isTraceEnabled()) {
          logger.trace("RemotingConnectionID=" + (connection == null ? "NULL" : connection.getID()) + " lock channel " + this);
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/failover/FailoverTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/failover/FailoverTest.java
index 3dcf9a9..43756f1 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/failover/FailoverTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/cluster/failover/FailoverTest.java
@@ -27,6 +27,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.apache.activemq.artemis.api.core.ActiveMQDuplicateIdException;
 import org.apache.activemq.artemis.api.core.ActiveMQException;
@@ -50,8 +51,10 @@ 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.ClientSessionFactoryImpl;
 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.Channel;
 import org.apache.activemq.artemis.core.protocol.core.Packet;
+import org.apache.activemq.artemis.core.protocol.core.impl.ActiveMQSessionContext;
 import org.apache.activemq.artemis.core.protocol.core.impl.ChannelImpl;
 import org.apache.activemq.artemis.core.protocol.core.impl.PacketImpl;
 import org.apache.activemq.artemis.core.protocol.core.impl.RemotingConnectionImpl;
@@ -1972,6 +1975,44 @@ public class FailoverTest extends FailoverTestBase {
    }
 
    @Test(timeout = 120000)
+   public void testChannelStateDuringFailover() throws Exception {
+      locator.setBlockOnNonDurableSend(true).setBlockOnDurableSend(true).setBlockOnAcknowledge(true).setReconnectAttempts(300).setRetryInterval(100);
+
+      sf = createSessionFactoryAndWaitForTopology(locator, 2);
+
+      final AtomicBoolean channelLockedDuringFailover = new AtomicBoolean(false);
+
+      ClientSession session = createSession(sf, true, true, 0);
+
+      backupServer.addInterceptor(
+         new Interceptor() {
+            private int index = 0;
+
+            @Override
+            public boolean intercept(Packet packet, RemotingConnection connection) throws ActiveMQException {
+               if (index < 1 && packet.getType() == PacketImpl.CREATESESSION) {
+                  sf.getConnection().addCloseListener(() -> {
+                     index++;
+                     ActiveMQSessionContext sessionContext = (ActiveMQSessionContext)((ClientSessionInternal)session).getSessionContext();
+                     channelLockedDuringFailover.set(sessionContext.getSessionChannel().isLocked());
+                  });
+
+                  Channel sessionChannel = ((RemotingConnectionImpl)connection).getChannel(ChannelImpl.CHANNEL_ID.SESSION.id, -1);
+                  sessionChannel.send(new ActiveMQExceptionMessage(new ActiveMQInternalErrorException()));
+                  return false;
+               }
+               return true;
+            }
+         });
+
+      session.start();
+
+      crash(session);
+
+      Assert.assertTrue(channelLockedDuringFailover.get());
+   }
+
+   @Test(timeout = 120000)
    public void testForceBlockingReturn() throws Exception {
       locator.setBlockOnNonDurableSend(true).setBlockOnDurableSend(true).setBlockOnAcknowledge(true).setReconnectAttempts(300).setRetryInterval(100);
 
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 b0af71b..287d730 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
@@ -301,6 +301,11 @@ public class BackupSyncDelay implements Interceptor {
       }
 
       @Override
+      public boolean isLocked() {
+         return false;
+      }
+
+      @Override
       public void lock() {
          throw new UnsupportedOperationException();
       }