You have read how NATS subjects, JetStream streams, KV mirrors, CQRS projections, and observability fit together in Parts 1–5 of this series. You have seen where a message bus sits in a growing architecture in The Pieces of Modern, Effective Software Design. The remaining question is blunt: when is NATS the right bus, and when should you reach for Kafka or AWS SQS instead?
This article is a decision framework, not a winner-takes-all shootout. Claims below are tied to official documentation. Where a row reflects engineering judgment — for example, typical ops burden in a small team — that is labeled as inference.
Researched against docs.nats.io, Apache Kafka documentation, and AWS SQS documentation on July 6, 2026.
TL;DR
- Core NATS is at-most-once, in-memory pub/sub with request/reply and queue groups — excellent for live internal messaging when subscribers are online.
- JetStream (built into
nats-server) adds durable streams, replay, KV, and at-least-once / opt-in exactly-once semantics — the NATS answer to temporal decoupling without adopting a separate platform. - Kafka is a distributed event log: topics are partitioned, retained, and replayable by design; stream processing (Kafka Streams, Connect, ecosystem) is the main reason teams adopt it.
- SQS is a managed point-to-point queue on AWS: no cluster to run, at-least-once standard queues, FIFO with exactly-once processing when you need ordering — not a log and not native pub/sub.
- Pick on symptoms (offline consumers, replay window, fan-out shape, cloud lock-in tolerance, who runs the cluster), not on unsourced latency leaderboards.
What You Will Learn Here
- How NATS (Core + JetStream), Kafka, and SQS differ on eight engineering dimensions
- When NATS wins — including patterns from Part 1 (request/reply, queue groups), Part 2 (streams/consumers), Part 3 (KV), and Part 4 (internal plumbing)
- When Kafka wins — log retention, partitions, stream processing
- When SQS wins — AWS-native decoupling without broker operations
- An ASCII decision flow and symptom → fit tables you can paste into an ADR
Series context: Part 1 · Part 2 · Part 3 · Part 4 · Part 5
Three Different Primitives
Before the comparison table, align on what each system is:
| System | Primary abstraction | Mental model |
|---|---|---|
| Core NATS | Subject-based pub/sub | Fire-and-forget routing; subscribers must be online |
| JetStream | Durable stream + consumer | Capture, replay, ack-based delivery on top of NATS transport |
| Kafka | Partitioned commit log | Append-only topics; consumers track offsets; retention is a first-class knob |
| SQS | Hosted queue | Producer enqueues; one consumer group processes; message deleted after ack |
Core NATS JetStream Kafka SQS
───────── ───────── ───── ───
Pub ──► Sub Pub ──► Stream ──► Cons Pub ──► Topic (log) Pub ──► Queue ──► Worker
(live only) (persist + replay) ──► many consumers (point-to-point)
(offsets, retention)
SQS is not a drop-in substitute for Kafka or NATS pub/sub. AWS documents SQS as decoupling via queues; fan-out to many subscribers typically pairs SNS → SQS (SQS overview).
Comparison Dimensions
Delivery semantics
| Dimension | Core NATS | JetStream | Kafka | SQS (standard) | SQS (FIFO) |
|---|---|---|---|---|---|
| Default guarantee | At-most-once, best-effort (What is NATS?) | At-least-once; work-queue retention can target exactly-once consumption per message (JetStream) | At-least-once by default; idempotent producer + transactions enable exactly-once within Kafka when configured (Kafka design — semantics) | At-least-once (SQS queue types) | Exactly-once processing with deduplication (SQS queue types) |
| Offline subscriber | Message lost | Replay from stream | Read from retained log | Message waits in queue (until retention) | Same |
| Idempotency expectation | Always design for it | Always design for it | Required for at-least-once paths | Required (duplicates possible on standard) | Less duplicate risk; still validate |
Engineering note: “Exactly-once” anywhere usually means “exactly-once under documented failure models.” Kafka’s docs are explicit that end-to-end exactly-once to external systems requires cooperation with those systems.
Persistence model
| Dimension | Core NATS | JetStream | Kafka | SQS |
|---|---|---|---|---|
| Storage | In-memory only; never written to disk (What is NATS?) | File or memory streams; retention limits (max_age, max_bytes, discard policy) | Log segments on broker disks; retention per topic | Redundant storage across AZs; default retention 4 days, max 14 days (SQS overview) |
| Long-term archive | No | Bounded by stream limits | Yes — weeks/months common; “store data for a long time is perfectly fine” per Kafka design docs | No — consumption deletes; not an analytics log |
| Ordering | No global order | Per-subject order within a stream | Per-partition total order | FIFO queues: order within message group |
Operations complexity
| Dimension | NATS (self-hosted) | Kafka (self-hosted) | SQS |
|---|---|---|---|
| Process to run | Single nats-server binary; JetStream enabled in config | Multi-broker cluster; ZooKeeper deprecated — KRaft mode in modern versions | None — fully managed API |
| Scaling model | Cluster + JetStream RAFT replication (1–5 replicas per stream) | Partitions, brokers, ISR, rebalancing | Automatic; API rate limits per queue type |
| Day-2 concerns | TLS, accounts/JWT, stream/consumer tuning, backup of JetStream store | Partition planning, consumer lag, broker disk, connector ops, upgrades | IAM, DLQ redrive, visibility timeout tuning, quota increases |
| Inference: smallest team burden | Lower for Core NATS; moderate with JetStream HA | Higher — plan for a platform-minded owner | Lowest ops surface if already on AWS |
NATS documents nats-server as “less than 20 MB” (What is NATS?). That speaks to deployment footprint, not a performance claim.
Fan-out vs queue
| Pattern | Core NATS | JetStream | Kafka | SQS |
|---|---|---|---|---|
| Pub/sub fan-out | Native — every subscriber gets a copy | Native — multiple consumers on a stream | Native — multiple consumer groups per topic | Not native — use SNS fan-out to multiple queues |
| Work queue / competing consumers | Queue groups | Work-queue retention + queue consumers | Consumer group per partition assignment | One consumer receives per in-flight message (visibility timeout) |
| Request/reply RPC | Built-in inbox pattern (Part 1) | Possible but not the sweet spot | Unusual — not the primary API | Not supported |
Replay
| Capability | Core NATS | JetStream | Kafka | SQS |
|---|---|---|---|---|
| Replay after the fact | No | Yes — by sequence, time, last message, replay speed (JetStream) | Yes — consumer resets offset; events not deleted on read (Kafka intro) | No meaningful replay — messages are consumed and deleted |
| Multiple independent read positions | N/A | Multiple consumers per stream | Multiple consumer groups | N/A (queue semantics) |
| Reprocess production traffic slowly | No | original replay policy | Reset offsets / new consumer group | Not applicable |
Latency profile (qualitative only)
Official docs describe mechanisms, not cross-vendor latency rankings. Reason qualitatively:
| Factor | NATS Core | JetStream | Kafka | SQS |
|---|---|---|---|---|
| Path length | Subject route, in-memory | Persist → replicate → deliver | Batch-oriented log append/fetch | HTTPS API + managed storage |
| Typical sweet spot (inference) | Live control-plane traffic, RPC | Durable internal events with modest retention | High-volume pipelines with batching | Async job handoff across AWS services |
| What not to do | Claim “X ms faster than Y” without your own benchmarks in your network | Same | Same | Same |
For SQS, polling vs long polling and visibility timeout directly affect observed end-to-end delay (message lifecycle) — tune these before swapping brokers.
Multi-cloud / portability
| Dimension | NATS | Kafka | SQS |
|---|---|---|---|
| Run anywhere | Yes — binary on bare metal, VMs, K8s, edge (What is NATS?) | Yes — self-managed or vendor-managed (MSK, Confluent, Aiven, etc.) | AWS only |
| Managed offerings | Synadia NGS, various clouds | Many | Native AWS |
| Protocol coupling | NATS wire protocol | Kafka protocol + rich ecosystem | AWS SDK / HTTP API |
| Inference: exit cost | Low if self-hosted; higher if tied to one managed NATS vendor | Moderate — ecosystem creates gravity | High — deepest integration, hardest to lift-and-shift |
Cost model (qualify every claim)
| Cost driver | NATS (self-hosted) | Kafka (self-hosted) | SQS |
|---|---|---|---|
| Metering | Infra you pay for (CPU, disk, network) — no per-message license from the OSS server | Same + often larger disk footprint for retention | Per-request pricing; 1 MiB payload = up to 16 request units (SQS pricing) |
| Free tier | N/A (your cloud bill) | N/A | 1 million requests/month (SQS pricing) |
| Hidden costs | HA replicas, monitoring, on-call | Connector VMs, schema registry, larger clusters | KMS, cross-region transfer, S3 extended client for large payloads |
| Inference | Cost-effective at steady internal volume on existing K8s | Cost-effective when log retention replaces other storage pipelines | Cost-effective for spiky AWS glue code if request count stays bounded |
Do not extrapolate “cheapest” from list prices alone — model requests, payload size, retention GB-months, and engineer time.
When NATS Wins
NATS fits when messaging is internal infrastructure — service-to-service, edge-to-cloud, control plane — and you want one lightweight runtime that spans Core pub/sub and JetStream durability.
| Symptom | NATS fit | Series / doc anchor |
|---|---|---|
| Services need RPC without HTTP everywhere | Core request/reply | Part 1 |
| Workers must share load on one subject | Queue groups or JetStream work-queue streams | Part 1, Part 2 |
| Subscribers go offline briefly; events must survive | JetStream streams + durable consumers | Part 2 |
| Latest entity state + change notifications | JetStream KV watch | Part 3 |
| Postgres is truth; bus is plumbing to search/warehouse | CDC → KV → index subjects | Part 4 |
| Same security context from edge to cloud | NATS 2.0 accounts/JWT; single binary | What is NATS? |
| You want persistence without a separate “streaming platform” team | JetStream built into nats-server | JetStream |
Internal mesh style (NATS sweet spot)
API ──request/reply──► Authz svc
│
└──publish──► orders.created ──► [billing worker]
└──► [analytics projector]
JetStream stream (optional durability)
└──► KV mirror ──watch──► indexer
When NATS is a weaker default (inference):
- Multi-team analytics on months of immutable history with SQL-over-streams tooling
- Organization standard is already Kafka Connect + Flink + schema registry
- Team has zero appetite to operate any message broker — SQS/Lambda may win on ops alone
When Kafka Wins
Kafka fits when the log itself is the product — high-volume ingestion, many downstream consumers at different speeds, long retention, stream processing.
| Symptom | Kafka fit | Doc anchor |
|---|---|---|
| Many teams read the same topic at different offsets | Consumer groups + retained log | Kafka intro — topics |
| Event sourcing / audit with replay months later | Configurable retention; disk-oriented design | Kafka design — persistence |
| Stream joins, windows, aggregations in the bus layer | Kafka Streams API | Kafka APIs |
| Ingest from Postgres, S3, Elasticsearch via managed connectors | Kafka Connect | Kafka APIs — Connect |
| Partition-key locality (per-user ordering) | Key → partition mapping | Kafka design — producer |
| Exactly-once between Kafka topics in a pipeline | Transactional producer + read_committed | Kafka semantics |
Log-centric analytics (Kafka sweet spot)
Producers ──► topic (partitions P0..Pn, replicated)
│
┌───────────┼───────────┬──────────────┐
▼ ▼ ▼ ▼
Warehouse Flink job Search sink Audit archive
(Connect) (Streams) (Connect) (long retention)
Tradeoffs to accept: broker count, partition planning, consumer lag dashboards, and upgrade discipline. Kafka’s own design docs target “high volume event streams” and “periodic data loads from offline systems” — not minimal internal RPC.
When SQS Wins
SQS fits when workloads are AWS-native, queue-shaped, and you want AWS to own availability.
| Symptom | SQS fit | Doc anchor |
|---|---|---|
| Decouple Lambda, ECS, EC2 workers | Standard queue + event source mapping | SQS overview |
| No team to run Kafka/NATS | Fully managed | SQS overview |
| Failed messages need inspection | Dead-letter queues + redrive | SQS DLQ |
| Strict per-entity ordering | FIFO + message group ID | SQS queue types |
| Fan-out to many workers on AWS | SNS → multiple SQS queues | SQS vs SNS |
| Retention measured in days, not months | Default 4d, max 14d retention | SQS overview |
AWS glue (SQS sweet spot)
API Gateway / ALB ──► SQS ──► Lambda / ECS worker
│
└── on failure ──► DLQ ──► redrive / alarm
When SQS is a weaker default (inference):
- Native pub/sub with dozens of live subscribers on the same subject
- Replay of arbitrary historical offsets for new consumers
- Multi-cloud or on-prem primary deployment
- Request/reply between internal microservices (use HTTP/gRPC or NATS Core instead)
Decision Flow
Start: async communication needed
│
▼
┌─────────────────────────────────────┐
│ Must run outside AWS or multi-cloud? │
└─────────────────────────────────────┘
│ yes │ no
▼ ▼
┌───────────────────┐ ┌──────────────────────┐
│ Need log retention │ │ Queue + AWS services │
│ + stream ecosystem│ │ only? │
└───────────────────┘ └──────────────────────┘
│ yes │ no │ yes │ no
▼ ▼ ▼ ▼
┌────────┐ ┌─────────────────────────┐ ┌────────┐
│ Kafka │ │ Subscribers often offline │ │ SQS │
│ │ │ or need replay/KV/RPC? │ │ (+SNS │
└────────┘ └─────────────────────────┘ │ if fan-│
│ yes │ no │ out) │
▼ ▼ └────────┘
┌─────────┐ ┌──────────┐
│JetStream│ │Core NATS │
│(+ Core │ │(live fan-│
│ as RPC) │ │out, RPC) │
└─────────┘ └──────────┘
Concrete Implementation: Symptom-Based Selection Guide
Use this section as an ADR appendix. Each row maps a production symptom to a reasonable default and the first config knob to verify.
Symptom → fit (messaging pattern)
| Symptom | Reasonable default | First knob to check |
|---|---|---|
| ”Billing must charge exactly once per order ID” | JetStream work-queue stream or SQS FIFO with dedup or Kafka idempotent producer + idempotent consumer | Idempotency key in your handler either way |
| ”Search indexer can lag 30s; API reads Postgres” | JetStream or Kafka for change feed; not Core NATS alone | Consumer ack wait / max deliver (Part 2) |
| “10 microservices need the same fire-and-forget event live” | Core NATS pub/sub | Plain vs queue subscribers (Part 1) |
| “New team wants SQL over 90 days of clicks” | Kafka (+ warehouse sink) | Topic retention bytes / time |
| ”Lambda processes uploads overnight” | SQS standard | Visibility timeout ≥ p99 handler duration |
| ”Service A calls Service B synchronously internal” | Core NATS request/reply | Timeout + ErrNoResponders handling |
| ”Share latest config per region” | JetStream KV + watch | Bucket replica count (Part 3) |
| “Exactly-once from topic A to topic B” | Kafka transactions | isolation.level=read_committed |
| ”Fan-out to 50 SQS queues on AWS” | SNS → SQS | Queue policy per subscriber |
Symptom → fit (operational constraints)
| Symptom | Lean toward | Why (source-backed or inference) |
|---|---|---|
| “We have no SRE for messaging” | SQS on AWS | Managed service (SQS overview) — inference for team size |
| ”We already run K8s and want one small binary” | NATS | Single server process (What is NATS?) |
| ”Compliance wants immutable audit log 1 year” | Kafka | Retention model (Kafka intro) |
| “Burst to millions/sec with AWS billing OK” | SQS standard | Scales without provisioning (SQS overview) |
| “Edge devices + cloud same bus” | NATS | Deployment model (What is NATS?) — inference for edge |
Minimal wiring examples (selection smoke test)
NATS JetStream — durable order event (from series context):
nats stream add ORDERS \
--subjects "orders.>" \
--storage file \
--retention limits \
--max-age 7d \
--replicas 3
Kafka — topic with explicit retention (illustrative):
# server.properties / topic config
log.retention.hours=168
min.insync.replicas=2
SQS — visibility timeout aligned to handler (AWS CLI):
aws sqs set-queue-attributes \
--queue-url "$QUEUE_URL" \
--attributes VisibilityTimeout=300
If you cannot articulate which row in the tables above matches your symptom, you are not ready to pick a broker — define the delivery guarantee and retention window first.
Hybrid Architectures (Common in Practice)
Real systems mix tools:
| Pattern | Composition |
|---|---|
| AWS ingress, internal bus | ALB → SQS → bridge service → NATS JetStream |
| Analytics off internal events | NATS JetStream → (connector) → Kafka → warehouse |
| Legacy lift to cloud | On-prem NATS ↔ supercluster ↔ cloud NATS; SQS only at AWS boundary |
Hybrids add operational seams — document ownership of retry, DLQ, and schema at each boundary.
Common Mistakes
- Choosing Kafka for internal RPC. Request/reply is not the Kafka sweet spot; use HTTP/gRPC or NATS Core.
- Choosing SQS for pub/sub. Use SNS fan-out or a bus with native subjects/topics.
- Using Core NATS for billing events. At-most-once + offline subscribers = silent loss (What is NATS?).
- Expecting SQS replay. Once deleted, it is gone — not a log.
- Ignoring idempotency. At-least-once is the default almost everywhere.
- Benchmark shopping. Vendor marketing and stale blog posts are not your network topology.
How This Fits the Series
| Part | Focus |
|---|---|
| 1 | Core NATS primitives |
| 2 | JetStream durability |
| 3 | KV as change stream |
| 4 | CQRS projections with NATS plumbing |
| 5 | Observability and SLOs |
| 6 (this article) | When NATS vs Kafka vs SQS |
For the architectural ladder (outbox, projections, search), see The Pieces of Modern, Effective Software Design.
Sources
- What is NATS? — QoS, Core vs JetStream, deployment scale
- Core NATS — at-most-once pub/sub
- JetStream — streams, retention, replay, replication, KV
- JetStream Key/Value Store — KV buckets and watch semantics
- Apache Kafka — Introduction — topics, partitions, consumer groups, APIs
- Apache Kafka — Design — log persistence, producer partitioning, pull consumption
- Apache Kafka — Message Delivery Semantics — at-least-once, exactly-once, transactions
- Amazon SQS — What is Amazon SQS? — queue lifecycle, SNS comparison
- Amazon SQS — Queue types — standard vs FIFO guarantees
- Amazon SQS — Dead-letter queues — redrive and
maxReceiveCount - Amazon SQS pricing — per-request metering and payload chunking