NEW: Free hands-on NATS workshops. Live sessions on NATS fundamentals, leaf nodes, AI agents on NATS, & more.
All posts

How to Fetch a Small NATS KV Key Hierarchy Without a Watcher

For a one-time read of a small NATS Key/Value key prefix or hierarchy, you usually do not need to create a watcher just to close it immediately. Use a JetStream batched Direct Get request against the KV bucket’s backing stream when you want a short-lived, consumer-free read of matching entries.

This guidance comes from a common NATS community question: how can you efficiently fetch a small hierarchy of KV keys without creating a watcher?

What is the short answer?

Use batched Direct Get when all of the following are true:

  • You need a one-time read, not continuous updates
  • The key hierarchy is small enough to fetch in a bounded batch or set of batches
  • You are comfortable using the JetStream stream that backs the KV bucket
  • You can correctly handle KV latest-value, delete, and purge semantics

A watcher is still the right tool when you want to observe changes over time.

Why might a KV watcher be more than you need?

NATS KV watchers are useful when you want to observe changes over time. They can also provide an initial view of matching keys and then continue receiving updates.

That makes a watcher a good fit for live monitoring, but it can be awkward for short-lived lookups such as:

  • Fetching all keys under service.api.> once
  • Reading a small configuration subtree at startup
  • Inspecting a narrow key hierarchy for an administrative operation

For those cases, creating a watcher, waiting for the initial state, and then closing it can add lifecycle complexity you may not need.

How does a KV key hierarchy map to a JetStream subject?

NATS KV is backed by a JetStream stream. A KV bucket named CONFIG, for example, is typically backed by a stream named:

1
KV_CONFIG

KV entries are stored on subjects under the bucket namespace, commonly shaped like:

1
$KV.CONFIG.<key>

So a KV key hierarchy such as:

1
service.api.>

maps to a JetStream subject filter like:

1
$KV.CONFIG.service.api.>

A JetStream Direct Get request can be made with a subject filter and a batch size to retrieve multiple matching messages without creating a consumer. The exact request fields and response handling depend on the client API and server version you are using; the important idea is that batched Direct Get operates directly against the stream and does not require an ephemeral consumer.

The JetStream design for this behavior, including the request fields and response framing, is described in ADR-31: JetStream Direct Get. Refer to it, and to your client documentation, for the canonical field names.

What prerequisites should you check?

Before relying on batched Direct Get for a KV hierarchy read, confirm the following:

  • The backing stream has Direct Get enabled. KV buckets created through the KV API generally have this enabled, but if you are working with an older bucket or a custom stream, confirm that allow_direct and, where relevant, mirror_direct are set.
  • Your NATS server version supports batched Direct Get and batched response semantics. On older servers, you may need to fall back to per-key Direct Get calls or to a short-lived consumer.

Do you need current KV values or raw stream messages?

Be precise about what you mean by “fetch a key hierarchy.”

A KV bucket is a convenience layer on top of JetStream. If you read the backing stream directly, you are operating below the KV API. That means you may see stream messages that represent KV revisions, deletes, or purges, depending on the bucket configuration and the request you make.

For a KV-like view of current values, you generally want the latest message per key subject, not every historical message matching the filter. If your tooling exposes a “last message per subject” helper, that is usually closer to what KV.Get semantics imply for multiple keys.

You also need to account for KV delete and purge markers. KV delete and purge operations are written to the stream with a KV-Operation header, with values such as DEL and PURGE. When you consume the backing stream directly, you see those marker messages alongside live values and must check the header before treating a message as a current key/value entry.

Which client API should you use?

Some client libraries expose KV operations but do not provide a wildcard or hierarchy-oriented Get directly in the KV API. In that case, use the JetStream API beneath KV.

If you are using Synadia’s Orbit Go utilities, the JetStream extension package includes helpers such as GetBatch and GetLastMsgsFor for this style of operation:

https://github.com/synadia-io/orbit.go/tree/main/jetstreamext#getbatch-and-getlastmsgsfor

Those helpers are JetStream-level operations, not a KV-specific wildcard Get. You still need to provide the correct backing stream name and subject filter for the bucket and key hierarchy you want to read.

When should you use KV.Get, a watcher, or batched Direct Get?

Use KV.Get when you know the exact key

Choose KV.Get when:

  • You need one known key
  • You want the KV API to handle the backing stream details
  • You do not need wildcard or prefix retrieval

Use a KV watcher when you need live updates

Choose a KV watcher when:

  • You want to observe changes over time
  • You need a live subscription to a key pattern
  • You want the KV API’s watcher behavior rather than raw stream reads

Use batched Direct Get for small one-time hierarchy reads

Choose batched Direct Get when:

  • You need a one-time read of a small key hierarchy
  • You want to avoid creating a consumer
  • You are comfortable working with the KV backing stream
  • You can correctly handle latest-value and delete-marker semantics

For large hierarchies, long-running scans, or workflows that need continuous updates, a watcher or consumer-based approach may still be the better tradeoff.

Practical checklist for fetching a KV hierarchy without a watcher

Before using batched Direct Get for KV hierarchy reads, verify the following:

  1. Identify the KV bucket name.
  2. Identify the backing stream name, commonly KV_<bucket>.
  3. Confirm the backing stream has Direct Get enabled (allow_direct, and mirror_direct for mirror streams).
  4. Confirm your server version supports batched Direct Get.
  5. Convert the KV key pattern into the corresponding stream subject filter, commonly $KV.<bucket>.<key-pattern>.
  6. Decide whether you need raw matching messages or the latest message per key subject.
  7. Handle KV delete and purge markers correctly by checking the KV-Operation header.
  8. Set a batch size appropriate for the expected number of keys.
  9. If the result may exceed one batch, use the pagination or continuation behavior exposed by your client API.

Summary

For a small, one-off NATS KV hierarchy fetch, a watcher is not your only option. Because NATS KV is backed by JetStream, you can use batched Direct Get with a subject filter against the bucket’s backing stream to retrieve matching entries without creating a consumer.

The main tradeoff is that this is a lower-level JetStream operation. It can be efficient and convenient, but you are responsible for mapping KV keys to stream subjects and preserving the KV semantics you care about, especially latest-value behavior and delete markers.


Want help from the NATS experts? Meet with our architects to get help tailored to your use case and environment.

Get the NATS Newsletter

News and content from across the community


Cancel