An outbox claim changes a pending record to claimed only if it is still pending, so a second claimant can observe that it lost the state transition.
Spring outbox claims: move pending work once per database state
Use the affected-row count
The H2 fixture changes E-41 from pending to claimed with a guarded UPDATE. The first call changes one row; repeating it changes zero. Treat zero as a lost claim, not as a successful delivery. The claim belongs after the atomic business write and before any external send.
One UPDATE is a useful local predicate, but a production worker needs a lease or another recovery rule. If a process dies after claiming and before sending, a permanent claimed flag strands the event. If the send succeeds and acknowledgement fails, retry can duplicate delivery. Consumers must tolerate repeated event IDs.
Make ownership recoverable
A durable design stores a claim token, deadline, attempt count and last failure under a target database transaction. Release or renew only when the token still matches. Do not let an expired worker complete a row owned by a newer worker. The source kit checks the simple state transition only; multi-worker and crash behavior remain untested.
Checked source
int claimed = jdbc.update(
"update outbox set state = 'claimed' where id = ? and state = 'pending'", "E-41");
if (claimed != 1) throw new IllegalStateException("claim lost");Verification boundary
OutboxWriteContractTest.receiptAndOutboxRowCommitOrRollBackTogether runs in the downloadable Spring source kit. The excerpt leaves out surrounding setup and imports; the kit contains the complete test.
Costs and limits
A primary-key guarded update is usually indexed, but lock waits and database isolation can dominate elapsed time. This lesson verifies one H2 transition, not concurrent worker exclusion, lease expiry or exactly-once delivery.
Common Mistakes
- Do not infer exactly-once delivery from one successful UPDATE.
- Do not leave claimed rows without a crash-recovery path.
- Do not let an old worker acknowledge a new owner's claim.
Read next
Spring transactional outbox: commit a receipt and event row together, Spring transaction propagation: joined rollback and independent commit, Spring transaction events: run a listener after commit without claiming durability.
Continue with checked worker recovery
Continue with Spring outbox claims: allow recovery after a worker lease expires, Outbox acknowledgements: require the current lease token.
