Why this question keeps coming up
Sooner or later, every team building more than one service asks: “How do these services talk to each other without being glued together?” The usual answer is a message broker, and then the argument starts. Someone says Kafka, someone says RabbitMQ, and the Azure person on the team says Service Bus.
I’ve seen this debate more than once. The frustrating part is that all three can move a message from A to B, so people pick based on what they already know. This post tries to give you a better way to decide, from a .NET developer’s point of view.
If you’re new to Kafka itself, start with my earlier post, Kafka for .NET Developers, and come back here after.
The one idea that explains everything
The three tools are built around different mental models:
- RabbitMQ is a smart post office. You hand it a letter, it routes the letter to the right mailbox, and once the recipient signs for it, the letter is gone.
- Azure Service Bus is the same idea as a managed enterprise post office, with extra services like scheduled delivery, tracking, and a dead-letter shelf, all run by Microsoft.
- Kafka is a public record book. Events are written down in order and stay there. Anyone can read from any page, at any time, as often as they like.
Almost every difference below comes from that split: messages that get delivered and removed versus events that get recorded and kept.
Side-by-side comparison
| Kafka | RabbitMQ | Azure Service Bus | |
|---|---|---|---|
| Core model | Distributed append-only log | Broker with exchanges and queues | Managed broker with queues and topics |
| After a message is read | Stays until retention expires | Removed once acknowledged | Removed once completed |
| Replay old messages | Yes, by resetting the offset | Not on classic queues (streams add replay) | No |
| Routing | By topic and partition key | Very flexible (direct, topic, fanout, headers) | Topics with subscription filters |
| Ordering | Per partition | Per queue (can break with competing consumers) | Per session |
| Throughput | Very high | Moderate to high | Moderate |
| Built-in extras | Retention, compaction, Kafka Streams | Priorities, TTL, plugins | Dead-lettering, scheduled messages, sessions, duplicate detection, transactions |
| Who runs it | You, or a managed service (Confluent, Event Hubs) | You, or a hosted provider | Microsoft |
| Operational effort | High if self-hosted | Moderate | Very low |
| Cost model | Infrastructure or managed service | Infrastructure | Pay per tier and usage |
Treat the throughput row as a rule of thumb. Real numbers depend on message size, hardware, and configuration, so benchmark with your own workload before making a big decision.
Kafka: when the stream itself is the product
Kafka shines when many systems need to read the same stream of events, or when history matters.
Pick it when:
- Several independent services consume the same events
- You need to replay data (new consumers, bug fixes, rebuilding a read model)
- Volume is very high, like clickstreams, telemetry, or change data capture
- You’re building event sourcing or real-time analytics
Think twice when:
- You only need simple “do this job once” processing
- Nobody on the team wants to operate a cluster
- You need per-message features like delayed delivery or priorities, which Kafka doesn’t offer natively
On a project I worked on, 13 microservices cooperated through Kafka as the message broker with a pub/sub model. That setup suits Kafka well, because each service can subscribe to just the events it cares about without the publisher ever knowing who is listening.
The .NET client is Confluent.Kafka. One thing to know: you manage offsets and partitions yourself, which gives you power but also more to think about.
RabbitMQ: when routing and task distribution matter
RabbitMQ is the classic choice for work queues and flexible routing. Its exchange model lets you decide exactly where a message goes. You can send it to one queue, broadcast it to many, or route it by pattern.
Pick it when:
- You’re distributing background jobs across workers
- You need rich routing rules
- You want a lightweight, open-source broker that runs anywhere
- Throughput needs are moderate and low latency matters
Think twice when:
- You need to replay old events as a core feature (RabbitMQ has streams for this, but it’s a different mode from classic queues)
- You expect enormous event volumes over long retention
A few lines show the .NET flavor, using the async API in recent versions of RabbitMQ.Client:
var factory = new ConnectionFactory { HostName = "localhost" };
await using var connection = await factory.CreateConnectionAsync();
await using var channel = await connection.CreateChannelAsync();
await channel.QueueDeclareAsync("emails", durable: true,
exclusive: false, autoDelete: false);
var body = Encoding.UTF8.GetBytes(json);
await channel.BasicPublishAsync(exchange: "", routingKey: "emails", body: body);
For durability on modern setups, look at quorum queues, which are the recommended replicated queue type. The official RabbitMQ tutorials are some of the best learning material in this space.
Azure Service Bus: when you want enterprise features without running anything
Service Bus is a fully managed broker on Azure. You don’t patch servers, size clusters, or manage disks. In exchange, you get a set of features that are genuinely useful in business systems:
- Dead-letter queues built in, so failed messages are parked automatically after a configurable number of delivery attempts. See the dead-letter queue docs.
- Sessions, for ordered processing of related messages (for example, everything for one customer). See message sessions.
- Scheduled messages, to deliver something at a future time.
- Duplicate detection, so retries don’t create double work.
- Transactions, to group operations atomically.
- Topics and subscriptions with filters, for pub/sub with per-consumer rules.
Pick it when:
- You’re already on Azure and want zero broker operations
- You need reliable, transactional, enterprise-style messaging
- Features like scheduling, sessions, and dead-lettering would otherwise be code you write yourself
Think twice when:
- You need very high throughput streaming or long-term replay
- You must stay cloud-neutral or run on-premises
A taste of the .NET SDK, Azure.Messaging.ServiceBus:
await using var client = new ServiceBusClient(connectionString);
// send
var sender = client.CreateSender("emails");
await sender.SendMessageAsync(new ServiceBusMessage(json));
// receive
var processor = client.CreateProcessor("emails");
processor.ProcessMessageAsync += async args =>
{
// do the work, then tell Service Bus it's done
await args.CompleteMessageAsync(args.Message);
};
processor.ProcessErrorAsync += args => Task.CompletedTask; // log in real code
await processor.StartProcessingAsync();
Notice the explicit CompleteMessageAsync. If your code throws before that, the message becomes available again and, after enough failures, lands in the dead-letter queue. That safety net is the main reason many teams like it.
I’ve worked with both sides of this comparison. On one project, 13 microservices talked to each other through Kafka using pub/sub. In my usual .NET and Azure work, I reach for Azure Service Bus. The difference I feel most is who carries the burden: with Service Bus, things like dead-lettering and retries come built in, while with Kafka I had to think harder about offsets, partitions, and what happens to a message that keeps failing.
A simple way to decide
Ask these questions in order:
| Question | If yes, lean toward |
|---|---|
| Do multiple systems need to read the same events, or do you need replay? | Kafka |
| Is it mostly background jobs and tasks handed to workers? | RabbitMQ or Service Bus |
| Are you on Azure and want no infrastructure to run? | Service Bus |
| Do you need delayed delivery, sessions, or built-in dead-lettering with minimal code? | Service Bus |
| Do you need complex routing rules and run your own infrastructure? | RabbitMQ |
| Is volume massive, like telemetry or clickstream data? | Kafka |
Real-world scenarios
“Send a welcome email after signup.” A simple job. RabbitMQ or Service Bus is plenty. Kafka would work, but it’s like using a truck to carry a pizza.
“Many services must react to every order, and analytics wants to replay last month’s orders.” Kafka. Fan-out and replay are exactly its strengths.
“Process payments reliably, in order per customer, with retries and a dead-letter store.” Service Bus with sessions fits very naturally.
“Route different message types to different worker pools by rules.” RabbitMQ’s exchanges handle this elegantly.
“We’re on Azure but already have Kafka-based code.” Look at Azure Event Hubs with its Kafka endpoint. It lets Kafka clients connect to a managed Azure service, with some differences from running real Kafka, so check the compatibility notes.
Mistakes teams make when choosing
- Picking Kafka because it’s popular. Popularity isn’t a requirement. Replay, fan-out, or scale are.
- Treating Kafka like a queue. It has different semantics: offsets, partitions, and consumer groups. Designing it like RabbitMQ leads to confusion.
- Ignoring operations cost. Self-hosting Kafka or RabbitMQ is a real commitment: upgrades, monitoring, disk, backups.
- Skipping idempotency. All three tools can deliver a message more than once in failure scenarios. Consumers must tolerate duplicates.
- Hard-wiring the broker into business code. Wrap it behind an interface, or use a library like MassTransit, so switching later isn’t a rewrite.
I’ve used both Kafka and Azure Service Bus in real projects, for very different jobs. On a quest-based project, I used Service Bus behind a CDS uploader that had to process Excel workbooks with thousands of rows. Each upload was handed off through a Service Bus queue and picked up by an Azure Function trigger, so the heavy work happened in the background instead of making the user wait. What I liked was that retries and dead-lettering were already there, and the Function trigger handled the plumbing, so I could focus on the processing logic instead of writing my own polling code. Kafka, on the other hand, was the backbone of a 13-microservice system, where many services needed to react to the same events. That contrast is what made the choice clear for me: Service Bus for “do this job reliably,” Kafka for “many services need to know what happened
Frequently asked questions
Is Kafka faster than RabbitMQ?
For sustained high-volume streaming, Kafka generally scales further. For low-latency, small-scale task delivery, RabbitMQ can feel just as quick. Test with your own workload.
Can I use more than one in the same system?
Yes, and many companies do. For example, Kafka for the event backbone and a queue for job processing.
Does Service Bus support pub/sub?
Yes, through topics and subscriptions, with filter rules per subscription.
Which is easiest for a beginner?
Service Bus (if you have an Azure account) or RabbitMQ locally via Docker. Kafka has a steeper learning curve.
Do I need a library on top of these?
Not required, but libraries like MassTransit add retries, sagas, and consistent patterns across brokers.
Where to keep learning
- Apache Kafka documentation
- RabbitMQ documentation and tutorials
- Azure Service Bus overview
- Azure Event Hubs for Kafka
- MassTransit, a .NET abstraction over multiple brokers
- Designing Data-Intensive Applications by Martin Kleppmann, for the thinking behind logs, queues, and streams
- Enterprise Integration Patterns by Gregor Hohpe and Bobby Woolf, the classic on messaging patterns
Final thoughts
There’s no single winner. The right choice follows from the problem: Kafka for replayable event streams at scale, RabbitMQ for flexible routing and task distribution, Azure Service Bus for managed, feature-rich enterprise messaging. Start from what your system actually needs, such as replay, routing, or operational simplicity, and let that pick the tool.
Spot something outdated or wrong? Tell me in the comments and I’ll update the post.