A retry budget moves repeatedly failed work into a state that automatic claims will no longer select.
Spring outbox retry budget: park a poison event for inspection
Stop at the boundary
The fixture uses a three-failure budget. Each claimed attempt fails with its current token. On the third failure, the row becomes DEAD; a later claim fails even after its due time. The stock balance remains unchanged because the consumer was never called. The row stays available for an operator to inspect rather than disappearing.
DEAD is only a local state in the model. It is not a configured broker dead-letter topic. A production operator workflow should expose the event ID, safe error category, attempt history, age, and a controlled replay action. Do not put raw credentials or personal payloads in those diagnostics.
Replay needs a rule
If a schema bug caused the failures, a code fix may make replay safe; if the consumer partially applied a side effect, replay requires a stable deduplication key. Decide whether the original event ID remains stable and whether the attempt counter resets. Record who released the event and why.
A single global attempt count may treat a brief outage like a poison payload. Pair attempts with a total elapsed budget and error classification when the actual transport exists. The retry lesson states what this local clock check covers.
Checked source
status = nextFailureCount >= 3 ? "DEAD" : "PENDING";
// The claim query selects only PENDING rows.Verification boundary
OutboxRelayFlowTest has seven local tests in the downloadable Spring source kit. The excerpt is shortened; the kit contains the complete test.
Costs and limits
The test checks one row and three sequential failures. It does not provide a dead-letter queue, retention policy, alerting, manual replay API, or broker failure classification.
Common Mistakes
- Do not silently discard exhausted events.
- Do not auto-replay a parked event with a new identity.
- Do not leak sensitive payloads into error dashboards.
Read next
Spring outbox retries: due time, delay cap, and attempt accounting, Spring consumer idempotency: reserve an event ID with the stock mutation, Spring relay failure trace: inspect persisted state at each cut point, Spring receipt outbox command: record intent without claiming delivery.
Related boundary
Spring AMQP poison messages: reject, requeue and dead-letter are different decisions
