A delivery trace tests the state left behind at producer failure, retry, lease expiry, duplicate delivery, and consumer failure.
Spring relay failure trace: inspect persisted state at each cut point
Read the row, not the exception alone
OutboxRelayFlowTest has seven deterministic checks. Producer rollback leaves zero request and outbox rows. A successful delivery leaves stock at 7 and the outbox row DELIVERED. A duplicate event changes stock only once. An unknown SKU rolls back the applied_event insert. Retry attempts remain unclaimable until due, and a third failure moves the row to DEAD.
At lease expiry, the old worker cannot acknowledge. A replacement token can. These assertions tie each branch to persisted state, which is more useful than testing only that a callback threw. The lease, retry, and consumer lessons explain the separate invariants.
Name the missing cut points
The test does not kill a process after a broker confirms publication but before the outbox acknowledgement. It also does not run two workers concurrently against the target database, start a scheduler, inject transport timeouts, or verify duplicate delivery after restart. Those checks need a real broker and database in a separate integration environment.
The logical clock prevents flaky sleeps and makes due times exact. It cannot reveal scheduler lag, clock skew between hosts, or throughput under load. Keep those claims out of a local unit-scale report.
Checked source
mvn -q -Dtest=OutboxRelayFlowTest test
# Run the full source-kit suite before publishing an updated ZIP.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 suite is reproducible locally with H2 and Spring JDBC. It cannot certify delivery behavior of a deployed queue, load balancer, or database cluster.
Common Mistakes
- Do not report a local state-machine test as an end-to-end broker test.
- Do not omit row-state assertions after a simulated failure.
- Do not use wall-clock sleeps for exact retry boundaries.
Read next
Spring outbox relay: claim, deliver, and acknowledge one event, Spring outbox retries: due time, delay cap, and attempt accounting, Spring consumer idempotency: reserve an event ID with the stock mutation, Spring tests: separate business rules, wiring and transport.
Continue with scheduled relay checks
Continue with Spring exercise: trace two poll ticks and a lost acknowledgement.
