So far, we’ve looked at different messaging patterns in NATS like Publish–Subscribe, Request–Reply, and Queue Groups. Those patterns are great for communication, but sometimes you need a way to store small pieces of state that services can read, update, or watch for changes.
NATS provides this through its Key-Value Store (KV), built on top of JetStream.
What is KV in NATS?

A Key-Value Store is a simple data structure:
- Key → the name of the value (like a variable or identifier).
- Value → the data associated with that key.
In NATS, the KV store is distributed and persistent. Multiple clients can update values, and others can subscribe to changes in real time. It feels like combining a database with a messaging system.
Why Use KV in NATS?
- Configuration Sharing
- Store application configs (e.g., feature flags) that multiple services can read and react to.
- Service Coordination
- Workers can update their status in the KV store, and other services can watch for changes.
- Lightweight State Tracking
- Keep track of counters, tokens, or session info without needing a heavy database.
NATS KV vs Redis
It’s natural to compare NATS KV with Redis, since both can store key-value pairs. But they are designed for different use cases:
Aspect | NATS KV | Redis |
---|---|---|
Built on | JetStream (message persistence layer) | In-memory database |
Persistence | Durable, message-log based, supports history | Optional persistence (RDB, AOF), but primarily in-memory |
Real-time Watch | Native, subscribers get notified instantly when keys change | Requires Pub/Sub feature, separate from main KV |
History | Keeps a versioned history of updates | Only stores latest value (unless you implement manually) |
Scale | Lightweight, integrated with NATS messaging | Very fast at caching and counters, but heavier to cluster |
Use case | Config sharing, service coordination, leader election | Caching, high-speed counters, session store |
What is “Version” in KV?
In NATS KV, every update to a key creates a new version (a sequence number).
- When you
Put
a value, JetStream stores it as an entry in the log with an incremented version. - If you
Get
orWatch
, you can see the version of the value. - This allows optimistic concurrency control: you can update a key only if it’s still at a certain version.
Example in Go
// Put a new value rev, _ := kv.Put("featureX.enabled", []byte("true")) fmt.Println("Current version:", rev) // Try update only if version matches err := kv.Update("featureX.enabled", []byte("false"), rev) if err != nil { fmt.Println("Update failed, value changed in the meantime") }
Why Version Matters?
- Consistency: Prevents overwriting changes from another client.
- Auditing: You can trace back changes by version.
- Safe collaboration: Multiple services can update the same key without stepping on each other’s changes.