Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Spring outbox relay: claim, deliver, and acknowledge one event

Last updated: 30 Sept 20264 min read
tutorial
IntermediateBy AITrove Editorial

An outbox relay moves a committed event through pending, claimed, and delivered states without folding a remote side effect into the producer transaction.

Download Spring source kit

The checked sequence

The fixture commits a shipment request and one outbox event together. A worker then claims the event with a token and lease, calls a separate consumer transaction, and acknowledges the event. The stock balance changes from 10 to 7 once. The delivered row cannot be claimed again. This sequence is invoked by a test, not a running background service.

The producer write proves one local transaction. The claim prevents a second sequential worker from taking an unexpired lease. The consumer ledger handles a repeat delivery. None of those steps makes a network publish atomic with a database commit.

State transitions have owners

PENDING is durable work waiting for a poller. DELIVERED means this model received a successful consumer result and completed a token-checked acknowledgement; it is not proof that an external broker persisted an event. A crash after consumer commit but before acknowledgement leaves PENDING work for another claim. The consumer must accept the same event ID without repeating the business mutation.

Keep the outbox row long enough for support and audit, then define retention separately. An index on status, next_attempt_at, and lease_until would matter at scale; this tiny H2 fixture does not measure a polling query plan.

Checked source

Java
accept("EVENT-21", "SKU-17", 3, false);
claim("EVENT-21", "worker-A", 100, 50);
consume("EVENT-21");
acknowledge("EVENT-21", "worker-A", 110);

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 JUnit test calls each stage in one process with a logical clock. It has no broker, scheduler thread, crash injector, or cross-node clock agreement. Delivery semantics across a real network need a separate integration test.

Common Mistakes

  • Do not mark an event delivered before checking the downstream result.
  • Do not treat an outbox insert as a completed publish.
  • Do not delete pending rows because a worker process restarted.

Read next

Spring transactional outbox: commit a receipt and event row together, Spring outbox claims: lease expiry and token-fenced acknowledgement, Spring consumer idempotency: reserve an event ID with the stock mutation, Spring relay failure trace: inspect persisted state at each cut point.

Continue with scheduled relay checks

Continue with Spring TaskScheduler: poll committed outbox rows in the background.

spring
spring-boot
outbox
Storage details