Apache Kafka vs RabbitMQ for Messaging in Java (and Where ActiveMQ Fits In)

If you’re standing in front of a whiteboard in Java land and someone has just drawn a box labelled “message queue,” you’re probably going to argue about Apache Kafka and RabbitMQ for the next forty minutes. They’ve become the default two-horse race for back-end messaging, and the choice between them isn’t really about speed or popularity — it’s about whether you want a queue or a log. Get that distinction right and the rest of the decision falls out cleanly. ☕

The One-Sentence Difference

RabbitMQ is a traditional message broker: producers push messages into queues, consumers pop them off, and once a message is acknowledged, it’s gone. Apache Kafka is a distributed commit log: producers append records to partitioned topics, consumers read at their own pace by tracking offsets, and records hang around for a configured retention period — usually days — whether anyone has read them or not. 💡

Everything else flows from that. The protocols differ, the durability model differs, the scaling model differs, the operational pain points differ. But the shape of the abstraction is the thing.

RabbitMQ in 90 Seconds

RabbitMQ implements the Advanced Message Queuing Protocol (AMQP) 0-9-1 by default, with plugins for Message Queuing Telemetry Transport (MQTT), Streaming Text Oriented Messaging Protocol (STOMP), and a Hypertext Transfer Protocol (HTTP) API. It’s written in Erlang, which is part of the secret to its reliability — Erlang’s process model is excellent at the kind of supervised, fault-tolerant work a broker has to do.

The mental model is exchanges, bindings, and queues. A producer publishes to an exchange; the exchange routes the message to zero or more queues based on bindings; consumers subscribe to queues. Routing can be direct (exact key match), topic (pattern match), fanout (broadcast), or headers-based. Once a queue holds a message and a consumer acknowledges it, the broker deletes it. 🐰

A minimal Spring Boot consumer:

1
2
3
4
5
6
7
8
@Component
public class OrderListener {

    @RabbitListener(queues = "orders.created")
    public void onOrderCreated(OrderCreatedEvent event) {
        // ...handle the order
    }
}

You add spring-boot-starter-amqp, declare your queue and exchange as @Beans, and you’re done. The library handles connections, channels, retries, and message conversion via Jackson.

Apache Kafka in 90 Seconds

Kafka is not a broker in the RabbitMQ sense. It’s a partitioned, replicated, append-only log running across a cluster of brokers. Producers write records to topics; each topic is split into partitions; each partition is an ordered, immutable sequence of records stored on disk and replicated to a configurable number of brokers. Consumers read by maintaining their own position (an offset) into each partition.

The model has two consequences that surprise people coming from RabbitMQ:

  • Reading a record doesn’t delete it. The next consumer (or the same one, restarted) can read it again by rewinding the offset. Retention is time-based or size-based.
  • Ordering is per-partition, not per-topic. If you need strict ordering across a logical key (“all events for user 42 in order”), you partition on that key.

A Spring Boot consumer:

1
2
3
4
5
6
7
8
@Component
public class OrderEventListener {

    @KafkaListener(topics = "orders", groupId = "order-processor")
    public void onOrderEvent(ConsumerRecord<String, OrderEvent> record) {
        // ...process the event
    }
}

Same shape, totally different machine underneath.

So… Is ActiveMQ the New Kafka?

Short answer: no. ActiveMQ is the new RabbitMQ — or rather, it has always been RabbitMQ’s older Java-ecosystem sibling. Apache ActiveMQ is a Java Message Service (JMS)-first broker that has been around since 2004. The modern version, ActiveMQ Artemis (formerly HornetQ, donated by Red Hat to the Apache Software Foundation), is a ground-up rewrite that delivers genuinely high throughput and a much better internal architecture than the original ActiveMQ “Classic.”

Artemis is fast, supports AMQP, STOMP, MQTT, OpenWire, and JMS, and is a perfectly reasonable broker. But it’s still a broker. It deletes messages on acknowledgement, it tracks consumer state inside the broker, and it doesn’t have Kafka’s “the log is the truth, rewind whenever” model.

The reason people conflate ActiveMQ and Kafka is that Artemis has gotten fast enough that the old throughput argument doesn’t apply — “use Kafka because ActiveMQ can’t keep up” was true in 2014, not in 2022. But the architectural difference is unchanged: Kafka is a log; ActiveMQ is a queue. Pick based on the abstraction, not the throughput number. 📊

Side-by-Side Comparison

