The KDoc states twice that Postgres wins when both store modules are on the classpath:
OutboxAutoConfiguration.kt:54 — "If both are present, Postgres takes priority. Override by defining your own @Bean OutboxStore."
OutboxAutoConfiguration.kt:249 — "When both Postgres and MySQL modules are on the classpath, [PostgresStoreConfiguration] takes priority."
In practice MySQL wins.
Observed
A Spring Boot consumer with okapi-postgres and okapi-mysql on the classpath, running against a PostgreSQL database:
bean='outboxStore' type=com.softwaremill.okapi.mysql.MysqlOutboxStore
SpringLiquibase : beans=1 names=[okapiMysqlLiquibase]
The context starts and the MySQL changelog is applied to the Postgres database (its DDL happens to be accepted), producing the MySQL table shape — id character(36), delivery_metadata json — instead of the Postgres one. Then every processor tick fails:
ERROR c.s.okapi.core.OutboxScheduler : Outbox processor tick failed, will retry at next scheduled interval
Caused by: org.postgresql.util.PSQLException: ERROR: syntax error at or near "INDEX"
at com.softwaremill.okapi.mysql.MysqlOutboxStore.claimPending(MysqlOutboxStore.kt:64)
FORCE INDEX (MysqlOutboxStore.kt:53) is MySQL-only syntax. publish() fails as well. The application looks healthy at startup and never delivers anything.
Why it is not caught
LiquibaseE2ETest asserts only that exactly one okapi*Liquibase bean activates, not which one:
val activeLiquibase = listOf("okapiPostgresLiquibase", "okapiMysqlLiquibase").filter { ctx.containsBean(it) }
activeLiquibase.size shouldBe 1
and its own comment already acknowledges the ambiguity: "Without this filter the OutboxStore precedence in the test JVM is non-deterministic between Postgres and MySQL". That test also sets okapi.processor.enabled=false, so claimPending is never exercised.
So the invariant the suite actually pins ("exactly one Liquibase activates") holds — it is the documented precedence that has no coverage.
Notes
This is not double instantiation: exactly one OutboxStore and one SpringLiquibase bean are created. The wrong one wins, decided by nested @Configuration processing order. Reproduced on v1.0.0 (e199775).
Having both modules on the classpath is a scenario the KDoc explicitly claims to handle, and it is reachable via a transitive dependency, a shared BOM, or an in-progress engine migration.
Possible directions
Either make the precedence deterministic and enforce it with a test that asserts which store wins, or — if it cannot be made deterministic — fail fast when both modules are present and no explicit @Bean OutboxStore disambiguates.
The KDoc states twice that Postgres wins when both store modules are on the classpath:
OutboxAutoConfiguration.kt:54— "If both are present, Postgres takes priority. Override by defining your own@Bean OutboxStore."OutboxAutoConfiguration.kt:249— "When both Postgres and MySQL modules are on the classpath, [PostgresStoreConfiguration] takes priority."In practice MySQL wins.
Observed
A Spring Boot consumer with
okapi-postgresandokapi-mysqlon the classpath, running against a PostgreSQL database:The context starts and the MySQL changelog is applied to the Postgres database (its DDL happens to be accepted), producing the MySQL table shape —
id character(36),delivery_metadata json— instead of the Postgres one. Then every processor tick fails:FORCE INDEX(MysqlOutboxStore.kt:53) is MySQL-only syntax.publish()fails as well. The application looks healthy at startup and never delivers anything.Why it is not caught
LiquibaseE2ETestasserts only that exactly oneokapi*Liquibasebean activates, not which one:and its own comment already acknowledges the ambiguity: "Without this filter the OutboxStore precedence in the test JVM is non-deterministic between Postgres and MySQL". That test also sets
okapi.processor.enabled=false, soclaimPendingis never exercised.So the invariant the suite actually pins ("exactly one Liquibase activates") holds — it is the documented precedence that has no coverage.
Notes
This is not double instantiation: exactly one
OutboxStoreand oneSpringLiquibasebean are created. The wrong one wins, decided by nested@Configurationprocessing order. Reproduced on v1.0.0 (e199775).Having both modules on the classpath is a scenario the KDoc explicitly claims to handle, and it is reachable via a transitive dependency, a shared BOM, or an in-progress engine migration.
Possible directions
Either make the precedence deterministic and enforce it with a test that asserts which store wins, or — if it cannot be made deterministic — fail fast when both modules are present and no explicit
@Bean OutboxStoredisambiguates.