$ bazel mod graph
<root> (gerrit@_)
├── bazel_features@1.39.0
├── rules_java@8.16.1
├── jgit@_
└── protobuf@33.4David Ostrovsky, GerritForge, 2026-03-25
Bazel Modules (Bzlmod) overview
Version support for Bazel Modules
Migrating Gerrit and JGit
Migrating Gerrit plugins
Current status and lessons learned
Introduced by the Bazel team around 2020
Replaces WORKSPACE with MODULE.bazel
Eliminates manual load() dependency wiring
Improves reproducibility and dependency resolution
Uses the Bazel Central Registry (BCR)
Gerrit currently uses Bazel 7.6.1
Full support for Bazel Modules
Hybrid mode still supported
WORKSPACE
WORKSPACE.bzlmod
MODULE.bazel
--enable_workspace is disabled by default
Legacy WORKSPACE builds require an explicit opt-in
Hybrid mode (WORKSPACE.bzlmod) support is removed entirely
--enable_workspace is now a no-op
All projects must use MODULE.bazel
Legacy dependencies must be handled via module extensions
Migration initiated by the JGit team in late 2025
JGit migrated first using RJE (jgit_deps)
Gerrit followed with gerrit_deps
Result: two independent dependency graphs
Different versions of the same dependencies were being pulled in
Multiple versions were included in release.war
Increased artifact size and runtime conflicts (classpath ambiguity)
Repositories were merged into a single external_deps
Gerrit enforces versions via amend_artifact
This should ideally be handled automatically by RJE
Bug report with PR: https://github.com/bazel-contrib/rules_jvm_external/issues/1549
external_deps.lock.json must be pinned when JGit dependencies are bumped
See: https://gerrit-review.googlesource.com/c/gerrit/+/564441
bazel mod graph shows the resolved dependency graph
$ bazel mod graph
<root> (gerrit@_)
├── bazel_features@1.39.0
├── rules_java@8.16.1
├── jgit@_
└── protobuf@33.4Must be updated whenever MODULE.bazel changes
Usually updated automatically during builds
Important: Always commit the updated lock file
$ bazelisk mod deps --lockfile_mode=updateSuppress automatic lock updates (CI verification)
Prevents desync between MODULE.bazel and MODULE.bazel.lock
Common idiom to enforce strict lock mode in .bazelrc
common --lockfile_mode=errorMODULE.bazel uses include() to improve maintainability
module(name = "gerrit")
include("//tools:bazlets.MODULE.bazel")
include("//tools:java_deps.MODULE.bazel")
include("//plugins:external_plugin_deps.MODULE.bazel")Conducting a release should not require LC+1
version.bzl removed
GERRIT_VERSION moved to tools/bazlets.MODULE.bazel
Use the rules_jvm_external extension
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
maven.install(
name = "external_deps",
lock_file = "//:external_deps.lock.json",
)Two different files are used (Library Compliance):
tools/deps.toml — dependencies with LC requirement
tools/nongoogle.toml — dependencies without LC requirement
Update the version in the toml file
Regenerate the lock file: external_deps.lock.json
$ bazel build :headless
REPIN=1 bazel run @external_deps//:pinmaven_jar is replaced with rules_jvm_external
Support for transitive dependencies: both a feature and a problem
WORKSPACE)transitive dependencies must be manually specified
maven_jar(
name = "openid-consumer",
artifact = "org.openid4java:openid4java:1.0.0",
)
maven_jar(
name = "nekohtml",
artifact = "net.sourceforge.nekohtml:nekohtml:1.9.10",
)rules_jvm_external)Transitive dependencies are resolved automatically
[libraries]
openid-consumer = { module = "org.openid4java:openid4java", version = "1.0.0" }Transitive dependencies are now resolved
$ bazelisk query "deps(@external_deps//:org_openid4java_openid4java)"
@external_deps//:net_sourceforge_nekohtml_nekohtmlExtra dependencies can be pulled into release.war unintentionally
Applies to both core Gerrit and plugins
Avoid shading new dependency unintentionally in release.war
Track release.war contents via an allowlist
bazel test //Documentation:check_release_war_jarsIf the contents change:
bazelisk build //:release.war.jars.txt
cp bazel-bin/release.war.jars.txt Documentation/release_war_jars.txt@external_depsUse the query command to see resolved dependencies:
bazelisk query "deps(@external_deps//:all)"
@external_deps//:commons_logging_commons_loggingsomepath Query Predicatebazelisk query "somepath(:release, @external_deps//:commons_logging_commons_logging)"
//:release
//java/com/google/gerrit/httpd/init:init
//java/com/google/gerrit/httpd/auth/openid:openid
//lib/openid:consumer
@external_deps//:org_openid4java_openid4java
@external_deps//:commons_logging_commons_loggingStandalone plugin build mode: build outside of the Gerrit workspace
In-tree build mode
Major macros were moved to bazlets to harmonize build modes
Examples are the oauth and javamelody plugins
After migration to Bazel Modules, it is easy to support standalone build mode
Outside the scope of this presentation
Is the plugin affected by the Bzlmod migration?
Create MODULE.bazel
Consume external dependencies with rules_jvm_external
Migrate external_plugin_deps.bzl to a MODULE fragment
Optional: implement tests for allowlist and Gerrit overlap
Replace provided Gerrit dependencies from the maven_jar repository:
deps = ["@commons-io//jar"],to rules_jvm_external repository and coordinates:
deps = ["@external_deps//:commons_io_commons_io"]Use lib/<dependency> wrapper rule instead:
deps = ["//lib/commons:io"]commons-compress was bumped to version 1.28.0
commons-io is a transitive dependency of commons-compress
Therefore, commons-io is exposed in the Plugin API
The explicit dependency can be removed entirely
Migrate to using Bazel Modules
Step-by-step tutorial for plugin migration
See: https://gerrit-review.googlesource.com/c/plugins/javamelody/+/553042
module(name = "gerrit-javamelody")
bazel_dep(name = "rules_jvm_external", version = "6.10")
maven.install(
name = "javamelody_plugin_deps",
artifacts = [
"net.bull.javamelody:javamelody-core:1.99.3",
],
lock_file = "javamelody_plugin_deps.lock.json",
repositories = [
"https://repo1.maven.org/maven2",
],
)gerrit_plugin and gerrit_tests macros are consumed from the bazlets repository
The interface was not changed
load(
"@com_googlesource_gerrit_bazlets//:gerrit_plugin.bzl",
"gerrit_plugin",
"gerrit_plugin_tests",
) deps = [
"@javamelody_plugin_deps//:net_bull_javamelody_javamelody_core",
],load(
"@com_googlesource_gerrit_bazlets//tools:runtime_jars_allowlist.bzl",
"runtime_jars_allowlist_test",
)
runtime_jars_allowlist_test(
name = "check_javamelody_third_party_runtime_jars",
allowlist = ":javamelody_third_party_runtime_jars.allowlist.txt",
hint = ":check_javamelody_third_party_runtime_jars_manifest",
target = ":javamelody__plugin",
)load(
"@com_googlesource_gerrit_bazlets//tools:runtime_jars_overlap.bzl",
"runtime_jars_overlap_test",
)
runtime_jars_overlap_test(
name = "javamelody_no_overlap_with_gerrit",
against = "//:headless.war.jars.txt",
hint = "Exclude overlaps via maven.install(excluded_artifacts=[...]) and re-run this test.",
target = ":javamelody__plugin",
target_compatible_with = in_gerrit_tree_enabled(),
)Add external_plugin_deps.MODULE.bazel with this content:
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
bazel_dep(name = "gerrit-javamelody")
local_path_override(
module_name = "gerrit-javamelody",
path = "plugins/javamelody",
)
use_repo(maven, "javamelody_plugin_deps")external_plugin_deps.bzlAfter the migration, external_plugin_deps.bzl should be removed.
Zuul was extended to support: external_plugin_deps.MODULE.bazel
GerritForge CI was extended to support: external_plugin_deps.MODULE.bazel
Seamless support for migration from maven_jar to Bazel Modules
Upload migration change to Bazel Modules: Verify+1
Migration is mandatory for Bazel 9+
Dependency unification is critical to avoid bloat
Lock files must be maintained carefully
Transitive dependencies require active monitoring
Start with a single dependency graph early in the process
Avoid creating parallel dependency trees
Automate the verification of final artifacts
Invest in tooling around lock file maintenance
JGit migration is complete
Gerrit core migration is in progress
PolyGerrit migration is ongoing
Plugin migration is ongoing
Tooling is still evolving
Bazel team — for designing and implementing Bzlmod
Ivan Frade — for migrating JGit to Bzlmod
Thomas Dräbing — for migrating Gerrit, Bazlets, PolyGerrit and plugins to Bzlmod
Matthias Sohn, Milutin Kristofic, and Luca Milanesio — for reviews and feedback