Skip to content

fix: AutoConfigureAfter covers Spring Boot 3.5.x metrics package#41

Merged
endrju19 merged 2 commits into
mainfrom
micrometer
May 11, 2026
Merged

fix: AutoConfigureAfter covers Spring Boot 3.5.x metrics package#41
endrju19 merged 2 commits into
mainfrom
micrometer

Conversation

@endrju19
Copy link
Copy Markdown
Collaborator

@endrju19 endrju19 commented May 8, 2026

Summary

  • OkapiMicrometerAutoConfiguration declared @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 before MeterRegistry is registered, skipping the whole listener/metrics/refresher trio.
  • Lists both class locations (3.5.x and 4.0.x) so the hint is honored on every supported runtime; Spring Boot tolerates missing entries per name, so listing both is safe.
  • Adds a reflection-based test that fails the build whenever no declared @AutoConfigureAfter target resolves on the runtime classpath. The existing functional test passed because it pre-registered MeterRegistry via withBean, 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:ktlintCheck
  • Verified that the new test fails on 3.5.12 before the fix and passes after

Closes #36

endrju19 added 2 commits May 8, 2026 16:13
…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.
@endrju19 endrju19 merged commit f157391 into main May 11, 2026
8 checks passed
@endrju19 endrju19 deleted the micrometer branch May 11, 2026 07:31
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OkapiMicrometerAutoConfiguration silently skipped on Spring Boot 3.5.x due to wrong @AutoConfigureAfter target

1 participant