Back to guides
Comparison

RabbitMQ vs Kafka: Complete Messaging System Comparison

| |
RabbitMQ 3.13, Apache Kafka 3.7
| rabbitmq, kafka, messaging, comparison, distributed-systems

After running both RabbitMQ and Kafka in production for years, the answer is clear: they solve different problems. Choosing between them isn’t about which is “better” - it’s about understanding their fundamentally different architectures and use cases.

This is a practical comparison based on production experience, not vendor marketing.

Tested on: RabbitMQ 3.13 (November 2024), Apache Kafka 3.7 (December 2024)

Quick Decision Guide

Use RabbitMQ when:

  • You need traditional message queue patterns (work queues, pub/sub, routing)
  • Complex routing rules and message priorities matter
  • Low latency for individual messages is critical (<10ms)
  • You want simpler operational model (fewer moving parts)
  • Message TTL and dead-letter queues are requirements

Use Kafka when:

  • You need event streaming and event sourcing
  • High throughput is more important than low latency (>100k msg/s)
  • You want message replay and time-travel capabilities
  • Long-term event log retention is required (days/weeks/years)
  • You’re building event-driven microservices or data pipelines

Fundamental Architecture Differences

RabbitMQ: Message Broker

RabbitMQ Architecture (Traditional Queue):

Producer → Exchange → Queue → Consumer

         (Routing Rules)

Key Concepts:
├─ Messages are DELETED after consumption
├─ Push model: Broker pushes to consumers
├─ Queue is unit of parallelism
└─ In-memory with optional persistence

Message Lifecycle:
1. Producer sends to Exchange
2. Exchange routes to Queue(s) based on routing key
3. Consumer acknowledges (ACK)
4. Message DELETED from queue

Kafka: Distributed Commit Log

Kafka Architecture (Event Log):

Producer → Topic → Partitions (on disk) → Consumers

                   (Immutable Log)

Key Concepts:
├─ Messages are RETAINED (configurable retention)
├─ Pull model: Consumers pull from broker
├─ Partition is unit of parallelism
└─ Always persisted to disk (sequential writes)

Message Lifecycle:
1. Producer appends to partition
2. Message persists on disk
3. Consumer tracks offset
4. Message RETAINED until retention policy expires

Architecture Diagram:

┌─────────────────────────────────────────────────────────┐
│ RabbitMQ: Queue-based (Delete after consumption)       │
├─────────────────────────────────────────────────────────┤
│                                                         │
│   Producer                                              │
│      │                                                  │
│      ├──> Exchange (fanout/direct/topic/headers)       │
│      │        │                                         │
│      │        ├──> Queue A ──> Consumer A1              │
│      │        ├──> Queue B ──> Consumer B1, B2         │
│      │        └──> Queue C ──> Consumer C1              │
│      │                                                  │
│      └──> Message deleted after ACK                    │
│                                                         │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│ Kafka: Log-based (Retain until expiry)                 │
├─────────────────────────────────────────────────────────┤
│                                                         │
│   Producer                                              │
│      │                                                  │
│      └──> Topic: orders                                │
│              ├─ Partition 0: [msg1, msg2, msg3, ...]   │
│              ├─ Partition 1: [msg4, msg5, msg6, ...]   │
│              └─ Partition 2: [msg7, msg8, msg9, ...]   │
│                       ↓                                 │
│              Consumer Group A                           │
│                ├─ Consumer A1 reads P0                  │
│                ├─ Consumer A2 reads P1                  │
│                └─ Consumer A3 reads P2                  │
│                                                         │
│              Consumer Group B (reads ALL independently) │
│                ├─ Consumer B1 reads P0, P1              │
│                └─ Consumer B2 reads P2                  │
│                                                         │
│              Messages retained for 7 days (configurable)│
│                                                         │
└─────────────────────────────────────────────────────────┘

Performance Characteristics

Throughput Benchmark

Test Setup:

  • Message size: 1KB
  • Hardware: 3 brokers, 16 CPU, 64GB RAM, NVMe SSD
  • Network: 10 Gbps
SystemThroughputLatency (p50)Latency (p99)CPU UsageWinner
RabbitMQ50k msg/s5ms25ms40%🏆 Latency
Kafka500k msg/s12ms45ms20%🏆 Throughput

