Skip to content

Publish hoist-core-bom to align transitive versions across consumer apps #561

@lbwexler

Description

@lbwexler

Problem

hoist-core declares versions for several transitive dependencies (OpenTelemetry, OTel JDBC instrumentation, etc.) but those declarations do not propagate cleanly to consumer apps.

Specifically, when a Grails app depends on hoist-core, Spring Boot's io.spring.dependency-management plugin (applied automatically by Grails) runs resolutionStrategy.eachDependency in the consumer build and silently downgrades hoist-core's transitive OpenTelemetry versions back to whatever Spring Boot's BOM pins (currently 1.55.0 in Spring Boot 3.5.14).

This is silent — there's no warning, no resolution conflict — and produces a version skew that can blow up at runtime. We hit this empirically while upgrading opentelemetry-jdbc from 2.26.1-alpha to 2.27.0-alpha: the new alpha called GlobalOpenTelemetry.getOrNoop() (added in OTel api 1.60+), but the consumer was actually running OTel api 1.55.0 because of the Spring Boot downgrade. Result: NoSuchMethodError at boot.

The current workaround inside hoist-core's own build is ext['opentelemetry.version'] = '<version>', which Spring's plugin reads as a property override. But that override only applies to hoist-core's own build — it doesn't propagate to consumer apps. Each consumer app would need to add the same line themselves, which is invisible boilerplate they have no obvious reason to know about.

Proposed solution: publish hoist-core-bom

Publish a companion Maven BOM artifact alongside hoist-core that declares the canonical versions for everything hoist-core's transitive surface depends on. Consumer apps import the BOM once and get the versions automatically — no per-dependency boilerplate.

Consumer app build.gradle (after)

dependencies {
    implementation platform(\"io.xh:hoist-core-bom:\$hoistCoreVersion\")
    implementation \"io.xh:hoist-core:\$hoistCoreVersion\"
}

That's it. No ext['opentelemetry.version'] override. No tracking of which transitive versions hoist-core expects. When hoist-core bumps OTel, consumers pick up the new version on their next hoist-core upgrade with zero additional changes.

What goes in the BOM

// hoist-core-bom build.gradle
javaPlatform {
    allowDependencies()
}

dependencies {
    constraints {
        api 'io.opentelemetry:opentelemetry-api:1.62.0'
        api 'io.opentelemetry:opentelemetry-sdk:1.62.0'
        api 'io.opentelemetry:opentelemetry-context:1.62.0'
        api 'io.opentelemetry:opentelemetry-common:1.62.0'
        api 'io.opentelemetry:opentelemetry-exporter-otlp:1.62.0'
        api 'io.opentelemetry:opentelemetry-exporter-common:1.62.0'
        api 'io.opentelemetry:opentelemetry-exporter-sender-okhttp:1.62.0'
        api 'io.opentelemetry.instrumentation:opentelemetry-jdbc:2.27.0-alpha'
        api 'io.opentelemetry.proto:opentelemetry-proto:1.10.0-alpha'
        // ... anything else hoist-core needs to control across consumers
    }
}

Why this works

Spring Boot's dependency-management plugin honors imported BOMs as user-supplied overrides. When the consumer imports our BOM via platform(...), those constraints win over Spring Boot's defaults — without the consumer needing to know which specific properties Spring uses internally.

Concrete payoff: OTel upgrade

We just punted on the OpenTelemetry 1.61.0 → 1.62.0 + opentelemetry-jdbc 2.26.1-alpha → 2.27.0-alpha bump because the only way to make it work in consumer apps was to ask every consumer to add a one-liner override. With a hoist-core-bom:

  1. We bump versions in the BOM
  2. Consumers run ./gradlew --refresh-dependencies after picking up the new hoist-core release
  3. Done

Cost

  • One new published artifact (io.xh:hoist-core-bom) — versioned in lockstep with hoist-core
  • Small additions to publishing/signing setup
  • One-time update to consumer apps (Toolbox + customer wrappers) to import the BOM
  • Documentation note in README / upgrade guide

Related context

  • See PR / commit history around the Grails 7.1.1 upgrade and the OTel 2.27.0-alpha bump for the empirical case that motivated this.
  • The current ext['opentelemetry.version'] override inside hoist-core's build is misleading: it makes hoist-core compile against one version, but consumer apps run against another, with no warning.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions