Brifing

Taming Bazel Dependency Conflicts in Modern Android Development

How to resolve complex dependency resolution failures caused by Bazel's Minimal Version Selection.

ProAndroidDev - Medium2 min

1Compose Upgrade Triggers Failures

Upgrading an Android application build to use Compose BOM version 2024.12.01 frequently introduces breaking dependency conflicts under Bazel. Updating dependency declarations inside MODULE.bazel can yield lengthy, cryptic error logs when packages like androidx.activity:activity-compose 1.7.0 demand older artifacts than the BOM's androidx.compose.ui:ui 1.7.6 requirements.

2Gradle Versus Bazel Resolution

Gradle implements a default newest-version-wins strategy that silently selects the highest semantic version discovered in the dependency tree. Conversely, Bazel's Bzlmod uses Minimal Version Selection, borrowing Go's deterministic approach to choose the earliest version satisfying explicit module constraints rather than picking newer available builds.

3Minimal Version Selection Limits

Bazel's Minimal Version Selection guarantees exceptional build reproducibility across environments by strictly evaluating declared constraints. However, when disparate libraries require conflicting, incompatible version ranges, the algorithm cannot deduce a compromise automatically. The build immediately halts because no single minimum version satisfies all transitive requests simultaneously.

4Pinned Policy Inadequacy

Developers frequently encounter recommendations to configure version_conflict_policy as pinned within their maven.install declarations. This configuration merely instructs Bazel to lock successfully resolved graphs into a pin file; it does not reconcile underlying version mismatches, meaning unresolved conflict errors persist during the execution phase.

5Debugging With Pin Logs

Isolating conflicting artifacts requires running a clean expunge followed by bazel run @maven//:pin maven_pin.txt via rules_jvm_external. This execution dumps detailed resolution traces into maven_pin.txt, allowing developers to inspect conflicting dependencies like androidx.collection:collection, which often spans versions from 1.0.0 to 1.4.2.

6Overriding Explicit Maven Artifacts

The definitive fix entails defining explicit maven.artifact overrides within MODULE.bazel before the primary maven.install block. Selecting the newest backward-compatible version identified in the error log forces rules_jvm_external to align all transitive consumers onto a unified release, effectively resolving ambiguous version range requests.

7Iterative Resolution Workflow

Resolving complex graphs requires an iterative loop of re-running the pinning command and applying overrides for subsequent conflicts. Once all overlapping dependencies receive explicit definitions, the pinning command finishes cleanly, allowing developers to compile target APKs and execute standard deployment commands via adb seamlessly.

8Long-Term Toolchain Implications

While manual overrides introduce verbosity, Bazel's deterministic dependency resolution prevents unexpected runtime regressions across continuous integration pipelines. However, ecosystem shifts like Kotlin 2 compiler migrations still complicate underlying toolchain rules such as rules_kotlin, forcing teams to balance aggressive library updates against rule maintenance.

Original article: ProAndroidDev - Medium — Taming Bazel Dependency Conflicts in Modern Android Development