Summary: Kafka wins on throughput (10x higher), RabbitMQ wins on latency (2-3x lower).

Why is Kafka faster?

Kafka Optimizations:
├─ Batching: Groups messages into batches
├─ Sequential disk I/O: Append-only log (faster than random I/O)
├─ Zero-copy: sendfile() system call (no user-space buffer)
├─ Compression: Batch compression (snappy/lz4/gzip)
└─ Page cache: OS-level caching (no JVM heap pressure)

RabbitMQ Bottlenecks:
├─ Per-message routing and ACK overhead
├─ Erlang VM overhead for message copying
├─ Memory-based queues (GC pressure at high throughput)
└─ No batching by default (can enable with plugins)

Latency Comparison

Scenario: Single message end-to-end latency

RabbitMQ (push model):
├─ Best case: 2-5ms (in-memory queue, local consumer)
├─ Typical: 5-15ms (persistent queue, remote consumer)
└─ Use case: Low-latency RPC, job queues

Kafka (pull model):
├─ Best case: 5-10ms (with linger.ms=0, consumer polling)
├─ Typical: 10-50ms (with batching, linger.ms=10)
└─ Use case: High-throughput event streaming

Winner: RabbitMQ for individual message latency

Messaging Patterns

RabbitMQ Routing Patterns

1. Work Queue (Load Balancing)

┌─────────────────────────────────────────┐
│ Work Queue Pattern                      │
├─────────────────────────────────────────┤
│                                         │
│  Producer ──> Queue ──> Consumer 1      │
│                   └───> Consumer 2      │
│                   └───> Consumer 3      │
│                                         │
│  Each message delivered to ONE consumer │
│  (Round-robin by default)               │
│                                         │
└─────────────────────────────────────────┘

Java Example (RabbitMQ):

// Producer
channel.queueDeclare("work_queue", true, false, false, null);
channel.basicPublish("", "work_queue",
    MessageProperties.PERSISTENT_TEXT_PLAIN,
    "Task payload".getBytes());

// Consumer
channel.basicQos(1);  // Prefetch 1 message at a time
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
    String message = new String(delivery.getBody(), "UTF-8");
    processTask(message);
    channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
};
channel.basicConsume("work_queue", false, deliverCallback, consumerTag -> {});

2. Pub/Sub (Fanout Exchange)

┌──────────────────────────────────────────┐
│ Pub/Sub Pattern                          │
├──────────────────────────────────────────┤
│                                          │
│             Fanout Exchange              │
│                    │                     │
│        ┌───────────┼───────────┐         │
│        ↓           ↓           ↓         │
│     Queue A     Queue B     Queue C      │
│        ↓           ↓           ↓         │
│  Consumer A   Consumer B   Consumer C    │
│                                          │
│  Each consumer gets ALL messages         │
│                                          │
└──────────────────────────────────────────┘

3. Topic Routing (Pattern Matching)

┌───────────────────────────────────────────────┐
│ Topic Exchange Pattern                        │
├───────────────────────────────────────────────┤
│                                               │
│  Producer                                     │
│     ├─> "orders.created" ──┐                 │
│     ├─> "orders.shipped" ──┤                 │
│     └─> "payments.success" ─┤                │
│                              │                │
│                        Topic Exchange         │
│                              │                │
│     ┌────────────────────────┼────────┐       │
│     ↓                        ↓        ↓       │
│  "orders.*"           "orders.created" "*.success"│
│     ↓                        ↓        ↓       │
│  Queue A                 Queue B   Queue C    │
│                                               │
│  Wildcards: * (one word), # (zero or more)   │
│                                               │
└───────────────────────────────────────────────┘

Kafka Partitioning & Consumer Groups

Kafka Partitioning Model:

┌──────────────────────────────────────────────────────┐
│ Kafka Topic with Partitions                         │
├──────────────────────────────────────────────────────┤
│                                                      │
│  Topic: user-events (3 partitions)                  │
│                                                      │
│  Partition 0: [event1, event5, event9, ...]         │
│  Partition 1: [event2, event6, event10, ...]        │
│  Partition 2: [event3, event7, event11, ...]        │
│                                                      │
│  Partitioning Strategy:                             │
│  ├─ By key: hash(key) % num_partitions              │
│  ├─ Round-robin (if no key)                         │
│  └─ Custom partitioner                              │
│                                                      │
│  Same key → same partition → ordering guarantee!    │
│                                                      │
└──────────────────────────────────────────────────────┘

