Choosing Between Core NATS and JetStream for Message Delivery
Deciding between Core NATS and JetStream depends on your tolerance for data loss. This guide compares fire-and-forget messaging versus durable streams with a practical validation example.
12 Jul 2025, 16:03 UTC

The Delivery Guarantee Dilemma
When architecting a system with NATS, the primary technical decision is whether to use Core NATS or JetStream. The choice depends entirely on whether your application can tolerate data loss during a subscriber outage. If a message is sent and no one is listening, Core NATS drops it; JetStream stores it.
The core trade-off is between latency/simplicity and reliability/persistence. Selecting the wrong pattern leads to either unnecessary disk I/O overhead or critical data gaps during network partitions.
Comparison of Messaging Patterns
| Feature | Core NATS | JetStream |
|---|---|---|
| Delivery Model | At-most-once (Fire-and-forget) | At-least-once / Exactly-once |
| Persistence | Memory only (Transient) | Disk or Memory (Persistent) |
| Subscriber State | Must be online to receive | Can replay historical data |
| Resource Cost | Ultra-low CPU/RAM | Higher (Disk I/O, Storage) |
| Primary Use Case | Real-time telemetry, Signaling | Event sourcing, Task queues |
Evaluating Trade-offs
Core NATS: The Low-Latency Path
Core NATS operates as a lightweight message bus. It does not track who received what. This makes it ideal for high-frequency data—such as stock tickers or system heartbeats—where a missed update is irrelevant because a newer update will arrive milliseconds later.
JetStream: The Durable Path
JetStream adds a persistence layer to NATS. It captures messages in a Stream (a logical group of messages) and allows Consumers to track their progress via acknowledgments (ACKs). This is essential for financial transactions or order processing where every message must be handled.
Implementation: Validating Persistence with JetStream
To implement a durable pattern, the NATS server must be started with JetStream enabled. If using the binary, use the -js flag.
1. Create a Durable Stream
Run the following command using the NATS CLI to define a stream that captures all messages on the ORDERS. > subject hierarchy. This ensures messages are written to disk before being delivered.
# Run on a machine with NATS CLI installed
# Permissions: Requires administrative access to the NATS cluster
nats stream add ORDERS --subjects "ORDERS.*" --storage file --retention limits
2. Publish and Verify Persistence
To verify that JetStream is working, publish a message while no subscribers are active. In Core NATS, this message would vanish. In JetStream, it is stored.
# Publish a test message
nats pub ORDERS.new "Order #1234"
3. Consume the Stored Data
Now, create a Pull Consumer. Pull consumers are preferred for high-throughput engineering because they allow the client to control the flow of data (batching) rather than being overwhelmed by a server push.
# Create a consumer and pull the message
nats consumer add ORDERS MY_CONSUMER --pull
nats consumer next ORDERS MY_CONSUMER
Expected Result: The command nats consumer next should return "Order #1234", proving the message survived the period where no subscriber was connected.
Operational Constraints and Risks
- Disk I/O Bottlenecks: JetStream's performance is bound by your disk speed. For high-throughput streams, use NVMe storage or configure
--storage memoryif persistence across server restarts isn't required. - Replication Factors: In a clustered environment, a replication factor (R) of 3 is common. However, if you lose more nodes than the quorum allows, the stream becomes unavailable for writes.
- Exactly-Once Processing: While JetStream supports exactly-once delivery, this requires the producer to send a unique
Nats-Msg-Id. The server uses this ID to deduplicate messages within a configured window.
Verification Checklist
To confirm your decision aligns with the implementation, check the following:
- Latency Check: If the added millisecond overhead of disk writes violates your SLA, revert to Core NATS.
- Recovery Check: Stop your subscriber service, publish 10 messages, restart the service, and verify all 10 messages are processed. If messages are missing, your consumer is likely configured as ephemeral rather than durable.
Rollback Procedure
If JetStream is causing excessive disk pressure or latency, you can migrate back to Core NATS by deleting the stream. Warning: This permanently deletes all stored messages.
# Remove the stream to stop persistence and return to Core NATS behavior
nats stream rm ORDERS0 replies
A thoughtful contribution can make all the difference. Be the first to share one.