NATS JetStream Explained: Streams, Consumers, and Durable Messaging

August 16, 2026 · Category: Guide · Tags: NATS, JetStream, Guide

Core NATS is a fast, fire-and-forget message bus: publish a message to a subject, and any subscriber currently connected receives it. If nobody is subscribed, the message is gone. That is perfect for request/reply and live dashboards, and useless for anything that must survive a disconnect.

JetStream is NATS' built-in persistence layer. It turns the same subject-based pub/sub into a durable message store with replay, acknowledgements, and per-consumer progress tracking. This guide walks through the two concepts you actually need to understand (streams and consumers), then covers retention, ack semantics, and how to decide what belongs in JetStream versus Core NATS.

Streams: The Durable Message Store

A stream is a server-side store of messages, bound to one or more subjects. When you publish to a subject a stream is bound to, the server persists the message instead of just broadcasting it to current subscribers.

Publish →  orders.created       →  Stream "ORDERS"
            orders.fulfilled    →    ├── msg 1 (orders.created)
            orders.cancelled    →    ├── msg 2 (orders.created)
                                  └── msg 3 (orders.cancelled)

Key stream settings:

Retention Policies

Policy Keeps messages while Best for
Limits Within max age/bytes/messages Time-series, telemetry, event logs
Interest At least one consumer is behind Task queues, work distribution
Work queue Until a consumer acks Exactly-one-processing jobs

The default is Limits: keep everything up to your configured bounds, then drop the oldest. That is the right choice for sensor data, logs, and any append-mostly workload. Work queue is the interesting one for job processing: each message is delivered to exactly one consumer and removed once acknowledged.

Consumers: The Stateful View

A consumer is a server-side, stateful view of a stream. The server tracks how far this consumer has progressed, so your application does not have to remember offsets. You can create multiple consumers over the same stream, each with its own position: one for a dashboard reading from the start, one for a worker only taking new messages.

Push vs Pull

Pull consumers are the workhorse of task processing: a fleet of workers each pull the next message, process it, and ack it. The server hands out each message to only one pull consumer in the group.

Acknowledgements and Redelivery

JetStream consumers acknowledge messages. The ack tells the server "I processed this, you can move on." The ack policy controls what happens if the consumer dies or takes too long:

Redelivery is bounded by max_deliver. When a message exceeds that, it is moved to the dead-letter subject if you configured one. Otherwise it is dropped. That is the mechanism that makes "exactly-once-ish" processing possible: at-least-once delivery plus deduplication plus bounded retries.

Mental model: Core NATS gives you at-most-once with zero overhead. JetStream gives you at-least-once with ack-based redelivery. It does not magically make processing idempotent: your handler should still be safe to run twice.

Replay and Catch-Up

Because the stream stores messages, a new consumer can replay history. You choose the delivery policy when you create the consumer:

Replay is what makes JetStream a historian, not just a bus: a downed dashboard reconnects, creates a consumer from its last sequence, and catches up on everything it missed.

JetStream vs Core NATS: What to Put Where

Core NATS JetStream
Delivery At-most-once, live only Durable, ack-based, replayable
Latency Lowest: no disk, no ack Higher: persistence + acks
Use for Request/reply, live telemetry, fan-out Histories, queues, job processing, catch-up
Storage cost None Disk, retention limits to manage

The common mistake is putting everything in JetStream "just in case." A live dashboard feeding off a stream adds disk I/O and ack overhead for data nobody will ever replay. Keep hot live data on Core NATS subjects; mirror the important subset into a stream only when you actually need history.

Getting Hands-On

The fastest way to internalize streams and consumers is to create a stream, publish to its subject, then create a couple of consumers with different delivery policies and watch each one advance independently. A GUI makes that far more tangible than typing nats CLI commands.

NATS Explorer ships an embedded NATS server with JetStream enabled. You can start the server, create a stream bound to orders.>, publish a few messages, then create a durable consumer and a pull consumer over the same stream, and watch their positions diverge as you publish more.

See JetStream Visually

NATS Explorer for macOS, Windows, and Linux includes an embedded server plus stream, consumer, KV, and Object store browsers. No CLI required.

Explore NATS Explorer →