Java Example (Kafka):

// Producer with key-based partitioning
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

KafkaProducer<String, String> producer = new KafkaProducer<>(props);

// Messages with same userId go to same partition (ordering preserved)
String userId = "user-123";
ProducerRecord<String, String> record = new ProducerRecord<>(
    "user-events",
    userId,  // Key for partitioning
    "{\"event\": \"login\", \"userId\": \"user-123\"}"
);
producer.send(record);

// Consumer Group
Properties consumerProps = new Properties();
consumerProps.put("bootstrap.servers", "localhost:9092");
consumerProps.put("group.id", "analytics-group");  // Consumer group
consumerProps.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
consumerProps.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

KafkaConsumer<String, String> consumer = new KafkaConsumer<>(consumerProps);
consumer.subscribe(Arrays.asList("user-events"));

while (true) {
    ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
    for (ConsumerRecord<String, String> record : records) {
        System.out.printf("Partition: %d, Offset: %d, Key: %s, Value: %s%n",
            record.partition(), record.offset(), record.key(), record.value());
    }
    consumer.commitSync();  // Commit offsets
}

Delivery Guarantees

RabbitMQ Guarantees

Delivery Guarantees (configurable):

1. At-most-once (fire and forget):
   ├─ Producer: No confirmation
   ├─ Consumer: Auto-ACK
   └─ Risk: Messages can be lost

2. At-least-once (default):
   ├─ Producer: Publisher confirms
   ├─ Consumer: Manual ACK after processing
   └─ Risk: Duplicates on failure

3. Exactly-once:
   ├─ NOT supported natively
   ├─ Must implement idempotency in application
   └─ Use deduplication plugin or app-level dedup

RabbitMQ Publisher Confirms:

// Enable publisher confirms
channel.confirmSelect();

// Sync confirm (slow, reliable)
channel.basicPublish("", "queue", null, message);
channel.waitForConfirmsOrDie(5000);  // Block until confirmed

// Async confirm (fast, complex)
channel.addConfirmListener(new ConfirmListener() {
    public void handleAck(long deliveryTag, boolean multiple) {
        // Message confirmed
    }
    public void handleNack(long deliveryTag, boolean multiple) {
        // Message rejected - retry
    }
});

Kafka Guarantees

Delivery Guarantees (configurable):

1. At-most-once:
   ├─ acks=0 (no broker acknowledgment)
   └─ Risk: Messages can be lost

2. At-least-once (common):
   ├─ acks=1 (leader acknowledgment)
   ├─ Consumer: Commit offset after processing
   └─ Risk: Duplicates on failure

3. Exactly-once (Kafka 0.11+):
   ├─ Idempotent producer (enable.idempotence=true)
   ├─ Transactional producer/consumer
   ├─ acks=all + min.insync.replicas=2
   └─ True exactly-once within Kafka

Kafka Exactly-Once Semantics (EOS):
├─ Producer: Idempotence + Transactions
├─ Consumer: Read committed + Transactional writes
└─ Guaranteed no duplicates, no lost messages

Kafka Exactly-Once Example:

// Idempotent producer
Properties props = new Properties();
props.put("enable.idempotence", "true");  // Prevents duplicates
props.put("acks", "all");                 // Wait for all replicas
props.put("retries", Integer.MAX_VALUE);

// Transactional producer
props.put("transactional.id", "my-transactional-id");
KafkaProducer<String, String> producer = new KafkaProducer<>(props);

producer.initTransactions();
try {
    producer.beginTransaction();
    producer.send(new ProducerRecord<>("topic", "key", "value1"));
    producer.send(new ProducerRecord<>("topic", "key", "value2"));
    producer.commitTransaction();  // Atomic commit
} catch (Exception e) {
    producer.abortTransaction();  // Rollback
}

// Consumer: Read committed only
Properties consumerProps = new Properties();
consumerProps.put("isolation.level", "read_committed");  // Ignore uncommitted

Operational Complexity

Cluster Architecture

RabbitMQ Cluster:

