fix: AutoConfigureAfter covers Spring Boot 3.5.x metrics package#41
Merged
Conversation
…ring Boot 3.5.x and 4.0.x The @AutoConfigureAfter hint referenced only the Spring Boot 4.0 package layout. On 3.5.x the class is in a different package, the name-based hint was silently dropped, and OkapiMicrometerAutoConfiguration could be evaluated before MeterRegistry was registered — skipping the entire auto-configuration. List both class locations so the hint is honored on every supported Spring Boot version listed in the README compatibility matrix. Adds a reflection-based test that fails the build whenever none of the declared @AutoConfigureAfter targets resolve on the runtime classpath, which would have caught this bug on the existing 3.5.12 CI matrix entry. Closes #36
- Tighten Class.forName guard to ClassNotFoundException only. The previous runCatching swallowed every Throwable, which would have masked NoClassDefFoundError or LinkageError under mixed-jar classpath conditions and let the very regression the test guards against slip through silently. - Rename the existing wiring test to "...when a MeterRegistry bean is provided directly". Its setup pre-registers MeterRegistry via withBean, sidestepping auto-config ordering — it's a useful DI-wiring test but never exercised the ordering contract. The new name advertises that limitation. - Add a higher-fidelity functional ordering test that loads Spring Boot's real MetricsAutoConfiguration / CompositeMeterRegistryAutoConfiguration / SimpleMetricsExportAutoConfiguration via withConfiguration (no pre-registered MeterRegistry). MeterRegistry is now created by Spring Boot, so @AutoConfigureAfter on OkapiMicrometerAutoConfiguration must actually resolve and order correctly for the listener to be wired. Verified to FAIL on Spring Boot 3.5.12 when the production fix is reverted, then PASS once reapplied. The Spring Boot auto-config FQCNs are resolved through a small resolveSpringBootClass helper that tries the 3.5.x and 4.0.x package layouts in order, so a single test exercises both supported runtimes from the CI matrix.
5 tasks
endrju19
added a commit
that referenced
this pull request
May 14, 2026
…ase-core (KOJAK-80) (#42) ## Summary Closes GitHub issue #38 and JIRA KOJAK-80. Also fixes a related undocumented `NoClassDefFoundError` that prevented startup on Spring Boot 3.5.x consumers without `liquibase-core` on the classpath (e.g. Flyway-only users) — discovered during the work. ### NCDF guard — fix for the related startup bug - Move `okapi*Liquibase` factory methods into dedicated `PostgresLiquibaseConfiguration` / `MysqlLiquibaseConfiguration` inner classes with class-level `@ConditionalOnClass(SpringLiquibase)`. Spring evaluates the class-level condition via string-name lookup **before** any method introspection, so `Class.getDeclaredMethods()` is never called on a class whose `SpringLiquibase` return type would trigger `NoClassDefFoundError`. ### Issue #38 Mode 1 / KOJAK-80 §1 — host's `liquibase` bean shadowed - Add `@AutoConfigureAfter(name = [3.x path, 4.x path])` on `OutboxAutoConfiguration` so Spring Boot's `LiquibaseAutoConfiguration` registers its own `liquibase` bean first. Without this, okapi's `okapiPostgresLiquibase` (typed `SpringLiquibase`) shadows Spring Boot's type-based `@ConditionalOnMissingBean(SpringLiquibase)` guard, silently suppressing the host application's own changelog. - **Decision re. ordering direction**: KOJAK-80 suggested `@AutoConfigureBefore` as the leading candidate. The PR uses `@AutoConfigureAfter` — `Before` would put okapi's bean first and trigger the exact shadowing it's meant to prevent (Spring Boot's `@ConditionalOnMissingBean(SpringLiquibase)` is type-based and would skip its own bean if okapi's is registered first). Rationale documented in the KDoc and the JIRA comment on KOJAK-80. - **No `@DependsOn` KDoc**: KOJAK-80 also asked for a note about host beans declaring `@DependsOn("okapiPostgresLiquibase")` to handle FKs into `okapi_outbox`. That motivating case is an anti-pattern (the purger needs to delete `okapi_outbox` rows; a host FK would block it and stall the queue) — documenting it would encourage misuse. The 99.999% of users whose changelogs don't reference okapi's tables get a working setup with no extra ceremony. Decision logged on KOJAK-80. - Keep `@ConditionalOnMissingBean(name = ...)` (not type-based) so user-supplied `@Bean SpringLiquibase liquibase()` coexists with okapi's bean, while `@Bean("okapiPostgresLiquibase")` still cleanly overrides okapi's default. ### Opt-out - New `okapi.liquibase.enabled` property (default `true`) for explicit opt-out when the host app includes okapi's changelog from its own master changelog. - New `LiquibaseDisabledNotice` inner config logs a WARN-level breadcrumb on `enabled=false`, linking a future *"relation okapi_outbox does not exist"* runtime error back to the startup decision. ## Test plan Adopts the testing pattern PR #41 introduced for the analogous Micrometer ordering bug (real Spring Boot autoconfig + reflection meta-test). - **Real autoconfig coexistence E2E** (Postgres + MySQL): pulls Spring Boot's `LiquibaseAutoConfiguration` via `resolveSpringBootClass(...)`, sets `spring.liquibase.change-log`, asserts both beans register and both changelogs run. - **Multi-DataSource E2E** (KOJAK-80 §2): two real Postgres containers, `okapi.datasource-qualifier=secondaryDs`, asserts `okapi_outbox` lands on the secondary and not the primary. - **Structural pins** (`LiquibaseAutoConfigurationTest`): - No `SpringLiquibase` return type on `OutboxAutoConfiguration` / `PostgresStoreConfiguration` / `MysqlStoreConfiguration` (NCDF guard would otherwise return). - Class-level `@ConditionalOnClass(SpringLiquibase)` on both new Liquibase configs. - Name-based `@ConditionalOnMissingBean` (type-based would re-introduce Mode 1). - `@AutoConfigureAfter` annotation is structurally sound; declared names resolve when Spring Boot Liquibase autoconfig is on classpath. - **Log capture**: `LiquibaseDisabledNotice` WARN message captured via logback `ListAppender` — deletion of the warn body breaks the test, not just deletion of the class. - **User override**: end-to-end test for `@Bean("okapiPostgresLiquibase")` taking precedence over okapi's default. ### Verified - [x] `./gradlew :okapi-spring-boot:test` — Spring Boot 4.0.6 (default) - [x] `./gradlew :okapi-spring-boot:test -PspringBootVersion=3.5.12 -PspringVersion=6.2.17` - [x] `./gradlew :okapi-spring-boot:ktlintCheck` - [x] Sanity-checked four likely regressions — each fails a distinct test in the new suite: - Remove 3.x `@AutoConfigureAfter` path → meta-test failure on 3.5.12 - Remove `@AutoConfigureAfter` entirely → unconditional structural check failure - Switch to type-based `@ConditionalOnMissingBean` → name-pin failure - Move Liquibase bean back into `PostgresStoreConfiguration` → NCDF structural guard failure - [x] Empirically verified on `okapi-autocomplete-test` (Spring Boot 3.5.7, no `liquibase-core` on classpath) — app starts cleanly; the original `NoClassDefFoundError` is gone.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
OkapiMicrometerAutoConfigurationdeclared@AutoConfigureAfter(name = ...)against the Spring Boot 4.0 package only. On 3.5.x — listed in the README compatibility matrix — that class does not exist, the hint is silently dropped, and the auto-configuration can be evaluated beforeMeterRegistryis registered, skipping the whole listener/metrics/refresher trio.@AutoConfigureAftertarget resolves on the runtime classpath. The existing functional test passed because it pre-registeredMeterRegistryviawithBean, bypassing ordering — the new test exercises the actual contract.Test plan
./gradlew :okapi-spring-boot:test— Spring Boot 4.0.6 (default)./gradlew :okapi-spring-boot:test -PspringBootVersion=3.5.12 -PspringVersion=6.2.17— Spring Boot 3.5.12./gradlew :okapi-spring-boot:ktlintCheckCloses #36