A common community question is how to deliver configuration to thousands of services with NATS JetStream: each service has its own configuration, should receive the current value when it starts, and should continue receiving future updates.
There are two practical approaches:
For most configuration use cases, start with NATS KV.
NATS KV is built on JetStream and is designed for “latest value by key” workloads. That maps naturally to service configuration:
svc_a, svc_b, or another naming scheme that fits your environment.Conceptually:
1KV bucket: service-config2
3key: svc_a -> configuration for service A4key: svc_b -> configuration for service B5key: svc_c -> configuration for service CThis is usually simpler than modeling each configuration value as a stream subject and managing consumers yourself. KV gives you a purpose-built abstraction for storing and observing values by key, while JetStream handles the persistence underneath.
max_msgs_per_subject = 1?A raw JetStream stream can represent this pattern. For example, you could publish service configurations on subjects such as:
1config.svc_a2config.svc_b3config.svc_cThen configure the stream to retain only the latest message per subject. That can work, and it gives you direct access to stream and consumer features.
However, if the requirement is simply “store the current configuration for this service and notify it when the value changes,” KV is the more direct API. It avoids making every application reason about stream retention, consumer cursors, subject filters, and delivery policies.
A useful way to think about the tradeoff:
| Requirement | Prefer |
|---|---|
| Latest config value by service ID | KV |
| Watch config changes by key or prefix | KV |
| Need low-level stream retention and consumer control | Raw JetStream stream |
| Need custom subject topology or stream processing behavior | Raw JetStream stream |
KV is not a separate storage system from JetStream; it is a JetStream-backed abstraction. But that abstraction is important. Avoid depending on KV’s internal stream naming or implementation details as an application contract. Use the documented KV APIs when you choose KV.
A common first design is to create one durable consumer and have all services use it to receive their own configuration updates.
That is usually not the right model.
A durable consumer represents a single logical delivery state. If many independent services share one durable, they are not getting independent per-service cursors. This can lead to surprising behavior and does not model “each service receives its own configuration stream” cleanly.
If you use raw JetStream streams, a better design is usually one filtered consumer per service instance or per service identity, depending on your delivery semantics. For example, a service interested only in config.svc_a can use a consumer filtered to that subject.
Thousands of consumers may be workable, but they are not free. Consumers are server-side resources. Durable consumers in particular persist state and must be tracked even when the application is not actively connected.
If services are dynamic and only need configuration while they are running, ephemeral consumers are often a better fit than durable consumers. They avoid leaving long-lived consumer state behind for services that may not be active.
The tradeoff is operational:
If you expect thousands of services to start at once, test that startup path under realistic conditions. Consumer creation, catch-up behavior, and configuration update fanout are all worth validating in your own environment.
Another design is to store configuration in JetStream and use stream republish so updates are emitted onto Core NATS subjects. Services can then subscribe with normal Core NATS subscriptions for ongoing updates.
This can reduce the need for a JetStream consumer per service for the live-update path. But it introduces another question: how does a service get the current value when it starts?
You still need a reliable initial read, such as:
This approach can be useful when you want lightweight live notifications, but it is less convenient than KV for the full “get current value, then watch for changes” lifecycle.
For per-service configuration, the most practical starting point is:
Use a raw JetStream stream when you need stream-specific controls that KV does not expose. In that case, prefer filtered consumers for the relevant service subject, and be intentional about durable versus ephemeral consumer lifecycle.
NATS KV is usually the right abstraction for service configuration because the problem is key-oriented: each service needs the latest value for its own key and a way to observe changes. JetStream streams can implement the same idea, but they require more consumer and retention design. If you choose the stream path, avoid sharing one durable consumer across independent services and validate the overhead of your consumer model at the scale you expect.
Want help from the NATS experts? Meet with our architects to get help tailored to your use case and environment.



News and content from across the community