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

Spring scheduled retries: leave failed work pending until its due time

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

A failed transport attempt can remain PENDING while its next_due timestamp prevents immediate reprocessing.

Download Spring source kit

One fake failure, then success

The scheduled fixture gives EVENT-42 a fake transport that throws once. The poller records one failure, clears the claim and sets next_due 80 milliseconds after that failure. A later scheduled tick claims the same event ID, calls the local consumer and acknowledges the row. The test waits for delivery, then checks two transport calls, one failure record and stock changing from 10 to 8 once.

The separate logical-clock test checks exact due-time boundaries and a capped delay. This scheduled test checks that Spring runs a callback again after a real short wait. It does not prove the scheduler runs exactly at the due timestamp; process load can delay it.

Classify before retrying

The fixture treats every RuntimeException as retryable. A deployed adapter must separate temporary transport trouble from invalid payloads, permanent authorization failure and exhausted budgets. Add a maximum elapsed time, a parked state and alerting. Retry with the same event ID because a lost response may hide a committed consumer mutation.

The consumer in the fixture writes a unique event marker with stock. The lost-acknowledgement check shows why that marker matters even when a transport call appears to finish. Do not use sleep inside the callback to delay a retry; persist the due time and release the scheduler thread.

Checked source

Java
catch (RuntimeException transportFailure) {
    jdbc.update("update scheduled_outbox set failures = failures + 1, next_due = ?, claim_token = null, lease_until = null where event_id = ? and claim_token = ?", now + 80, eventId, token);
}

Verification boundary

ScheduledOutboxPollerTest.failedTransportWaitsThenRetriesSameEvent. The excerpt is shortened or a labelled design sketch; the source kit contains the complete checked fixture.

Costs and limits

The excerpt shortens the checked SQL lease predicate. The test uses a fake exception and a short wall-clock delay; there is no external transport, jitter, error taxonomy or load test.

Common Mistakes

  • Do not retry a permanent rejection forever.
  • Do not sleep while holding the polling thread.
  • Do not create a new event ID for the same retry.

Read next

Spring TaskScheduler: poll committed outbox rows in the background, Spring outbox retries: due time, delay cap, and attempt accounting, Spring outbox retry budget: park a poison event for inspection, Spring scheduled relay: recover when the consumer commits before acknowledgement.

spring
spring-boot
scheduled-relay
Storage details