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:
- We bump versions in the BOM
- Consumers run
./gradlew --refresh-dependencies after picking up the new hoist-core release
- 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.
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-managementplugin (applied automatically by Grails) runsresolutionStrategy.eachDependencyin 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-jdbcfrom2.26.1-alphato2.27.0-alpha: the new alpha calledGlobalOpenTelemetry.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:NoSuchMethodErrorat 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-bomPublish 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
Why this works
Spring Boot's
dependency-managementplugin honors imported BOMs as user-supplied overrides. When the consumer imports our BOM viaplatform(...), 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:
./gradlew --refresh-dependenciesafter picking up the new hoist-core releaseCost
io.xh:hoist-core-bom) — versioned in lockstep with hoist-coreRelated context
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.