NATS JetStream Explained: Streams, Consumers, KV, and When to Use Core NATS

August 16, 2026 · Category: Guide · Tags: NATS, JetStream, KV Store, 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, and the KV and Object stores are built on top of it. This guide walks through streams and consumers, then retention, ack semantics, replay, and how to decide between Core NATS, a stream, and a KV bucket.

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.

KV and Object Store: Latest Value, Not History

NATS KV is built on a JetStream stream but exposes a different interface: get, put, delete, plus watches and a bounded revision history per key. Each key holds one current value, and an update overwrites the previous one in place. It is a state store, not a message log.

The Object store is the same idea for large blobs such as firmware images or files. It splits the object into chunks on top of JetStream. Do not push a 4 MB file through a single message: it runs into the default max_payload of 1 MB.

Core NATS vs JetStream vs KV: What to Put Where

Core NATS JetStream stream KV bucket
Delivery At-most-once, live subscribers only Durable, ack-based, replayable Latest value per key, watchable
Latency Lowest: no disk, no ack Higher: persistence + acks Same as a stream write
Use for Request/reply, live telemetry, fan-out, presence heartbeats Event history, work queues, catch-up, buffering between producer and consumer Config, device state, flags
Storage cost None Disk, retention limits to manage Disk, bounded by key count and history depth

A typical system uses all three on one server: Core NATS for live telemetry and request/reply, a stream for the audit history of events that matter, and a KV bucket for device configuration that services watch.

Common Mistakes

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. The same window has KV and Object store browsers, so you can put a config key in a bucket and watch it update live.

See JetStream Visually

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

Explore NATS Explorer →