Durable Consumers in NATS JetStream for Reliable Event Processing
Use NATS JetStream durable consumers to get at-least-once event processing with replay after crashes, without building your own queue. A named consumer tracks acknowledged sequence so restarts resume safely.
27 Jul 2025, 10:09 UTC

An order service publishes orders.created to NATS, a worker picks it up, crashes before acknowledging, and the event disappears. With core NATS publish/subscribe the delivery is in-memory and at-most-once. The practical fix is not to add a queue in front of NATS, it is to use JetStream with a durable consumer so the broker remembers where each named subscriber left off.
The problem with fire-and-forget
NATS core is subject-based publish/subscribe and request-reply with in-memory delivery. It is fast and simple, but a subscriber that disconnects loses any messages that were in flight. For event processing you usually need at-least-once delivery across restarts and deployments, not best-effort.
JetStream adds an optional persistence layer on top of NATS. It stores messages in durable streams and tracks consumer state. A durable consumer is a named subscription bound to a stream and a consumer configuration. The broker tracks the acknowledged sequence per consumer name, so a client can reconnect after a crash or redeploy and resume from the last acked message.
What a durable consumer gives you
Delivery semantics are controlled by ack policy and max deliveries. With AckExplicit and manual ack, unacknowledged messages are redelivered after an ack wait timeout or on NAK. That provides at-least-once processing without requiring exactly-once guarantees from the broker.
The key properties are:
- Named durability: the consumer name is stable across connections. A new connection with the same name reuses the same state.
- Replay from last ack: the broker remembers the last delivered and acknowledged sequence numbers.
- Competing consumers: multiple ephemeral consumers can share a stream for horizontal scaling, while a fixed durable name gives one logical worker.
Durable consumers do not give exactly-once. Idempotent processing and deduplication remain application responsibilities.
A practical pattern
Publish events to a subject hierarchy like orders.created, orders.paid. Define a JetStream stream that captures the hierarchy, then create a durable consumer with manual ack.
Stream definition conceptually:
streams add ORDERS
subjects ["orders.>"]
retention limits
storage file
Consumer definition conceptually:
consumers add ORDERS
durable "order-processor-1"
ack_policy explicit
ack_wait 30s
max_deliver 5
Application connect logic:
# connect to nats://localhost:4222 with JetStream context
# get stream ORDERS, create or get durable consumer named order-processor-1
# fetch next message, process, then ack or nakRun this on a single deployment instance with a fixed durable name. On restart the same name resumes from the last acked sequence. For scaling, use distinct durable names per instance or use an ephemeral consumer group with work queue semantics.
Trade-offs and operational limits
Persistence adds storage and I/O. JetStream is slower than core NATS and uses disk. You must design stream retention, storage limits, and consumer backpressure for the workload.
JetStream APIs and configuration options are version-sensitive across NATS Server releases. Behavior of ack wait, max deliveries, and consumer limits can differ between major versions. Check server documentation for the version you run.
Durability adds operational complexity. Stream retention policies control how long messages are kept. Unbounded retention grows disk usage. Consumer state is stored with the stream and must be monitored.
A practical way to check the result without claiming a test:
- Start a NATS server with JetStream enabled and create a stream with subjects
orders.>and retention limits. - Publish a small batch of messages.
- Create a durable consumer with AckExplicit and a fixed durable name, then stop the consumer before acking.
- Reconnect with the same durable name and observe that the broker offers the same messages again. Monitor consumer state via server monitoring to confirm last delivered and acknowledged sequence numbers resume correctly.
Use durable consumers when you need reliable, replayable delivery and can tolerate at-least-once semantics with idempotent handlers. If you need lower latency and can accept loss, core NATS remains the right choice.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.