Introduction
If you have been following the event‑driven architecture (EDA) scene for the last six months, you have seen a seismic shift. Kafka, once the undisputed king of message brokers, is now sharing the throne with a surprisingly lightweight contender: NATS JetStream. The conversation on Twitter/X and Hacker News is buzzing with developers claiming that NATS is the "Kafka for serverless" and that RabbitMQ is finally becoming a legacy system. In this hot‑take, I will break down why NATS JetStream is gaining traction, where Kafka still makes sense, and how you can decide which broker fits your microservice stack today.
The Current Hype
Serverless: Functions‑as‑a‑Service (FaaS) platforms like AWS Lambda and Cloudflare Workers demand ultra‑low latency and minimal cold‑start overhead. NATS fits like a glove.Kubernetes‑native: Operators for NATS and JetStream are now GA, making deployment as easy as helm install nats.Cost pressure: Running a Kafka cluster at scale costs $$$ in storage, networking, and ops. NATS can run on a single node for many workloads.Observability: Modern tooling (Prometheus, Grafana, Loki) integrates out‑of‑the‑box with NATS, while Kafka still requires a separate ecosystem.Kafka: The Giant That Won't Move
Kafka was built for massive throughput (millions of msgs/sec) and durability. Its log‑based architecture provides exactly‑once semantics, replayability, and strong ordering guarantees. However, those strengths come with trade‑offs:
Complex ops: Zookeeper (or KRaft) adds another moving part. Upgrading a 5‑node cluster is a weekend project.Heavy resource usage: Each broker needs ample RAM, SSD, and network bandwidth. For a typical 10‑topic, 3‑replica setup you might need 30+ GB of RAM just to stay healthy.Latency: End‑to‑end latency often sits around 10‑20 ms, which is acceptable for analytics pipelines but painful for user‑facing APIs.Sample Kafka Producer (Java)
java
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);
producer.send(new ProducerRecord<>("orders", "order-123", "{\"price\":100}"));
producer.close();
RabbitMQ: The Old Faithful (But Getting Old)
RabbitMQ excels at complex routing (topic, fanout, headers) and offers a mature client ecosystem. It shines in traditional enterprise integration patterns where you need request/reply or delayed delivery. Yet, its performance ceiling is lower than Kafka and NATS:
Throughput: ~200k msgs/sec on a beefy VM, far below Kafka's 5M+.Persistence overhead: Durable queues add disk I/O latency.Scaling: Clustering is possible but not as seamless as Kafka's partitioning model.Sample RabbitMQ Publisher (Python)
python
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.queue_declare(queue='tasks')
channel.basic_publish(exchange='', routing_key='tasks', body='{"id":42}')
connection.close()
NATS JetStream: The Dark Horse
NATS started as a simple, ultra‑lightweight pub/sub system. JetStream added persistence, stream replay, and at‑least‑once delivery while keeping the core NATS philosophy: simplicity, speed, and minimal footprint.
Why NATS is exploding right now
Sub‑millisecond latency: Benchmarks show 0.5‑1 ms round‑trip for 1 KB messages on a single node.Tiny footprint: A default NATS server uses ~30 MB RAM and <200 MB disk for a 10 GB stream.K8s‑first: The nats-operator auto‑creates streams, consumers, and even performs rolling upgrades without downtime.Built‑in request/reply: Perfect for microservice RPC patterns without adding a separate HTTP layer.Cloud‑native pricing: Running JetStream on a 2‑vCPU instance costs a fraction of a comparable Kafka broker.Sample NATS JetStream Publisher (Go)
go
package main
import (
"log"
"github.com/nats-io/nats.go"
)
func main() {
nc, err := nats.Connect("nats://localhost:4222")
if err != nil { log.Fatal(err) }
js, err := nc.JetStream()
if err != nil { log.Fatal(err) }
// Ensure a stream exists (idempotent)
_, err = js.AddStream(&nats.StreamConfig{Name: "ORDERS", Subjects: []string{"orders.*"}})
if err != nil && err != nats.ErrStreamNameAlreadyInUse { log.Fatal(err) }
// Publish a message
_, err = js.Publish("orders.created", []byte("{\"price\":100}"))
if err != nil { log.Fatal(err) }
log.Println("Message published to JetStream")
nc.Drain()
}
When to Pick Which Broker?
| Use‑case | Recommended Broker |
|---|
| High‑volume analytics pipelines (TB/day) | Kafka |
| Complex routing, delayed jobs, legacy integrations | RabbitMQ |
| Low‑latency, cloud‑native microservices, serverless | NATS JetStream |
| Mixed workloads with both stream replay and request/reply | NATS JetStream (with separate Kafka for heavy analytics) |
Decision matrix
Throughput > 1M msgs/sec → Kafka.Latency < 2 ms and resource budget < 1 vCPU → NATS.Need for AMQP features (dead‑letter exchanges, topic wildcards) → RabbitMQ.Code Showdown: Consumer Side
Below is a side‑by‑side comparison of a consumer that processes order events.
Kafka Consumer (Java)
java
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Collections.singletonList("orders"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
process(record.value());
}
consumer.commitSync();
}
NATS JetStream Consumer (Go)
go
sub, err := js.Subscribe("orders.created", func(msg *nats.Msg) {
process(msg.Data)
msg.Ack()
}, nats.Durable("order‑worker"), nats.ManualAck())
if err != nil { log.Fatal(err) }
defer sub.Unsubscribe()
select {}
Notice how the NATS version is half the lines and does not require explicit offset management – JetStream handles it automatically.
Hot Take: NATS Is the Future of EDA for Most Startups
The market is shifting. Startups that once spent months tuning Kafka clusters are now spinning up a single NATS node and scaling horizontally with a Helm chart. The argument that "Kafka is the only durable broker" is dead. JetStream gives you durability, replay, and consumer groups without the operational nightmare.
If you are building a new SaaS product in 2024, my advice is:
Start with NATS JetStream for all event streams. You get sub‑ms latency, simple ops, and a tiny cost bill.Add Kafka only when you hit the 1M msgs/sec threshold or need long‑term audit logs that survive cluster failures for years.Keep RabbitMQ around for legacy integrations but plan to migrate to NATS when possible.The real winners will be the teams that treat the broker as a runtime library, not a separate infra monster. NATS' Go and Rust clients feel like native language features, encouraging developers to embed messaging directly into business logic.
Conclusion
Event‑driven architecture is not a one‑size‑fits‑all. The era of "Kafka or bust" is over. NATS JetStream delivers the sweet spot of speed, simplicity, and durability that modern cloud‑native teams crave. Use Kafka for massive data pipelines, RabbitMQ for complex routing, but let NATS be the default backbone of your microservices. The conversation on HN will keep raging, but the metrics are clear: lower latency, lower cost, and less operational friction make NATS the hot new standard for 2024 and beyond.