┌───────────────────────────────────────────┐
│ RabbitMQ Cluster (Mirrored Queues)       │
├───────────────────────────────────────────┤
│                                           │
│  Node 1 (Master)                          │
│    ├─ Queue A (master)                    │
│    └─ Queue B (mirror)                    │
│                                           │
│  Node 2 (Slave)                           │
│    ├─ Queue A (mirror)                    │
│    └─ Queue B (master)                    │
│                                           │
│  Node 3 (Slave)                           │
│    ├─ Queue A (mirror)                    │
│    └─ Queue B (mirror)                    │
│                                           │
│  Challenges:                              │
│  ├─ Queue mirroring adds latency          │
│  ├─ Network partitions are tricky         │
│  └─ Rebalancing requires manual work      │
│                                           │
└───────────────────────────────────────────┘

Kafka Cluster:

┌────────────────────────────────────────────┐
│ Kafka Cluster (Distributed Log)           │
├────────────────────────────────────────────┤
│                                            │
│  Broker 1                                  │
│    ├─ Partition 0 (leader)                 │
│    ├─ Partition 1 (follower)               │
│    └─ Partition 2 (follower)               │
│                                            │
│  Broker 2                                  │
│    ├─ Partition 0 (follower)               │
│    ├─ Partition 1 (leader)                 │
│    └─ Partition 2 (follower)               │
│                                            │
│  Broker 3                                  │
│    ├─ Partition 0 (follower)               │
│    ├─ Partition 1 (follower)               │
│    └─ Partition 2 (leader)                 │
│                                            │
│  ZooKeeper / KRaft (metadata)              │
│    ├─ Leader election                      │
│    ├─ Cluster membership                   │
│    └─ Configuration management             │
│                                            │
│  Challenges:                               │
│  ├─ ZooKeeper dependency (being removed)   │
│  ├─ Rebalancing can be slow                │
│  └─ Partition reassignment is manual       │
│                                            │
└────────────────────────────────────────────┘

Operational Comparison

AspectRabbitMQKafkaWinner
Setup ComplexityEasyMedium🏆 RabbitMQ
MonitoringBuilt-in UIJMX + 3rd party tools🏆 RabbitMQ
Scaling StrategyVertical (add resources)Horizontal (add brokers)🏆 Kafka
UpgradesRolling upgradesRolling upgrades⚖️ Tie
Backup/RestoreEasy (queue export)Hard (log-based)🏆 RabbitMQ
Retention ModelMemory/disk limitTime/size based⚖️ Different
DependenciesNone (Erlang runtime)ZooKeeper/KRaft + JVM🏆 RabbitMQ
Learning CurveMediumSteep🏆 RabbitMQ

Summary: RabbitMQ is operationally simpler for most aspects. Kafka requires more expertise but scales horizontally better.

Use Cases

When RabbitMQ Wins

1. Task Queues / Job Processing
   ├─ Example: Image processing, email sending
   ├─ Why: Work queue pattern, automatic load balancing
   └─ Pattern: Producer → Queue → Worker pool

2. RPC (Request/Reply)
   ├─ Example: Microservice synchronous calls
   ├─ Why: Correlation ID, reply-to queue
   └─ Pattern: Client → Request queue → Server → Reply queue

3. Priority Queues
   ├─ Example: High-priority alerts vs low-priority logs
   ├─ Why: Native priority queue support (0-255)
   └─ Pattern: Messages with priority header

4. Complex Routing
   ├─ Example: Multi-tenant message routing
   ├─ Why: Topic exchanges, header exchanges, routing keys
   └─ Pattern: Exchange routing logic

5. Dead-Letter Queues (DLQ)
   ├─ Example: Failed message handling
   ├─ Why: Native DLQ with TTL and retry policies
   └─ Pattern: Main queue → DLQ after max retries

When Kafka Wins

1. Event Sourcing
   ├─ Example: Audit log, order history
   ├─ Why: Immutable log, replay capability
   └─ Pattern: Append-only event log

2. Stream Processing
   ├─ Example: Real-time analytics, fraud detection
   ├─ Why: Kafka Streams, ksqlDB
   └─ Pattern: Topic → Stream processor → Aggregated topic

3. Log Aggregation
   ├─ Example: Centralized logging, metrics
   ├─ Why: High throughput, long retention
   └─ Pattern: Application logs → Kafka → Elasticsearch