Aspect RabbitMQ Apache Kafka ActiveMQ Artemis
Core abstraction Broker (queue) Distributed log Broker (queue)
Implementation language Erlang Scala / Java Java
Wire protocols AMQP 0-9-1, MQTT, STOMP Custom binary (Kafka protocol) AMQP, MQTT, STOMP, OpenWire, JMS
Message lifetime Deleted on ack Retained for configured period (e.g. 7 days) Deleted on ack
Consumer state Tracked in broker Tracked by consumer (offset) Tracked in broker
Replay Not natively; needs a dead-letter dance First-class — just reset the offset Not natively
Ordering guarantee Per queue Per partition Per queue
Throughput ceiling Tens of thousands msg/s per node Hundreds of thousands to millions msg/s per cluster Tens of thousands msg/s per node
Latency Sub-millisecond achievable Few-millisecond typical (batch-oriented) Sub-millisecond achievable
Routing model Rich (exchanges, bindings, headers) Topic + partition key, no broker-side routing Rich (similar to RabbitMQ)
Transactions Yes (broker) Yes (across topics, idempotent producer) Yes (JMS XA)
Stream processing External (e.g. Spring Cloud Stream) First-class (Kafka Streams, ksqlDB) External
Cluster coordinator Built-in (Erlang clustering / Khepri or Mnesia) ZooKeeper, or KRaft on newer versions Built-in
Management UI Web UI ships in-box None official; tools like AKHQ, Conduktor Web console ships in-box
Spring starter spring-boot-starter-amqp spring-kafka spring-boot-starter-artemis
Best fit Task queues, RPC, complex routing Event streaming, audit logs, change-data-capture JMS-heavy enterprises, AMQP polyglot

How to Actually Choose

Forget the benchmarks. Ask these three questions:

  1. Do I need to replay history? If a new consumer needs to see events from yesterday, last week, or last month, you want Kafka. If “once it’s processed, it’s gone” is your model, you want a broker.
  2. Do I need broker-side routing? If your producer publishes one logical event and seven different consumers each need a different subset based on routing keys or headers, that’s RabbitMQ’s home turf. Kafka does this differently — every consumer reads the whole topic and filters client-side, or you write a separate topic per logical subset.
  3. What does “high throughput” actually mean for you? A few thousand messages per second is comfortably in RabbitMQ / ActiveMQ territory. Tens of thousands per second sustained, with partitioned ordering and replayability, is where Kafka starts to earn its operational complexity tax. Don’t pay that tax if you don’t need it. 🐳

The Operational Footnote

Kafka is more work to run than RabbitMQ. A production Kafka cluster needs at least three brokers, careful disk planning (it’s an append-only log; throughput is bounded by sequential write speed), partition-count decisions you can’t easily change later, and consumer-group rebalancing that misbehaves in subtle ways. Managed options — Confluent Cloud, Amazon Managed Streaming for Apache Kafka (MSK), Aiven — are how most teams avoid building this muscle in-house. 🛡️

RabbitMQ clusters are simpler. Three nodes, quorum queues for the data you really can’t lose, a mirrored management UI, and you’re done. The operational ceiling is lower but so is the floor.

ActiveMQ Artemis sits in between — easier than Kafka, slightly more configuration surface area than RabbitMQ, and a natural fit if your shop is already JMS-heavy.

Closing Thoughts

The Kafka-versus-RabbitMQ debate is mostly a category error. They aren’t competing products; they’re competing abstractions. Kafka is a log you happen to read like a queue. RabbitMQ is a queue with rich routing. ActiveMQ Artemis is a queue with deep JMS integration. Pick the abstraction your problem actually has, run it on the managed service if you can, and put the saved attention into your application code. 🎉

Further Reading

  • Kafka: The Definitive Guide (Confluent)confluent.io/resources/kafka-the-definitive-guide. The standard reference; covers partitions, replication, exactly-once semantics, Kafka Streams.
  • RabbitMQ documentationrabbitmq.com/documentation.html. Especially the “Reliability Guide” and “Quorum Queues” sections.
  • ActiveMQ Artemis documentationactivemq.apache.org/components/artemis/documentation/. The architecture and clustering chapters are worth the time.
  • Spring for Apache Kafkadocs.spring.io/spring-kafka. Listener containers, transaction management, error handling.
  • Spring AMQPdocs.spring.io/spring-amqp. The RabbitMQ-side equivalent.
  • “Kafka is not a queue” — Jay Kreps’ classic LinkedIn engineering post that explains the log abstraction better than any documentation does.
This entry was posted in java and tagged , , , , , . Bookmark the permalink.

Comments are closed.