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

Spring receipt outbox command: record intent without claiming delivery

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

A receipt outbox row records work to deliver after its business transaction commits.

Download Spring source kit

Commit intent with the receipt

The checked write inserts the outbox row after the versioned state update and before the replay record. TransactionTemplate commits the three operations together. A forced failure after the outbox insert rolls back both the event and the receipt update. That is the useful guarantee: no committed event for an uncommitted state in this one database transaction.

The event ID includes tenant, receipt and request key in the local fixture; it is stable across a retry of the same command. The lease lesson covers a later worker claim, and the consumer lesson covers duplicate handling. Neither is wired into this web context. The outbox row starts as a database fact, not a message delivered to a broker.

Define the event contract

A real event needs a versioned schema, tenant-safe identifiers, ordering rules and a retention policy. Decide whether one receipt requires ordered delivery and how a consumer recognizes old or duplicate versions. Do not place an HTTP call inside the transaction to simulate atomic publication. That only holds a database connection while another system can fail independently.

Checked source

Java
transaction.execute(status -> {
    updateReceipt(trustedTenant, receiptId, expectedVersion, nextState);
    jdbc.update("insert into receipt_outbox values (?, ?, ?, ?)",
        eventId, trustedTenant, receiptId, nextState);
    saveReplay(trustedTenant, requestKey, responseVersion);
    return responseVersion;
});

Verification boundary

TenantReceiptCommandFlowTest.authorizedWriteCommitsReceiptOutboxAndReplayRecord and TenantReceiptCommandFlowTest.failureAfterOutboxInsertRollsBackAllThreeTables in the downloadable Spring source kit. The excerpt is shortened; the kit contains the complete test.

Costs and limits

The test has no relay, broker, external consumer, crash between claim and acknowledgement, or concurrent first command. Row storage and later polling add write and read load; capacity planning belongs to the target database and worker workload.

Common Mistakes

  • Do not call an inserted outbox row a delivered event.
  • Do not do remote I/O inside the database transaction.
  • Do not discard tenant identity from the event contract.

Read next

Spring tenant command transaction: keep state, event and replay record together, Spring transactional outbox: commit a receipt and event row together, Spring outbox claims: allow recovery after a worker lease expires, Spring consumer deduplication: commit the event ID with the mutation.

Continue with checked relay behavior

Continue with Spring outbox relay: claim, deliver, and acknowledge one event.

spring
spring-boot
receipt-outbox-command
Storage details