Acknowledge What Was Handled
Table of Contents
+#+updated: 2026-10-14 +#+pattern_group: nats
This page defines the NATS pattern Acknowledge What Was Handled in the nats pattern language. A durable subscription acknowledges a message only once the handler has finished the work, and every exit from the handler answers the message — ack, nak or term — rather than relying on an implicit acknowledgement.
1. Also known as
- NATS: explicit acknowledgement, `AckPolicy=explicit`.
- The pattern language: Idempotent Receiver is the property this pattern preserves.
2. Context
A durable consumer holds a message until its reader acknowledges it. The server's default is to acknowledge when the handler returns, whatever the handler decided.
3. Problem
How does a durable consumer make the acknowledgement mean "the work is done", rather than "the handler returned"?
4. Forces
- The server cannot know whether the work succeeded; only the handler can.
- A handler that fails still returns normally, unless something stops it.
- A message left unacknowledged is redelivered, which is the desired outcome of a failure and the wrong outcome of a success.
- A message that can never be handled must not be redelivered forever.
5. Solution
Two things together: the subscription is created with an explicit acknowledgement policy, and the handler states the outcome on every path. A guard tied to the message makes the statement impossible to forget:
// Created with an explicit ack policy and an attempt bound. nats.js_queue_subscribe(subject, durable, queue_group, handler, stream_name); // In the handler: the guard holds the ack until the work is committed. void provision_step(ores::nats::message msg) { nats_ack_guard ack(msg); // answers nak if nothing else answers auto req = decode<provision_step_request>(msg); if (!req) { ack.reject(); // term: never deliverable return; } if (!try_provision(*req)) { ack.retry(); // nak: deliver again later return; } ack.accept(); // ack: the work is committed }
The four answers map to four outcomes, and the guard's destructor covers the path that forgets:
| Outcome | Answer | Meaning |
|---|---|---|
| The work is committed | ack | Delivered no more |
| A transient failure | nak | Delivered again, after a delay |
| Not deliverable ever (malformed, unknown kind) | term | Dropped; recorded as such |
| The handler threw, or returned without deciding | nak | The safe default: assume not done |
Two further rules complete the pattern:
- The acknowledgement follows the commit, never precedes it. Acknowledging before the write turns a crash between the two into lost work.
- The attempt count is bounded. A message that always fails must stop being delivered; an unbounded redelivery of a poison message is a crash loop.
6. Failure handling
| Concern | What the pattern does |
|---|---|
| Errors | A failed message is nak'd and redelivered rather than acknowledged as done. |
| Timeouts | AckWait bounds how long a handler may hold a message before the server assumes it died. |
| Retries | Redelivery is the retry; the bound decides when it ends. |
| Idempotency | Required, because a nak causes a second delivery; the handler must be safe to run twice. |
| Observability | Each answer is logged with the subject and the correlation id, so a redelivered message can be followed. |
7. Pitfalls
- Relying on the implicit acknowledgement. A handler that catches its own exception and returns acknowledges work it never did.
- Acknowledging on the error path. The failure is recorded as success and the message is gone.
- No attempt bound. A poison message is redelivered forever, restarting the consumer each time.
- A hostile message acknowledged instead of discarded. A malformed payload is a `term`, not an ack and not an endless nak.
- A long job with no progress signal. The job is redelivered while it still runs; a message that takes longer than AckWait must extend its own deadline.
8. Security
A malformed or unauthenticated message must never be acknowledged as handled. It is rejected with a named code, and the refusal is logged; see Report a Refusal Off the Body.
9. Resulting context
- Benefits
- The acknowledgement becomes a statement about the work, so at-least-once delivery is real rather than nominal.
- Liabilities
- The handler must now decide explicitly, and a wrong `ack` still loses work — the guard can default safely but cannot judge.
10. In ORE Studio
`ores.nats` creates JetStream subscriptions through `js_queue_subscribe()`, and the acknowledgement policy and attempt bound are part of the options that call takes. The guard lives beside the transport so that a handler written without one is visible as an omission.
11. Related patterns
- Durable Subscription — the consumer this pattern answers.
- Idempotent Message Identity — why a redelivery is safe to repeat.
- Never Let an Exception Cross the Bus — what happens to a handler that throws.
- At Least Once Prerequisites — the claim or idempotent write an answer commits.
12. Sources
- NATS documentation, Ack responses and redelivery — the four answers, and the poison-message path.
- NATS documentation, Delivery and acknowledgment.
13. See also
- NATS service patterns — the structure note that orders this pattern language, and where to read this page in it.