Yet another Maven dependency graph generator for Bazel.
Mabel provides the mabel_rule rule and artifact macro, which automatically generate a set of targets that can be used as dependencies based on a given list of Maven coordinates. The rule outputs the dependency graph to a lockfile (similar to Yarn's lock file or npm's package-lock.json).
- Transitively resolves all dependencies from a given list of Maven dependencies and manages version conflicts, ensuring that only one version of each artifact is available in the dependency graph.
- Generates repository rules for all remote artifacts.
- Generates required Java rules with transitive dependencies.
- Allows marking dependencies as
test_only. - Automatically detects which rule type to create for a given dependency:
aar_importfor Android artifacts.java_plugin+java_libraryfor annotation processors. More about this here.jvm_importfor anything else.
- Allows implementation replacement for
jvm_importandaar_import. These can be replaced with another rule or macro. - Supports custom Maven repository URLs and locking dependencies to a specific Maven repository.
- Adds
licensesdata tojvm_importrules if a license is declared in the artifact's POM file. Also adds license metadata to the targets'tagsattribute:mabel_license_name- The name of the license as it appears in thepom.xmlfile.mabel_license_url- The URL to the license file as it appears in thepom.xmlfile.mabel_license_detected_type- The type of license (Apache,MIT,GPL, etc.) as detected by Mabel.
- Adds
srcjarif sources are available in the Maven repository. - Handles POM options:
- Profiles and placeholders.
- Version specifications.
- Dependencies that do not have a POM.
- Exports the Maven coordinate as a tag in the
jvm_importrule. This can help with Bazel's pom_file rule.
- Calculates
sha256for each remote artifact. - Produces a lockfile that describes the dependency graph. This file should be checked into your repository.
Unlike other build systems, Bazel does not provide a dependency management service as part of the build and does not provide a way to specify a Maven dependency (which will be resolved transitively) and be available during compilation.
There are several attempts to solve this problem (such as sync-deps, gmaven, rules_jvm_external, migration-tooling, maven-rules, and bazel-deps), but some do not support Kotlin or Android, and some do not support customized Maven repositories.
Mabel uses a two-phase approach for dependency management:
- Phase 1: Lockfile Generation - Run the
mabel_ruletarget to resolve dependencies and generate a JSON lockfile - Phase 2: Module Extension - Configure the
mavenextension to read the lockfile and create repository rules
This approach ensures reproducible builds and allows you to review dependency changes before committing them.
Add mabel as dependencies:
bazel_dep(name = "mabel", version = "0.31.0") # Check latest releaseIn your module's BUILD.bazel file (e.g., //third_party:BUILD.bazel), load the mabel_rule and artifact symbols, and define a mabel_rule target with the list of your dependencies:
load("@mabel//rules:mabel.bzl", "mabel_rule", "artifact")
mabel_rule(
name = "maven_deps",
lockfile_path = "third_party/maven_install.json",
maven_deps = [
artifact("com.google.guava:guava:33.0.0-jre"),
artifact("org.apache.commons:commons-lang3:3.14.0"),
artifact("com.google.code.findbugs:jsr305:3.0.2"),
],
)Attributes:
-
name- A unique name for this target. -
lockfile_path- Path to the output JSON lockfile (relative to workspace root). This file will be created/updated when you run the target. -
maven_deps- List ofartifact()macro invocations representing Maven coordinates to resolve. -
version_conflict_resolver- (Optional) Defaultlatest_version. Strategy for resolving version conflicts. Can belatest_versionorbreadth_first. -
calculate_sha- (Optional) DefaultTrue. Calculates thesha256value of each remote artifact. -
fetch_srcjar- (Optional) DefaultFalse. Also tries to fetch sources jar for each dependency.
The artifact() macro accepts:
coordinate- Maven coordinate in the formgroup-id:artifact-id:version(e.g.,"com.google.guava:guava:33.0.0-jre").type- (Optional) Target type to create. Default isinherit. Can bejar,aar,naive,processor,inherit, orauto.test_only- (Optional) Marks this dependency as test-only.maven_exclude_deps- (Optional) List of Maven dependencies to exclude from resolution.repositories- (Optional) List of URLs pointing to Maven servers. Defaults to Maven Central.exports_generation_type- (Optional) Defaultinherit. Override exports generation for this artifact. Can beinherit,all,requested_deps,none.debug_logs- (Optional) DefaultFalse. Prints debug logs for this artifact.
Run the mabel_rule target to resolve all transitive dependencies and generate the lockfile:
bazel run //third_party:maven_depsThis will:
- Resolve all transitive dependencies from the specified Maven coordinates
- Apply version conflict resolution (using the configured strategy)
- Download and calculate SHA256 hashes for all artifacts
- Generate
third_party/maven_install.jsoncontaining all resolved dependency metadata
The lockfile is a JSON file with the following structure:
{
"version": "1.0",
"artifacts": {
"com.google.guava:guava:33.0.0-jre": {
"repo_name": "com_google_guava__guava__33_0_0_jre",
"url": "https://repo1.maven.org/maven2/...",
"sha256": "...",
"dependencies": ["com_google_code_findbugs__jsr305__3_0_2", ...],
"exports": [...],
"runtime_deps": [...],
"test_only": false,
"target_type": "jar",
"licenses": [...]
}
}
}Important: Commit this lockfile to your version control system. It should be treated like package-lock.json in npm or Cargo.lock in Rust.
In your MODULE.bazel, configure the mabel extension to read the lockfile:
mabel = use_extension("@mabel//rules:extensions.bzl", "mabel")
mabel.install(
lockfile = "//third_party:maven_install.json",
aliases_repo = "maven",
)
# Import the maven alias repository for convenient access
use_repo(mabel, "maven")
# Optionally (but discouraged) import specific versioned repositories if needed
use_repo(mabel,
"com_google_guava__guava__33_0_0_jre",
"org_apache_commons__commons_lang3__3_14_0",
"com_google_code_findbugs__jsr305__3_0_2",
)The mabel.install() tag accepts:
lockfile- Label pointing to the JSON lockfile generated in Step 3.aliases_repo- Name of the alias repository to create (e.g.,"maven"). This repository provides clean, version-free paths to your dependencies.
The use_repo() call imports the repository rules created by the extension. The "maven" repository is an alias repository that provides convenient access to all dependencies. Repository names follow the {group_id}__{artifact_id}__{version} pattern, where dots, hyphens, and other special characters are replaced with underscores.
How to find repository names:
You can find the exact repository names in the lockfile under the repo_name field for each artifact, or use this command:
cat third_party/maven_install.json | grep -o '"repo_name": "[^"]*"' | cut -d'"' -f4 | sortReference the dependencies in your Bazel targets using the @maven alias repository:
java_library(
name = "mylib",
srcs = ["MyLib.java"],
deps = [
"@maven//com/google/guava/guava",
"@maven//org/apache/commons/commons-lang3",
],
)The @maven repository provides clean, version-free paths to your dependencies using the pattern @maven//group/id/artifact-id.
When you need to update dependencies:
- Modify the
maven_depslist in yourmabel_rule(add, remove, or change versions) - Run
bazel run //third_party:maven_depsto regenerate the lockfile - Review the changes to
maven_install.json(usegit diff) - Update the
use_repo()call inMODULE.bazelif you added or removed dependencies - Commit the updated lockfile
You can define multiple mabel_rule targets for different dependency sets (e.g., separate sets for main code, tests, or different modules):
mabel_rule(
name = "main_deps",
lockfile_path = "third_party/main_install.json",
maven_deps = [
artifact("com.google.guava:guava:33.0.0-jre"),
],
)
mabel_rule(
name = "test_deps",
lockfile_path = "third_party/test_install.json",
maven_deps = [
artifact("junit:junit:4.13.2", test_only = True),
artifact("org.mockito:mockito-core:5.0.0", test_only = True),
],
)Then configure multiple lockfiles in MODULE.bazel:
mabel = use_extension("@mabel//rules:extensions.bzl", "mabel")
mabel.install(
lockfile = "//third_party:main_install.json",
aliases_repo = "maven",
)
mabel.install(
lockfile = "//third_party:test_install.json",
aliases_repo = "maven_test",
)
use_repo(mabel,
"maven", # Alias repository for main dependencies
"maven_test", # Alias repository for test dependencies
)Each mabel.install() creates its own alias repository, allowing you to organize dependencies by category. You can use @maven//... for main dependencies and @maven_test//... for test dependencies.
The @maven alias repository provides a convenient way to reference Maven artifacts without needing to specify versions in your BUILD files.
- Clean syntax:
@maven//com/google/guava/guavainstead of@com_google_guava__guava__33_0_0_jre//:jar - Version agnostic: Update versions in one place (the lockfile) without changing BUILD files
- Consistent with Maven conventions: Uses groupId/artifactId path structure
The extension automatically creates the @maven repository that contains alias targets for all dependencies in your lockfiles. Each alias points to the actual versioned repository.
For example, for com.google.guava:guava:33.0.0-jre, the extension creates:
- A versioned repository:
@com_google_guava__guava__33_0_0_jre<- do not use that - An alias at:
@maven//com/google/guava/guava-> points to@com_google_guava__guava__33_0_0_jre//:jar
The alias repository follows Maven's groupId/artifactId structure:
- groupId: Dots become slashes (e.g.,
com.google.guava→com/google/guava) - artifactId: Used as-is (e.g.,
guava→guava)
Examples:
com.google.guava:guava→@maven//com/google/guava/guavaorg.apache.commons:commons-lang3→@maven//org/apache/commons/commons-lang3com.squareup.okhttp3:okhttp→@maven//com/squareup/okhttp3/okhttp
This rule merges the dependencies into one version-conflict-resolved dependency graph, ensuring you do not have conflicting versions of an artifact in your classpath.
Attributes:
maven_deps- List ofartifacttargets representing Maven coordinates.lockfile_path- Path to output JSON lockfile. This file will be generated and used by the module extension to create repository rules.artifacts_path- (Optional) Cache location to download artifacts into. Empty means[user-home-folder]/.mabel/artifacts/.calculate_sha- DefaultTrue. Calculates thesha256value of each remote artifact.debug_logs- DefaultFalse. If set toTrue, prints debug logs while resolving dependencies.default_exports_generation- Defaultrequested_deps. Specifies for which targets theexportsattribute should be generated. Can be:all,requested_deps,none.default_target_type- Defaultauto. The type of artifact targets to generate. Can be:jar,aar,naive,processor,auto.fetch_srcjar- DefaultFalse. Also attempts to fetch the source jar for each dependency.generated_targets_prefix- A prefix to add to all generated targets. Default is empty (no prefix). This is useful if you want to generate several unrelated graphs.mabel_repository_rule_name- Defaultmabel. The name of the mabel remote repository.output_graph_to_file- DefaultFalse. If set toTrue, outputs the graph todependencies.txt.public_targets_category- Defaultall. Sets public visibility of resolved targets. Can be:requested_deps,recursive_exports,all.version_conflict_resolver- Defaultlatest_version. Defines the strategy used to resolve version conflicts. Can be:latest_version,breadth_first.
This macro declares a Maven dependency to be resolved and imported into your workspace.
Attributes:
coordinate- Maven coordinate in the formgroup-id:artifact-id:version.type- The type of target(s) to create for this artifact. Default isinherit. Can bejar,aar,naive,processor,inheritorauto. For more details, see TargetType.java.test_only- Marks this dependency to be used in tests only.maven_exclude_deps- List of Maven dependencies that should not be resolved. You can omit theversionor bothartifact-id:version.repositories- List of URLs that point to Maven servers. The default list includes Maven Central.exports_generation_type- Defaultinherit. Overrides exports generation for this artifact. Can beinherit,all,requested_deps,none.debug_logs- DefaultFalse. Prints debug logs for this artifact.
You can find several examples under the examples/ folder in this repository. These examples are built as part of the CI process, so they represent working use cases.
If the resolved artifact is an aar file, Mabel will create an aar_import target.
For dependencies detected as annotation processors, Mabel creates a java_plugin rule for each detected processor_class, and then wraps all of these rules in a java_library rule that exports the plugins.
For example, if you include com.google.auto.value:auto-value:1.6.3, which is a Java annotation processor, Mabel creates the following rules:
//resolver:main_deps___com_google_auto_value__auto_value- Ajava_librarywithout annotation-processing capabilities.//resolver:main_deps___com_google_auto_value__auto_value___processor_class_0..4 - Ajava_pluginwith annotation-processing capabilities using each detected processor class. There are four of these because there are four processor classes.//resolver:main_deps___com_google_auto_value__auto_value___generates_api___processor_class_0..4 - The same as above, but withgenerates_apienabled.//resolver:main_deps___com_google_auto_value__auto_value___processor_class_all- Ajava_librarythat groups all processors that do not generate API.//resolver:main_deps___com_google_auto_value__auto_value___generates_api___processor_class_all- Same as above, but generates API.
Please read the Bazel docs about which variant you want.
Since Mabel wraps the java_plugin rules in java_library rules, you should add them to the deps list of your rule and not to the plugins list, unless you're directly using the X___processor_class_Y variant, in which case you should use it in the plugins field.
If you get errors about missing repositories, make sure all repository names are listed in the use_repo() call. You can extract all repository names from the lockfile:
cat third_party/maven_install.json | grep -o '"repo_name": "[^"]*"' | cut -d'"' -f4 | sortMabel resolves version conflicts using the configured strategy (latest_version by default). If you encounter issues:
- Check the generated lockfile to see which versions were selected
- Try the alternative
breadth_firstresolver strategy - Use
maven_exclude_depsto explicitly exclude problematic transitive dependencies
If the lockfile changes when you haven't modified dependencies:
- Maven metadata might have been updated (for SNAPSHOT versions)
- A transitive dependency might have changed
- Use
calculate_sha = True(default) to ensure reproducible builds
After running bazel run //third_party:maven_deps:
- Review the
git diffof the lockfile to understand what changed - Update the
use_repo()call in MODULE.bazel if repository names changed - Run
bazel clean --expungeif you encounter caching issues
Contributions are welcome! Please see the CONTRIBUTING.md file for guidelines.
Apache License 2.0. See LICENSE file for details.