Releases: leonlee/outbox
v0.9.2
Full Changelog: v0.9.1...v0.9.2
v0.9.1
Bug Fixes
- Timestamp truncation in OutboxPoller:
Instant.now()is now truncated to millisecond precision before passing topollPending/claimPending, preventing mismatches with millis-precision stored timestamps - Longest-prefix matching in JdbcOutboxStores:
detect()now picks the longest matching JDBC URL prefix instead of the first match, ensuring the most specific store wins - INTEGRATIONS.md schema: Fixed incorrect column list and status enum to match actual
outbox_eventtable
Robustness Improvements
- Deterministic ORDER BY: Added
event_idtie-breaker to all 10ORDER BY created_atSQL clauses across stores and purgers, ensuring consistent ordering for events sharing the same millisecond timestamp - MySqlOutboxStore claim locking: Rewrote
claimPending()to useSELECT ... FOR UPDATE SKIP LOCKEDfor atomic row-level locking (MySQL 8.0+), replacing the non-atomicUPDATE...ORDER BY...LIMITapproach - MySqlOutboxStore IN clause chunking: Phase 2 UPDATE now chunks the
IN (...)clause in batches of 500 to stay within MySQL parameter limits - Index-friendly purge SQL: Replaced
COALESCE(done_at, created_at)with an OR pattern (done_at < ? OR (done_at IS NULL AND created_at < ?)) that allows the database to use indexes ondone_atandcreated_at
Dependencies
central-publishing-maven-plugin0.6.0 → 0.10.0ulid-creator5.2.3 → 5.2.4
Full Changelog: v0.9.0...v0.9.1
v0.9.0
Full Changelog: v0.8.4...v0.9.0
v0.8.4
What's Changed
Bug Fixes
withConnectioncatches RuntimeException —OutboxDispatcher.withConnection()now catchesSQLException | RuntimeExceptioninstead of justSQLException. Previously, aRuntimeExceptionfrom a store method (e.g.OutboxStoreException) could propagate intodispatchEvent's catch block, causinghandleFailureto run on a successfully-processed event.OutboxPoller.markDeadcatches RuntimeException — Same pattern fix as above, broadened catch toSQLException | RuntimeException.WriterOnlyBuilder.build()passes metrics — Previously passednullfor metrics to theOutboxconstructor, soOutbox.close()never calledmetrics.close()— leaking Micrometer meters. Auto-config now also wires metrics inWRITER_ONLYmode.convertToEnvelopereconstructsavailableAt— Addedavailable_atto all SELECT queries (pollPending,selectClaimed,queryDead, PostgreSQLRETURNING), addedavailableAtfield toOutboxEventrecord, and set it on the reconstructedEventEnvelope.DefaultJsonCodecvalidates lone surrogates — Added surrogate pair validation: high surrogates must be followed by\uDC00-\uDFFF, lone low surrogates are rejected.DeadEventManagerpropagates errors — Changed from swallowing exceptions (returningList.of()/0/false) to wrappingSQLExceptioninRuntimeException. Callers can now distinguish database failures from empty results.
Improvements
WriterOnlyBuilderrejects irrelevant config — 8 methods (listenerRegistry,jsonCodec,interceptor,interceptors,intervalMs,batchSize,skipRecent,drainTimeoutMs) now throwUnsupportedOperationExceptioninstead of silently accepting values that are ignored at build time.metrics()is intentionally kept for lifecycle management.- Added
spring-boot-configuration-processor— The starter module now generatesspring-configuration-metadata.jsonfor IDE auto-completion ofoutbox.*properties.
Documentation
- Added
CODE_REVIEW.mdwith full code review findings and verified-correct areas.
Full Changelog: v0.8.3...v0.8.4
v0.8.3
Full Changelog: v0.8.2...v0.8.3
v0.8.2
Handler-Controlled Retry Timing
This release adds two complementary mechanisms for handlers to control retry timing, giving listeners fine-grained control over when events are re-delivered.
DispatchResult (sealed interface)
EventListener gains a new handleEvent() default method that returns a DispatchResult:
DispatchResult.done()— event processed successfully (same as before)DispatchResult.retryAfter(Duration)— defer re-delivery without counting againstmaxAttempts. The event is reset to PENDING with a futureavailable_at. Useful for polling external systems or respecting rate-limit headers.
Existing onEvent() listeners continue to work unchanged — the default handleEvent() delegates to onEvent() and returns Done.
RetryAfterException
Throw RetryAfterException when a transient failure occurs and the handler knows the appropriate retry delay (e.g., from an HTTP Retry-After header). Unlike DispatchResult.RetryAfter, this does count against maxAttempts.
Comparison
| Mechanism | Counts against maxAttempts | Delay source | Use case |
|---|---|---|---|
DispatchResult.RetryAfter |
No | Handler-specified | Polling, waiting for preconditions |
RetryAfterException |
Yes | Handler-specified | Transient failure with known retry delay |
| Other exception | Yes | RetryPolicy |
Unexpected failure |
Other Changes
OutboxStore.markDeferred()— new SPI method for deferred retry without incrementing attemptsMetricsExporter.incrementDispatchDeferred()— new metric for tracking deferred events- Documentation updated across SPEC.md, TUTORIAL.md, and README.md
Full Changelog: v0.8.1...v0.8.2
v0.8.1
What's New
- Spring Boot Starter demo sample — new
samples/outbox-spring-boot-starter-demoapp demonstrating zero-config auto-configuration with@OutboxListenerannotation. Compare withoutbox-spring-demowhich requires manualOutboxConfigurationwith 7+ bean definitions.
Dependency Updates
- build(deps): bump actions/upload-artifact from 4 to 6 by @dependabot[bot] in #35
- build(deps): bump org.postgresql:postgresql from 42.7.3 to 42.7.10 by @dependabot[bot] in #36
- build(deps-dev): bump org.apache.maven.plugins:maven-shade-plugin from 3.5.1 to 3.6.1 by @dependabot[bot] in #39
- build(deps): bump org.codehaus.mojo:exec-maven-plugin from 3.1.0 to 3.6.3 by @dependabot[bot] in #40
- build(deps): bump com.mysql:mysql-connector-j from 8.3.0 to 9.6.0 by @dependabot[bot] in #38
Full Changelog: v0.8.0...v0.8.1
v0.8.0
Full Changelog: v0.7.2...v0.8.0
v0.7.2
Full Changelog: v0.7.1...v0.7.2
v0.7.1
Full Changelog: v0.6.0...v0.7.1