All notable changes to this project are documented in this file.
The format is based on Keep a Changelog,
and this project adheres to Semantic Versioning.
Until 1.0.0, breaking changes may appear in any release and are flagged with BREAKING below.
ExposedConnectionProvidernow requires adatabase: Databaseconstructor argument and reads the active transaction fromdatabase.transactionManager.currentOrNull()instead of the globalTransactionManager.currentOrNull(). Previously, in a multi-database Exposed app, the provider would silently return whichever transaction happened to be innermost-active on the calling thread — regardless of whichDatabaseit belonged to — since everyOutboxStore/OutboxPublisheroperation routes throughwithConnection. Construct one instance perDatabase, matchingExposedTransactionRunnerandExposedTransactionContextValidator. (#96)
1.0.0 — 2026-07-28
First stable release. The public API now follows semantic versioning — breaking changes will only ship in a new major version.
OutboxSchedulerConfig.concurrency— fans out each scheduler tick to N parallel workers via a configurableworkerExecutorFactory(fixed platform-thread pool by default; a virtual-thread factory is also provided). Each worker claims its own disjoint batch viaFOR UPDATE SKIP LOCKED, so no app-level coordination is needed, and ticks never overlap.concurrency = 1(default) preserves the original single-worker, zero-overhead behavior. Benchmarked at 3.6×–6.6× throughput scaling (concurrency 4→64); virtual threads showed no advantage over platform threads in that range. Wired intookapi-spring-bootviaokapi.processor.concurrency. (#73)HttpMessageDeliverer.deliverBatchnow fires all requests concurrently viaHttpClient.sendAsync()instead of blocking sequentially onHttpClient.send()per entry — 5×–15× throughput improvement depending on batch size and webhook latency. (#77)KafkaMessageDeliverer.deliverBatch— fire-flush-await pattern replaces N sequential blockingproducer.send().get()round-trips with one batchedproducer.flush()— 13×–41× throughput improvement. (#40)OutboxStore.updateAfterProcessingBatch(entries)— batches a processed batch's DB write into a single JDBCexecuteBatch()call instead of N individualupdateAfterProcessing()round-trips (~10× faster in isolation). Default implementation loops the existing per-entry method, so customOutboxStoreimplementations keep working unmodified;PostgresOutboxStore/MysqlOutboxStoreoverride it. (#71)
okapi-spring-bootstartup crash with 2+PlatformTransactionManagerbeans.OkapiMicrometerAutoConfigurationnow honoursokapi.transaction-manager-qualifierand falls back gracefully (metrics run without a read-only snapshot transaction) instead of throwingNoUniqueBeanDefinitionException, matching howOutboxAutoConfigurationalready resolved the PTM. (#81, #80)OutboxProcessorconstructor missing@JvmOverloads— Java callers could no longer omit thelistener/clockparameters and had to pass all four explicitly; restored. (#75, #74)
0.3.0 — 2026-06-08
- Domain table renamed
outbox→okapi_outbox(indexesidx_outbox_*→idx_okapi_outbox_*). okapi now owns a prefixed table, so a pre-existingoutboxno longer collides. Not configurable. (#37) - Liquibase tracking tables default to
okapi_databasechangelog/okapi_databasechangeloglockinstead of sharing the app's defaults; override via the new properties to keep the old layout. (#37) - okapi's Liquibase migrations consolidated into a single
001__create_okapi_outbox_table.sqlper database. Resulting schema is unchanged, but theoutbox:001checksum changed — upgraders must start on a fresh okapi schema or clear okapi's rows fromokapi_databasechangelog. (#50) OutboxScheduler/OutboxPurger(okapi-core) now require a non-nullTransactionRunner. The old nullable default silently ran non-transactionally, lettingFOR UPDATE SKIP LOCKEDdrop its lock under JDBC auto-commit and deliver entries more than once. Spring Boot users unaffected; direct users (Ktor, manual wiring, Java/Kotlin) must supply one. (#51)OutboxProcessorScheduler/OutboxPurgerSchedulernow require a non-nullTransactionRunner(was a nullableTransactionTemplate?). Spring autoconfig derives it from anyPlatformTransactionManager; direct constructor users must passSpringTransactionRunner(template)or a thin wrapper. (#49)PostgresOutboxStore/MysqlOutboxStoreno longer take aclockparameter — it became unused after the lag-gauge fix (#58). Drop the second constructor argument; Spring Boot users unaffected. (#59)okapi-spring-bootautoconfig fails fast when it cannot verify the PlatformTransactionManager↔outbox-DataSource binding in a multi-DataSource context with nookapi.transaction-manager-qualifierset. Name the PTM via that qualifier, or supply an explicit@Bean TransactionRunnerto bypass. (#49)
MessageDeliverer.deliverBatch(entries)— batch-aware delivery method with a sequential default impl (loopsdeliver(), preserving order and per-entry result classification). Existing deliverers need no change; transports can override it for concurrent I/O, andCompositeMessageDelivererroutes batches by delivery type. (#35)okapi.liquibase.changelog-table/okapi.liquibase.changelog-lock-table— Spring Boot properties to override okapi's Liquibase tracking-table names (defaultsokapi_databasechangelog/okapi_databasechangeloglock).
- HTTP delivery exception classification.
HttpMessageDelivererpreviously caught every exception asRetriableFailure, so corrupt delivery metadata or an unknown service wasted the whole retry budget before being markedFAILEDinstead of failing fast.JsonProcessingExceptionand other non-IO errors (malformed URI, unknown service) are nowPermanentFailure;IOException/InterruptedExceptionstay retriable. (#44) okapi.transaction-manager-qualifieris now honoured even whenTransactionAutoConfigurationregisters a uniqueTransactionTemplate. Previously the qualifier was silently ignored in multi-PTM setups, defaulting to the @Primary PTM. Rule is now: explicit qualifier > auto-wired TT. (#49)okapi-kafkanow exposeskafka-clientsandokapi-coreasapidependencies.KafkaMessageDeliverer's public constructor takesProducer<String, String>, so those types belong on the consumer's compile classpath transitively — no more addingkafka-clientsby hand or hitting surprisingokapi-coreclasspath failures. (#47)- Startup
NoClassDefFoundErroron Spring Boot 3.5.x withoutliquibase-core(e.g. Flyway-only apps) — okapi's Liquibase beans are now guarded by class-level@ConditionalOnClass(SpringLiquibase). Also stops okapi'sSpringLiquibasebean from shadowing the host application's own changelog — okapi's auto-config is now ordered after Spring Boot'sLiquibaseAutoConfiguration. (#42, #38) okapi-micrometerauto-config ordering on Spring Boot 3.5.x.@AutoConfigureAfternow lists both the 3.5.x and 4.0.x metrics-package locations, so the listener / metrics / refresher are no longer silently skipped whenMeterRegistryregisters later. (#41)OutboxPurgererror log preserves partial-batch progress. A mid-loop failure now reports how many entries / batches were already purged this tick, so operators can tell an early outage from a late transient hiccup. (#55)
Existing deployments upgrading directly from 0.2.x to 1.0.0 must complete the steps in UPGRADING.md before starting the new version.
0.2.0 — 2026-04-29
- Observability:
OutboxProcessorListenerAPI and theokapi-micrometermodule (counters, timers, gauges; Spring Boot Actuator integration). (#27) - Multi-datasource transaction validation in
okapi-spring-boot(SpringTransactionContextValidator,okapi.datasource-qualifierproperty). (#17) @JvmOverloads/@JvmStaticannotations across the public API for Java interop. (#24)- Maven Central release pipeline. (#18)
OutboxStoremigrated from JetBrains Exposed to plain JDBC inokapi-postgresandokapi-mysql. The Exposed-based path remains available via the optionalokapi-exposedmodule. (#26)- Configuration unification:
Durationtypes throughout, dedicatedOutboxPurgerConfigandOutboxSchedulerConfig. (#16) OutboxProcessorSchedulerandOutboxPurgerv2 — configurable interval, batch size, retention; reliable shutdown viaSmartLifecycle. (#11, #14)
- Actionable error message in
ExposedConnectionProviderwhen no transaction is bound to the current thread. (#32) okapi-micrometerartifact published to Maven Central; theokapi.metrics.refresh-intervalproperty documented. (#29)
0.1.0 — 2026-04-07
Initial public release.
- Transactional outbox pattern for Kotlin/JVM with PostgreSQL and MySQL stores.
okapi-httpandokapi-kafkadeliverers; pluggableMessageDelivererAPI.OutboxProcessorwith configurableRetryPolicyand delivery-result classification (Success/RetriableFailure/PermanentFailure).okapi-spring-bootautoconfiguration for stores, transports, scheduler, and purger.okapi-exposedintegration (transaction runner, connection provider, validator).- Concurrent processing via
FOR UPDATE SKIP LOCKED.