4. CDC (Change Data Capture)
   ├─ Example: Database replication, cache invalidation
   ├─ Why: Debezium + Kafka Connect
   └─ Pattern: DB → Kafka → Downstream consumers

5. Data Pipelines
   ├─ Example: Data warehouse ingestion
   ├─ Why: Kafka Connect, Schema Registry
   └─ Pattern: Source → Kafka → Sink connectors

Real-World Case Studies

LinkedIn (Kafka Creator)

Context: Social network with billions of events/day
Choice: Kafka (they built it!)

Use case:
├─ Activity tracking (views, likes, shares)
├─ Metrics collection
├─ Log aggregation
└─ Stream processing

Scale:
├─ 7 trillion messages/day
├─ 3000+ Kafka brokers
└─ Petabytes of data

Why Kafka won:
├─ Built for their scale requirements
├─ High throughput over low latency
├─ Event log for analytics

Uber (RabbitMQ for some, Kafka for others)

Context: Ride-sharing platform

RabbitMQ use cases:
├─ Dispatch system (low-latency job queue)
├─ Payment processing (transactional messages)
└─ Push notifications (routing to specific users)

Kafka use cases:
├─ Trip event log (replay for debugging)
├─ Real-time analytics (surge pricing)
└─ Data pipelines (ML features)

Lesson: Use both for different problems

Migration Considerations

RabbitMQ → Kafka

When to migrate:

✓ Outgrowing RabbitMQ throughput
✓ Need event replay and audit log
✓ Building event-driven architecture
✗ Happy with task queue pattern
✗ Low latency is critical

Migration Strategy:
1. Dual-write: Publish to both RabbitMQ and Kafka
2. Migrate consumers incrementally
3. Validate behavior in production
4. Decommission RabbitMQ

Kafka → RabbitMQ

When to migrate:

✓ Overengineered for simple use case
✓ Need complex routing (topic exchanges)
✓ Priority queues required
✗ Event sourcing is core requirement
✗ High throughput is critical

Migration Strategy:
1. Identify bounded contexts
2. Replace Kafka topics with RabbitMQ queues
3. Adapt consumer code (pull → push model)
4. Monitor latency and throughput

Conclusion

After years in production, my guidance:

RabbitMQ is a traditional message broker - great for queues, routing, and RPC. If you’re building task processing, job queues, or need complex routing, RabbitMQ is simpler and performs better for low-latency workloads.

Kafka is a distributed commit log - designed for event streaming, high throughput, and data pipelines. If you’re doing event sourcing, stream processing, or need to replay events, Kafka is the right tool.

For new projects:

  • Start with RabbitMQ if you need a message queue
  • Start with Kafka if you need an event log

For existing projects:

  • Don’t migrate unless you have clear pain points
  • Both are mature, battle-tested systems

The right choice depends on your use case, not which technology is “newer” or “more scalable.”

Quick Reference

Decision Matrix

RequirementRabbitMQKafkaWinner
Throughput50k msg/s500k+ msg/s🏆 Kafka
Latency<10ms10-50ms🏆 RabbitMQ
Message RetentionUntil ACKDays/weeks⚖️ Different
Replay Capability❌ No✅ Yes🏆 Kafka
Routing Patterns✅ Excellent (4 types)🟡 Basic (key-based)🏆 RabbitMQ
Priority Queues✅ Yes (0-255)❌ No🏆 RabbitMQ
Exactly-Once Delivery🟡 App-level✅ Native🏆 Kafka
Operational Overhead🟢 Low🟡 Medium🏆 RabbitMQ
Learning Curve🟡 Medium🔴 Steep🏆 RabbitMQ
Primary Use CaseTask queues, RPCEvent log, streaming⚖️ Different

Quick Decision:

  • Choose RabbitMQ for: Task processing, job queues, complex routing, low latency
  • Choose Kafka for: Event sourcing, stream processing, high throughput, data pipelines

Cite this guide

If you reference this guide, please link to the original URL and credit the author.

Michal Drozd. "RabbitMQ vs Kafka: Complete Messaging System Comparison". https://www.michal-drozd.com/en/guides/rabbitmq-vs-kafka/ (Published November 18, 2025, updated December 21, 2025).