ActiveMQ vs RabbitMQ: Key Differences, JMS Support and How to Choose
ActiveMQ and RabbitMQ are open-source message brokers built around different standards. ActiveMQ is a Java broker designed around the JMS API: applications send to queues and topics through a JMS client, and OpenWire is its native wire protocol. RabbitMQ is written in Erlang and designed around AMQP 0-9-1: producers publish to exchanges, bindings decide which queues receive a copy, and client libraries exist for most languages. As a rule, an estate of Java services written against JMS fits ActiveMQ's model, and a polyglot estate that needs flexible routing fits RabbitMQ's.
Several of the differences that decide the choice in 2026 are recent. RabbitMQ 4.0 removed classic queue mirroring, so replication now comes from quorum queues, built on Raft, and from streams, while ActiveMQ Classic still fails over through storage that a primary and a standby share. ActiveMQ's JMS 2.0 support is still partial, and open-source RabbitMQ offers JMS through a client library, with broker-side JMS only in its commercial edition. The two projects also patch differently: ActiveMQ ships free fixes for three release lines, RabbitMQ only for the series still in community support.
On this page, ActiveMQ means ActiveMQ Classic, the 5.x and 6.x line. Apache Artemis, once ActiveMQ's next-generation broker, has been a separate Apache project since November 2025, and it changes the comparison in ways covered near the end. Redisson is our product. We sell neither broker; Redisson PRO, which implements JMS on Valkey and Redis, has its own section. Versions and defaults were checked on November 9, 2026, against ActiveMQ Classic 6.4.0 and RabbitMQ 4.4.0.
ActiveMQ vs RabbitMQ at a Glance
| ActiveMQ Classic | RabbitMQ | |
|---|---|---|
| What it is | A Java message broker from the Apache Software Foundation | A message broker written in Erlang; open source, with a commercial edition, Tanzu RabbitMQ, from Broadcom |
| Licence | Apache License 2.0 | Mozilla Public License 2.0; Tanzu RabbitMQ is commercial |
| Latest releases | 6.4.0, 6.3.3 and 5.19.12, all on October 27, 2026 | 4.4.0, on October 20, 2026 |
| Runtime | JVM; Java 17 or later for 6.x | Erlang runtime |
| Native protocol | OpenWire, spoken by the JMS client | AMQP 0-9-1 |
| Other protocols | AMQP 1.0, STOMP, MQTT 3.1 and 3.1.1, WebSocket. Since 6.2.6 and 5.19.7, the shipped configuration enables OpenWire only | AMQP 1.0, built in since 4.0; MQTT 3.1, 3.1.1 and 5.0, STOMP, WebSocket and the Streams protocol through bundled plugins |
| Routing | JMS queues and topics, plus wildcards, composite destinations, virtual topics and selectors | Exchanges (direct, topic, fanout, headers) route messages to queues through bindings |
| JMS | JMS 1.1 and XA; JMS 2.0 and Jakarta Messaging 3.1 partial | An open-source JMS client with gaps; broker-side JMS queues and topics in the commercial Tanzu edition. No XA |
| Queue types | One; persistence is chosen per message | Classic (one node), quorum (replicated) and stream (a replayable log) |
| Failover | A primary and a standby share a file system or a database | Quorum queues replicate across nodes with Raft, and streams with a protocol of their own; no shared storage |
| Scaling out | Networks of brokers forward messages to where consumers are | Queues spread over cluster nodes; federation and shovels link clusters |
| Failed messages | 7 deliveries, 1 second apart, then the shared ActiveMQ.DLQ | Quorum queues: a delivery limit of 20, then the queue's dead-letter exchange, or dropped if it has none |
| Delayed delivery | A scheduler, off by default; JMS delivery delay throws | Quorum queues, since 4.4, through the x-opt-delivery-time header |
| Priority | Off by default; KahaDB groups priorities into three bands | Classic queues up to 255 levels; quorum queues 32 strict levels since 4.3 |
| Replay | No log to replay; retroactive consumers get a recent window of topic messages | Streams, from an offset or a timestamp |
| Message size | No hard limit by default; the shipped OpenWire connector caps frames at 10 MB | 16 MiB by default, configurable up to 512 MiB |
| Management | Web console on port 8161, bound to localhost by default; JMX and Jolokia | Management plugin with a web UI on port 15672 and an HTTP API; Prometheus endpoint |
| Kubernetes | Docker image; no official operator | Official open-source Cluster and Messaging Topology Operators |
| Embedded in a JVM | Yes, over the VM transport | No; tests run it in a container |
| Free fixes | Three active lines: 6.4, 6.3 and 5.19; no end dates published | Series in community support, about nine months each: 4.4 and 4.3 today |
How Each Broker Routes Messages
ActiveMQ follows JMS, which defines two kinds of destination. A message sent to a queue goes to one consumer; a message published to a topic goes to every subscriber, and a durable subscription keeps messages for a subscriber while it is offline. ActiveMQ adds routing of its own: wildcard subscriptions such as ORDERS.>, composite destinations that deliver one send to several destinations, virtual topics that give each group of consumers its own queue behind a topic, and message selectors, SQL-like expressions over headers and properties that filter what a consumer receives. Destinations are created on first use, and the sending code names the queue or topic it writes to.
RabbitMQ's AMQP 0-9-1 model puts a router between producer and queue. A producer publishes to an exchange with a routing key, and bindings decide which queues get a copy. A direct exchange matches the key exactly; a topic exchange matches patterns in which * stands for one dot-separated word and # for zero or more; a fanout exchange copies to every bound queue; and a headers exchange matches on header values. The default exchange routes by queue name, which is how a plain work queue looks the same on both brokers. Queues are usually declared by the applications that use them, and operators shape their behaviour at runtime with policies.
The two models differ in who owns routing. In ActiveMQ the sender picks the destination, and the JMS code says which. In RabbitMQ the sender picks an exchange and a key, and the bindings, which can change without touching any producer, decide where messages land. RabbitMQ is the more flexible router; ActiveMQ is the more direct fit for code that already speaks JMS.
ActiveMQ Concepts in RabbitMQ Terms
| ActiveMQ Classic | Closest RabbitMQ equivalent |
|---|---|
| Queue | A quorum queue, published to through the default exchange with the queue's name as the routing key |
| Topic with non-durable subscribers | A fanout or topic exchange; each subscriber binds an exclusive, auto-delete queue of its own |
| Durable subscription | A durable queue per subscriber, bound to the exchange |
Virtual topic: producers send to VirtualTopic.Orders, each consumer group reads Consumer.Billing.VirtualTopic.Orders | An exchange with one queue per consumer group; the group's consumers share that queue |
| Composite destination | One exchange bound to several queues, or exchange-to-exchange bindings |
Wildcards * and > | Topic exchange binding patterns * and # |
| Message selector | Routing keys or a headers exchange. Queue consumers cannot filter in open-source RabbitMQ; stream consumers can, with AMQP 1.0 filter expressions |
Message groups (JMSXGroupID) | A consistent hash exchange that spreads keys over several queues, each with a single active consumer |
| Exclusive consumer | Single active consumer |
| Temporary queue for request and reply | An exclusive, server-named queue, or direct reply-to |
Redelivery policy and ActiveMQ.DLQ | The quorum queue's delivery limit, delayed retry and dead-letter exchange |
Scheduled delivery (AMQ_SCHEDULED_*) | x-opt-delivery-time on quorum queues; no repeat or cron |
| Advisory topics | The event exchange plugin and the management HTTP API |
Protocols and Clients
ActiveMQ's native protocol is OpenWire, spoken by its Java JMS client and by NMS, its .NET client, which is still maintained; CMS, the C++ client, has had no release since 2019. ActiveMQ also speaks AMQP 1.0, since 5.8, STOMP, MQTT 3.1 and 3.1.1, and STOMP and MQTT over WebSocket. It does not support MQTT 5. One change is easy to miss: since 6.2.6 and 5.19.7, released in May 2026, the activemq.xml shipped with every active line enables only the OpenWire connector and comments out AMQP, STOMP, MQTT and WebSocket "to reduce the exposed attack surface", although ActiveMQ's AMQP documentation still describes AMQP as enabled by default. A broker built from a recent distribution, or from a copy of its configuration, needs those connectors switched back on for the clients that use them.
RabbitMQ's native protocol is AMQP 0-9-1, with client libraries for Java, .NET, Python, Go, JavaScript and most other languages. AMQP 1.0 became a core, always-on protocol in RabbitMQ 4.0, and the RabbitMQ team publishes AMQP 1.0 clients of its own. MQTT 3.1, 3.1.1 and 5.0, STOMP 1.0 to 1.2, STOMP and MQTT over WebSocket, and the Streams protocol come as plugins that ship with the server and are switched on with one command.
The protocols the two share are AMQP 1.0, STOMP and MQTT up to 3.1.1. AMQP 0-9-1 is not among them: ActiveMQ never implemented it, so a RabbitMQ client library cannot connect to ActiveMQ, and clients move between the brokers with a change of library unless they already use one of the shared protocols. Even then, addresses differ: RabbitMQ 4.x expects AMQP 1.0 addresses such as /queues/orders or /exchanges/events/order.created.
JMS Support in ActiveMQ and RabbitMQ
ActiveMQ is a JMS provider. It implements JMS 1.1 in full and supports XA, so a message and a database update can commit in one distributed transaction. Its JMS 2.0 support, and Jakarta Messaging 3.1 support in the 6.x line, is documented as partial. Version 6.4.0 added receiveBody(), but delivery delay, shared subscriptions and asynchronous send with a CompletionListener still throw UnsupportedOperationException when called, so code that compiles still fails when it runs. ActiveMQ targets asynchronous send for 6.5.0 and the other two for 7.0.0. Our ActiveMQ vs Reliable Queue comparison lists the ActiveMQ-specific features that JMS code tends to depend on as well.
Open-source RabbitMQ supports JMS through a client library, helped by a bundled broker plugin for selectors on topics; JMS in the broker itself comes with the commercial edition. Java code reaches RabbitMQ through one of three routes:
- The open-source RabbitMQ JMS Client translates JMS into AMQP 0-9-1. Its 2.x line implements JMS 2.0 from version 2.7.0, and its 3.x line JMS 3.0, the
jakarta.jmsnamespace. Shared subscriptions and asynchronous send work, and selectors on topics need the RabbitMQ JMS Topic Exchange plugin, which ships with the broker. The client's documentation lists the gaps: no selectors on queues, a queue browser that takes the messages it shows away from other consumers until it closes,NoLocalignored, client ID uniqueness enforced only within one JVM, no XA, and local transactions that "do not guarantee atomicity and can result in partial completion". - Tanzu RabbitMQ, the commercial edition, implements JMS in the broker through closed-source plug-ins: a JMS queue type in 4.3, then in 4.4 JMS topics with durable and shared subscriptions, temporary topics,
noLocaland client IDs unique across the cluster. Clients connect over AMQP 1.0 through RabbitMQ's open-source fork of Apache Qpid JMS and get selectors evaluated on the broker, browsing that leaves messages in place, and delivery delay, which non-durable subscriptions and temporary queues ignore. Transactions are not supported, local or XA. - Amazon MQ for RabbitMQ documents JMS 1.1, 2.0 and 3.1 workloads through the open-source client, without XA, selectors on queues,
NoLocalorJMSProducer.setDeliveryDelay.
Spring Boot's auto-configured @JmsListener container uses transacted sessions unless a JTA transaction manager is configured, so a listener relies on local transactions without containing any transaction code. On Tanzu RabbitMQ, which supports none, set sessionTransacted to false on the listener container factory; on the open-source client, the listener inherits local transactions that do not guarantee atomicity.
RabbitMQ alternatives compares these routes with other JMS providers in one table.
JMS Delivery Delay Works on Neither Open-Source Broker
On ActiveMQ Classic 6.4.0, setDeliveryDelay() throws, as described above. The open-source RabbitMQ JMS Client implements it by declaring an exchange of type x-delayed-message, which we confirmed in its 3.9.1 jar, and only the delayed-message plugin provides that exchange type. The RabbitMQ team no longer maintains the plugin and archived it in April 2026; it is built on Mnesia, which RabbitMQ 4.3 removed, so on 4.3 and 4.4 the client has no exchange type to declare. Amazon MQ lists the call as unsupported. Both brokers can still delay a message by their own means, which the section on delays covers; only the JMS 2.0 call fails. Of the options on this page, JMS delivery delay works on Apache Artemis, on Tanzu RabbitMQ's JMS queues and durable topic subscriptions, and on Redisson PRO.
Code moves between JMS providers without a rewrite only when the new provider implements the calls it makes. ActiveMQ runs JMS 1.1 code as written; moving the same code to RabbitMQ means accepting the open-source client's gaps, paying for Tanzu, or rewriting it against RabbitMQ's own API, as moving between them describes.
Durability, Replication and Failover
ActiveMQ makes a persistent message durable before the producer moves on. Outside a transaction, the client sends a persistent message synchronously and waits for the broker's reply, and KahaDB, the default store, syncs its journal to disk on every write unless you configure a weaker policy. The message then lives on one broker's disk. For failover, a primary and a standby share that storage, a shared file system or a database, and whichever holds the lock serves clients, which reconnect through the failover: transport. The shared storage is therefore the part that has to be highly available. To scale out, brokers join a network and forward messages towards their consumers, which moves messages between brokers without copying them. ActiveMQ's one shared-nothing option, the replicated LevelDB store, was removed in 5.17.
RabbitMQ replicates each queue on its own. A quorum queue is a Raft group with three members by default, placed on different nodes; it confirms a published message once a majority of members have written it, and it writes every message to disk whatever its delivery mode. In RabbitMQ's own words, a confirmed message "should not be lost as long as at least a majority of RabbitMQ nodes hosting the quorum queue are not permanently made unavailable". Streams also confirm once a majority of replicas have a message, but they leave flushing to disk to the operating system, so RabbitMQ suggests quorum queues when more data safety is required. Classic queues live on one node, because RabbitMQ 4.0 removed classic queue mirroring, the replication mechanism that many older guides still describe.
The guarantee covers confirmed messages only. Publisher confirms are opt-in: a producer that never calls confirmSelect() does not learn whether the broker stored anything, and RabbitMQ promises nothing for unconfirmed messages. Quorum queues also favour consistency over availability, so a queue that loses a majority of its members stops serving until they return. Cluster metadata moved to Raft as well: Khepri became the default store for new clusters in 4.2, and 4.3 removed Mnesia along with the old partition-handling modes such as pause_minority. Between data centres, RabbitMQ links clusters with federation or shovels, and ActiveMQ with networks of brokers.
Acknowledgements, Redelivery and Dead Letters
Both brokers retry a message whose processing fails. They differ in where the rule lives, how long they wait and what happens at the end. The table compares the defaults for an ActiveMQ queue and a RabbitMQ 4.4 quorum queue, the type RabbitMQ recommends for messages that must not be lost.
| ActiveMQ Classic 6.4 | RabbitMQ 4.4 quorum queue | |
|---|---|---|
| Where the retry rule lives | In the client: a redelivery policy on the connection factory. A broker-side redelivery plugin, available since 5.7, can take over | In the broker: the queue's delivery limit, set by queue argument or policy |
| Attempts | 7 deliveries: the first plus 6 redeliveries | A delivery limit of 20, counted in failed deliveries; the 21st failure ends the retries |
| What counts as a failure | A rollback, a transacted session closed before commit, recover() under CLIENT_ACKNOWLEDGE, or a consumer that dies or times out holding the message | Since 4.3: basic.reject with requeue, the AMQP 1.0 modified outcome with delivery-failed set, or a dropped connection. basic.nack, the released outcome and consumer timeouts do not count |
| Wait between attempts | 1 second, fixed; exponential backoff is opt-in. Unless nonBlockingRedelivery is set, the consumer takes no other message while it waits | None by default. Delayed retry, since 4.3, holds the message back with a linear back-off while the rest of the queue keeps flowing |
| After the last attempt | Moved to ActiveMQ.DLQ, one queue shared by every destination unless you configure a DLQ per destination | Republished to the queue's dead-letter exchange, or dropped if the queue has none |
| Dead-lettering guarantee | A move inside the broker's own store | At most once by default; at least once with the dead-letter-strategy policy set to at-least-once and overflow to reject-publish |
| Non-persistent messages | Discarded after the last attempt, not dead-lettered | Not applicable: quorum queues persist every message |
| Expired messages | Dead-lettered if persistent | Dead-lettered if the queue has a dead-letter exchange |
| Prefetch | 1,000 messages per queue consumer | Set by the consumer with basic.qos; 0 means no limit, which quorum queues cap at 2,000 |
| A consumer that hangs | Keeps its prefetched messages until it disconnects | Its messages return to the queue after 30 minutes by default, and the consumer is cancelled. Timeouts do not count toward the limit, so a message that hangs every consumer keeps coming back |
Two differences are traps when moving between the brokers. On RabbitMQ 4.3 and later, a worker ported from ActiveMQ that signals failure with basic.nack and requeue never reaches the delivery limit, and neither does a message that stalls its consumer until the timeout, so a poison message cycles until someone notices. Use basic.reject with requeue, or the AMQP 1.0 modified outcome with delivery-failed set, to have failures counted. And a RabbitMQ quorum queue without a dead-letter exchange deletes a message at the limit, where ActiveMQ keeps a persistent message in ActiveMQ.DLQ. Our dead letter queue guide covers what to do with the messages once they land, and the ActiveMQ comparison walks through a poison message on a 6.4.0 broker.
Delayed Delivery, Scheduling and Priority
ActiveMQ delays messages with its own scheduler, not with JMS. Set the AMQ_SCHEDULED_DELAY property and the broker holds the message for that many milliseconds; AMQ_SCHEDULED_PERIOD, AMQ_SCHEDULED_REPEAT and AMQ_SCHEDULED_CRON repeat it on an interval or a cron expression. The scheduler stays off until the broker sets schedulerSupport="true", and while it is off the properties are ignored without an error: on a 6.4.0 broker, a message with a three-second delay arrived after 33 ms with the scheduler off and after 3.3 seconds with it on.
RabbitMQ gained native delays in 4.4. A message published to a quorum queue with an x-opt-delivery-time header, a Unix time in milliseconds, is replicated and confirmed at once and handed to consumers when that time arrives; AMQP 1.0 clients set it as a message annotation. No plugin is involved, and delayed messages count toward the queue's length limit. Classic queues do not support it, and there is no repeat or cron option, so recurring jobs need an application scheduler. Before 4.4, a delay meant the delayed-message exchange plugin, now archived, or a holding queue whose message TTL dead-letters into the real one. Quorum queues have also held back retries since 4.3, and 4.4 lets AMQP 1.0 consumers defer a message until a prerequisite is ready.
Priority is off by default in ActiveMQ. A destination policy has to set prioritizedMessages="true", and KahaDB then sorts messages into three bands, below 4, 4 and above 4, rather than the ten levels JMS defines; the JDBC store keeps all ten. The client has a say too: unless the connection factory sets messagePrioritySupported, each consumer's prefetch buffer hands messages over in arrival order. In RabbitMQ, a classic queue declared with x-max-priority supports up to 255 levels, with two to four recommended, and since 4.3 quorum queues deliver 32 levels, 0 to 31, in strict order with nothing to declare, treating a message without a priority as 4. From 4.0 to 4.2 they had two.
The Same Work Queue in Java
The two samples below do the same job. They publish an order with a 30-second delay and a priority above the default, then process orders, retrying failures with a growing delay and dead-lettering whatever keeps failing. The comments mark the defaults that differ. The ActiveMQ version uses JMS 2.0 on ActiveMQ 6.4.0:
import jakarta.jms.JMSConsumer;
import jakarta.jms.JMSContext;
import jakarta.jms.Queue;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.RedeliveryPolicy;
import org.apache.activemq.ScheduledMessage;
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory(
"failover:(tcp://mq-a:61616,tcp://mq-b:61616)");
// Retries are a client-side policy. Without these lines: six redeliveries,
// one second apart, then the broker moves the message to ActiveMQ.DLQ
RedeliveryPolicy retry = cf.getRedeliveryPolicy();
retry.setMaximumRedeliveries(4);
retry.setUseExponentialBackOff(true);
retry.setMaximumRedeliveryDelay(60_000);
// Otherwise the consumer takes no other message while a failed one waits
// out its delay. The price is ordering
cf.setNonBlockingRedelivery(true);
// Otherwise the prefetch buffer hands messages over in arrival order,
// whatever their priority
cf.setMessagePrioritySupported(true);
try (JMSContext ctx = cf.createContext(JMSContext.SESSION_TRANSACTED)) {
Queue orders = ctx.createQueue("orders");
ctx.createProducer()
// setDeliveryDelay() throws in 6.4.0. This property works only
// on a broker running with schedulerSupport="true"
.setProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, 30_000L)
// Also needs prioritizedMessages="true" in a broker policy entry
.setPriority(8)
.send(orders, "order-1042");
ctx.commit();
JMSConsumer worker = ctx.createConsumer(orders);
while (true) {
String order = worker.receiveBody(String.class);
try {
process(order);
ctx.commit();
} catch (RuntimeException e) {
// A rollback is a failed delivery under the policy above
ctx.rollback();
}
}
}
The RabbitMQ version uses the AMQP 0-9-1 Java client, 5.37.1, against a 4.4 broker. Here the retry rules belong to the queue, so the code declares them:
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Map;
ConnectionFactory cf = new ConnectionFactory();
cf.setHost("rabbit");
Connection connection = cf.newConnection();
Channel channel = connection.createChannel();
// Where messages go after their last attempt. Without a dead-letter
// exchange, a quorum queue drops them
channel.exchangeDeclare("orders.dlx", "fanout", true);
channel.queueDeclare("orders.dead", true, false, false,
Map.of("x-queue-type", "quorum"));
channel.queueBind("orders.dead", "orders.dlx", "");
channel.queueDeclare("orders", true, false, false, Map.of(
"x-queue-type", "quorum",
// Four failed deliveries are requeued and the fifth is dead-lettered,
// as in the ActiveMQ sample. The default limit is 20
"x-delivery-limit", 4,
"x-dead-letter-exchange", "orders.dlx",
// 4.3 and later: hold a failed message back for 10 s, then 20, 30
// and 40 s, never more than 60 s, while other messages keep flowing
"x-delayed-retry-type", "failed",
"x-delayed-retry-min", 10_000,
"x-delayed-retry-max", 60_000));
// Publisher confirms are opt-in. A quorum queue confirms once a majority
// of its replicas has the message
channel.confirmSelect();
long in30s = Instant.now().plusSeconds(30).toEpochMilli();
AMQP.BasicProperties props = new AMQP.BasicProperties.Builder()
// Quorum queues order 32 levels, 0 to 31, and treat a message
// without a priority as 4
.priority(8)
// 4.4 and later: a native delay, epoch milliseconds as a long
.headers(Map.of("x-opt-delivery-time", in30s))
.build();
channel.basicPublish("", "orders", props,
"order-1042".getBytes(StandardCharsets.UTF_8));
channel.waitForConfirmsOrDie(5_000);
// At most 10 unacknowledged deliveries; a prefetch of 0 means no limit
channel.basicQos(10);
DeliverCallback onOrder = (consumerTag, delivery) -> {
long tag = delivery.getEnvelope().getDeliveryTag();
try {
process(new String(delivery.getBody(), StandardCharsets.UTF_8));
channel.basicAck(tag, false);
} catch (RuntimeException e) {
// basicReject counts toward the delivery limit. Since 4.3,
// basicNack does not, so a poison message would cycle forever
channel.basicReject(tag, true);
}
};
// The broker cancels a consumer that holds a delivery past the consumer
// timeout, 30 minutes by default, and this worker then receives nothing
channel.basicConsume("orders", false, onOrder, consumerTag ->
System.err.println("orders consumer cancelled: " + consumerTag));
The backoff differs in shape as well as in placement: ActiveMQ's multiplies the delay, 1, 5 and 25 seconds and then the 60-second cap, while RabbitMQ's grows linearly, here 10, 20, 30 and 40 seconds. Dead-lettering in the RabbitMQ sample is at most once; for at least once, add the policy keys from the table above.
Replay, Streams and Where Kafka Fits
ActiveMQ has no log to replay. A queue message is deleted once a consumer acknowledges it, and a durable subscription keeps a message only until that subscriber acknowledges it. The nearest feature is the retroactive consumer: when a topic subscriber starts, it receives the recent messages that the topic's subscription recovery policy keeps in a bounded in-memory buffer.
RabbitMQ has offered replay since 3.9, released in 2021, through streams: replicated, append-only logs that consumers read without removing messages. A consumer attaches at the first or last message, at an offset or at a timestamp, and retention is set by size or age. Producers can avoid duplicates by name and sequence number, super streams partition a stream to scale it, and AMQP 1.0 consumers can filter on the broker with property or SQL expressions. RabbitMQ's own comparison page sums up the convergence: "RabbitMQ has had a replicated log since 2021, and Kafka gained queue semantics in 2026."
So ActiveMQ is not like Kafka, and RabbitMQ partly is. Kafka still goes further on long retention, partitioned consumer groups and stream processing, and since Kafka 4.2 its share groups also let consumers acquire and acknowledge records individually, with dead-letter queues since 4.4. If event history is the main requirement, compare RabbitMQ streams with Kafka directly; Redis vs Kafka and Apache Kafka alternatives cover that side. Amazon MQ does not support streams. For Java services whose history fits in memory, Redisson PRO's Reliable PubSub topics can be rewound too, through Redisson's own API rather than JMS, as described below.
Operations, Security and Support
ActiveMQ runs on a JVM, Java 17 or later for 6.x, and is configured in activemq.xml, a Spring XML file, plus Jetty files for the web console. RabbitMQ runs on the Erlang runtime and is configured in rabbitmq.conf, while much of a queue's behaviour can be set and changed at runtime through policies. Both have Docker images. For Kubernetes, the RabbitMQ team maintains two open-source operators, one for clusters and one for queues, exchanges, users and policies; ActiveMQ Classic has no official operator or Helm chart.
ActiveMQ exposes queue depths, consumer counts and broker state through JMX, the web console and Jolokia, and publishes broker events to advisory topics. RabbitMQ's management plugin, bundled but switched off by default, adds a web UI on port 15672 and an HTTP API, and the Prometheus plugin serves metrics on port 15692.
Out of the box, ActiveMQ 6.4.0 binds the web console to the loopback interface, tightens the default Jolokia access policy and enables only OpenWire, but its sample configuration still leaves client authentication off "so the sample broker starts out-of-the-box". RabbitMQ's default guest user can connect only from localhost. Both projects ship security fixes often. ActiveMQ Classic has published 25 advisories with 2026 CVE numbers, 17 rated important, among them CVE-2026-34197, a code-execution flaw that an authenticated user could reach through Jolokia, which CISA added to its Known Exploited Vulnerabilities catalogue in April 2026. RabbitMQ has published at least 68 advisories on GitHub since May 2026, among them at least two rated critical and 13 high. The projects count and rate advisories differently, so the totals do not compare directly. With either broker, run a version that still receives security fixes.
Support Windows and Licences
ActiveMQ is an Apache Software Foundation project under the Apache License 2.0, and every release is free. It currently maintains three lines, 6.4, 6.3 and 5.19, which all had releases on October 27, 2026: 6.4.0, 6.3.3 and 5.19.12. Older lines, 6.2 and 5.18 among them, get no more fixes, and the project announces no end dates in advance. Lines vary in length: 5.19, the line for code still on the javax.jms namespace, has had releases since March 2025, while 6.2 lasted less than a year, from November 2025 to September 2026. Commercial support comes from third parties such as OpenLogic by Perforce and meshIQ. Moving from 5.x to 6.x is a project of its own, because 6.x uses the jakarta.jms namespace and needs Java 17.
The RabbitMQ server is open source under the Mozilla Public License 2.0, and Broadcom sells Tanzu RabbitMQ, which the RabbitMQ team describes as "layering enterprise-grade, closed-source plugins over the open-source core". The free edition gets fixes only while a release series is in community support, about nine months per series: until July 31, 2027 for 4.4 and January 31, 2027 for 4.3, according to rabbitmq.com. Later patches for a series go to paying customers only; 4.2.10 and later were not released publicly. Rolling upgrades follow published paths: a cluster reaches 4.4 from 4.2 or 4.3, and older clusters go through 4.2 first, while a blue-green migration to a new cluster avoids the constraint.
Without a support contract, RabbitMQ users move to a new series about twice a year, on dates published in advance. ActiveMQ's 5.19 line has had releases for more than a year and a half, longer than a RabbitMQ series now gets free fixes, but ActiveMQ gives no notice of when a line will end.
Which Is Faster?
We found no recent, independent benchmark of ActiveMQ Classic against RabbitMQ 4.x, and the throughput tables on comparison pages rarely say how they were measured. The most recent public test we found compares RabbitMQ with Artemis instead. A December 2025 OpenMessaging Benchmark run of RabbitMQ 4.2.2 against Artemis 2.44.0, one broker at a time on a single machine with classic queues, reported "no clear winner": Artemis reached about 25% more throughput with non-durable messages, while RabbitMQ confirmed publishes in under a millisecond and used less memory.
ActiveMQ sends each persistent message outside a transaction synchronously, and KahaDB syncs its journal on every write by default, so a producer sending one message at a time waits for a broker round trip and a disk sync; transactions and asynchronous sends spread that cost over many messages. A quorum queue confirms after a majority of replicas have written a message, which adds network round trips, but a producer can keep many confirms outstanding. Message size, persistence, acknowledgement mode, prefetch and replication move the result more than the choice of broker, so measure your own workload on the versions you would run.
Moving Between ActiveMQ and RabbitMQ
From ActiveMQ to RabbitMQ, start with the client API, because it sets the size of the job. Code written to JMS has three options: run it on the open-source RabbitMQ JMS Client within the gaps listed above, run it on Tanzu RabbitMQ's commercial JMS queues and topics, or rewrite it against RabbitMQ's own API. In Spring, that rewrite replaces JmsTemplate and @JmsListener with Spring AMQP's RabbitTemplate and @RabbitListener. Non-Java clients on OpenWire, through NMS or CMS, are rewritten either way, while STOMP, MQTT and AMQP 1.0 clients can usually keep their library and change addresses.
Then rebuild what the broker did implicitly. Map each destination with the table above. Replace redelivery policies with delivery limits, delayed retry and a dead-letter exchange per queue, remembering that on 4.3 and later rejects count toward the limit and nacks do not. Move AMQ_SCHEDULED_DELAY to x-opt-delivery-time on quorum queues, and repeating or cron schedules to an application scheduler. Decide where selectors go, since queue consumers on open-source RabbitMQ cannot filter. Messages stored in KahaDB do not move: drain queues before cutting over, or bridge them. A RabbitMQ shovel accepts an AMQP 1.0 source, so once ActiveMQ's AMQP connector is enabled, a shovel can consume from an ActiveMQ queue and republish into RabbitMQ while producers move over. Both brokers listen for AMQP on port 5672 by default, so on a shared host one of them has to move. Test how message properties and body types survive the conversion before relying on it.
The other direction is rarer. RabbitMQ's AMQP 0-9-1 clients cannot connect to ActiveMQ, so producers and consumers move to JMS, or to AMQP 1.0, STOMP or MQTT clients. Exchanges and bindings turn into composite destinations, virtual topics and selectors, and streams have no ActiveMQ counterpart. In both directions, run the brokers side by side for a while and make consumers idempotent, because a bridge that retries can deliver a message twice.
A Third Option for JVM Estates: JMS on Valkey or Redis
Some teams comparing ActiveMQ and RabbitMQ would rather run neither broker: every service is written in Java, and they already operate Valkey or Redis. For them, Redisson PRO, the commercial edition of our Java client, includes a JMS provider that implements JMS 2.0 and Jakarta Messaging 3.0 and 3.1 and, according to Redisson's documentation, passes all TCK tests. Queues are stored as Reliable Queues and topics as Reliable PubSub topics in Valkey or Redis, with no broker process at all. Delivery delay and shared durable subscriptions, which fail on ActiveMQ Classic 6.4.0, work here; priority runs from 0 to 9 with nothing to enable; and a subscription can be rewound to a timestamp or message ID through Redisson's own API. In Spring Boot, JmsTemplate and @JmsListener code stays as it is, and the connection factory carries the queue settings:
import jakarta.jms.ConnectionFactory;
import jakarta.jms.JMSContext;
import java.time.Duration;
import org.redisson.api.RedissonClient;
import org.redisson.api.queue.QueueConfig;
import org.redisson.jms.RedissonConnectionFactory;
import org.springframework.context.annotation.Bean;
@Bean
RedissonConnectionFactory connectionFactory(RedissonClient redisson) {
RedissonConnectionFactory cf = new RedissonConnectionFactory(redisson);
cf.setQueueConfig("orders", QueueConfig.defaults()
// Five delivery attempts, then the dead-letter queue. Without
// a name, a message that runs out of attempts is deleted
.deliveryLimit(5)
.deadLetterQueueName("orders.dead")
// Unacknowledged after two minutes counts as a failed attempt
.visibility(Duration.ofMinutes(2)));
return cf;
}
// Plain JMS 2.0, including the delay that ActiveMQ Classic 6.4.0 rejects
void remindLater(ConnectionFactory connectionFactory) {
try (JMSContext ctx = connectionFactory.createContext()) {
ctx.createProducer()
.setDeliveryDelay(Duration.ofSeconds(30).toMillis())
.setPriority(8)
.send(ctx.createQueue("orders"), "order-1042");
}
}
It fits a narrower set of estates than either broker:
- It is a JVM library. Clients in other languages, and anything that speaks AMQP, MQTT or STOMP, still need a broker.
- The backlog lives in memory. Size Valkey or Redis for the deepest backlog you expect, bound queues with
maxSize, and run messaging with thenoevictioneviction policy. - Durability comes from Valkey or Redis. Enable AOF persistence. The default
syncMode,AUTO, waits for each write to reach the AOF on the primary and replicas when AOF is on, or for the replicas otherwise, but a write that misses the sync timeout only logs a warning unlesssyncFailureModeisTHROW_EXCEPTION. Replication is asynchronous, so on AWS use MemoryDB, or ElastiCache for Valkey with synchronous durability. - Redisson's documentation does not describe XA support for JMS or the semantics of transacted sessions. Plan without XA, and test transacted sessions before relying on them, remembering that Spring Boot's listener containers use them by default.
- Redisson PRO is commercial. The licence includes 24x7 support with an SLA for the client, and a free trial runs against your own servers.
ActiveMQ vs Reliable Queue and Reliable PubSub compares it with ActiveMQ feature by feature, and Redis vs RabbitMQ does the same from the RabbitMQ side.
How to Choose
Read the rows in order and stop at the first one that fits. If a later row also fits, check your pick against it too.
| Situation | Start with |
|---|---|
| Your JMS code calls delivery delay | Apache Artemis; Tanzu RabbitMQ's JMS queues and durable topic subscriptions, without transactions; or Redisson PRO if every client is on the JVM and Valkey or Redis is already in production. Spring Boot listeners use transacted sessions by default, as noted above. ActiveMQ Classic 6.4 throws, and the open-source RabbitMQ JMS Client relies on a plugin that RabbitMQ 4.3 and later cannot run |
| Your JMS code uses shared subscriptions or asynchronous send | Apache Artemis; RabbitMQ through the open-source JMS Client, on Amazon MQ too, within the gaps listed above; or Redisson PRO on the terms in the row above. ActiveMQ Classic 6.4 throws on both |
| A new or migrating estate with producers and consumers in several languages, or routing by key, pattern or header | RabbitMQ, provided any JMS code can do without queue selectors and NoLocal. On Amazon MQ, the RabbitMQ engine accepts AMQP clients only, so STOMP and MQTT clients there need the ActiveMQ engine |
| Queues replicated without shared storage | RabbitMQ quorum queues, or Artemis replication if you want to keep JMS |
| Consumers need to re-read past messages | RabbitMQ streams, which Amazon MQ does not offer; Kafka for long retention or stream processing; Redisson PRO's Reliable PubSub, through its own API, if every reader is on the JVM and the history fits in memory |
| You are on AWS and want the broker run for you | Amazon MQ: the RabbitMQ engine for AMQP clients and for JMS 2.0 or 3.x code without delivery delay, queue selectors or XA; the ActiveMQ engine for JMS 1.1 code, XA and queue selectors included, and for MQTT or STOMP clients. For JMS code that needs delivery delay, with every client on the JVM, Redisson PRO on MemoryDB, or on ElastiCache for Valkey with synchronous durability. |
| Every client is on the JVM, Valkey or Redis is already in production, the backlog fits in memory, and you would rather not run a broker | Redisson PRO's Reliable Queues, Reliable PubSub or JMS provider. |
ActiveMQ vs RabbitMQ: Frequently Asked Questions
Does ActiveMQ Use AMQP?
Yes, AMQP 1.0, which ActiveMQ Classic has supported since version 5.8, alongside OpenWire, STOMP and MQTT. It does not support AMQP 0-9-1, the protocol most RabbitMQ applications use, so RabbitMQ client libraries cannot connect to ActiveMQ. Since versions 6.2.6 and 5.19.7, the configuration shipped with ActiveMQ enables only OpenWire, so the AMQP connector has to be switched on in activemq.xml before AMQP 1.0 clients can connect. RabbitMQ has supported AMQP 1.0 natively since 4.0, which makes 1.0 the one AMQP version both brokers speak.
Can JMS Code Move From ActiveMQ to RabbitMQ?
Partly. The open-source RabbitMQ JMS Client runs JMS 2.0 and 3.0 code over AMQP 0-9-1, shared subscriptions and asynchronous send included, but it supports neither selectors on queues nor XA, ignores NoLocal, and its local transactions do not guarantee atomicity. Its delivery delay depends on a plugin that RabbitMQ 4.3 and later cannot run. Tanzu RabbitMQ, the commercial edition, adds broker-side JMS queues and topics with selectors, and delivery delay on queues and durable subscriptions, but no transactions. ActiveMQ-specific features such as virtual topics, scheduled delivery and redelivery policies have to be rebuilt either way. Code that needs XA fits Apache Artemis, or stays on ActiveMQ Classic.
Is ActiveMQ or RabbitMQ Like Kafka?
ActiveMQ is not: it deletes messages once they are acknowledged and keeps no log to replay them from. RabbitMQ is partly like Kafka through streams, replicated append-only logs, available since 2021, that consumers read from an offset or a timestamp without removing messages. Kafka still goes further on long retention, partitioned scale-out and stream processing, and since Kafka 4.2 it also offers queue-style consumption through share groups. For work queues with per-message acknowledgement, retries and dead letters, both brokers remain the simpler choice.
What Is the AWS Equivalent of RabbitMQ and ActiveMQ?
Amazon MQ, which runs both as a managed service: RabbitMQ 4.3, 4.2 and 3.13, and ActiveMQ 5.19 and 5.18. A RabbitMQ broker is a single instance or a three-node cluster across three Availability Zones, and an ActiveMQ broker a single instance or an active/standby pair across two. Amazon MQ does not support ActiveMQ 6.x, and its RabbitMQ engine accepts AMQP clients only, with no MQTT, STOMP or streams. AWS's serverless services, SQS and SNS, are the other option, but they need code changes, since SQS supports JMS only for queues, at JMS 1.1.
Which Is Easier to Run, ActiveMQ or RabbitMQ?
A single broker of either kind is quick to start: ActiveMQ needs a JVM and its activemq.xml, RabbitMQ the Erlang runtime and a short configuration file, and both have Docker images. The difference shows in high availability and upgrades. ActiveMQ fails over through shared storage that must itself be highly available, while RabbitMQ replicates quorum queues across a cluster and has an official Kubernetes operator. To keep free fixes, RabbitMQ expects a move to a new release series about twice a year; ActiveMQ patches three lines at once but publishes no end dates for them.
How Does Apache Artemis Compare With RabbitMQ?
Artemis, the broker that left the ActiveMQ project in November 2025, closes most of ActiveMQ Classic's gaps: it implements JMS 2.0 and Jakarta Messaging 3.1 completely, supports XA, replicates without shared storage and supports MQTT 5. RabbitMQ keeps the advantages that come from AMQP 0-9-1, client libraries in most languages and routing through exchanges and bindings, plus replay through streams. For Java estates built on JMS that need the complete API or XA, Artemis is the stronger candidate; for polyglot estates, RabbitMQ. A December 2025 benchmark of the two on one machine found no clear winner.
Can ActiveMQ and RabbitMQ Run Side by Side During a Migration?
Yes. The usual approach is to run both, move consumers and producers in stages, and bridge the queues that both sides still use. A RabbitMQ shovel can consume from an AMQP 1.0 source, so with ActiveMQ's AMQP connector enabled it can move messages from ActiveMQ queues into RabbitMQ, and integration tools such as Apache Camel can bridge the two in either direction. Make consumers idempotent while both paths are live, because a bridge that retries can deliver a message twice.
Similar articles
- ActiveMQ Alternatives: Seven Options and What Happens to Your JMS Code
- RabbitMQ Alternatives: Seven Options, and What Each One Breaks
- ActiveMQ vs Valkey & Redis Based Reliable Queue and PubSub
- Redis vs. RabbitMQ: Choosing the Right Message Broker
- IBM MQ Alternatives: Eight Options, and What Moves Without a Rewrite
- Apache Kafka Alternatives: 7 Options Compared
- JMS Messaging Over Valkey and Redis
- How to Replace RabbitMQ With Valkey & Redis in Spring Cloud Stream Framework