Java and Bazel

This page contains resources that help you use Bazel with Java projects. It links to a tutorial, build rules, and other information specific to building Java projects with Bazel.

Working with Bazel

The following resources will help you work with Bazel on Java projects:

Migrating to Bazel

If you currently build your Java projects with Maven, follow the steps in the migration guide to start building your Maven projects with Bazel:

Java versions

There are two relevant versions of Java that are set with configuration flags:

  • the version of the source files in the repository
  • the version of the Java runtime that is used to execute the code and to test it

Without an additional configuration, Bazel assumes all Java source files in the repository are written in a single Java version. To specify the version of the sources in the repository add build --java_language_version={ver} to .bazelrc file, where {ver} is for example 11. Bazel repository owners should set this flag so that Bazel and its users can reference the source code’s Java version number. For more details, see Java language version flag.

Bazel uses one JDK for compilation and another JVM to execute and test the code.

By default Bazel compiles the code using a JDK it downloads and it executes and tests the code with the JVM installed on the local machine. Bazel searches for the JVM using JAVA_HOME or path.

The resulting binaries are compatible with locally installed JVM in system libraries, which means the resulting binaries depend on what is installed on the machine.

To create a hermetic compile, you can use command line flag --java_runtime_version=remotejdk_11. The code is compiled for, executed, and tested on the JVM downloaded from a remote repository. For more details, see Java runtime version flag.

There is a second pair of JDK and JVM used to build and execute tools, which are used in the build process, but are not in the build results. That JDK and JVM are controlled using --tool_java_language_version and --tool_java_runtime_version. Default values are 11 and remotejdk_11, respectively.

For more details, see configuring Java toolchains.

Best practices

In addition to general Bazel best practices, below are best practices specific to Java projects.

Directory structure

Prefer Maven’s standard directory layout (sources under src/main/java, tests under src/test/java).

BUILD files

Follow these guidelines when creating your BUILD files:

  • Use one BUILD file per directory containing Java sources, because this improves build performance.

  • Every BUILD file should contain one java_library rule that looks like this:

    java_library(
        name = "directory-name",
        srcs = glob(["*.java"]),
        deps = [...],
    )
    
  • The name of the library should be the name of the directory containing the BUILD file. This makes the label of the library shorter, that is use "//package" instead of "//package:package".

  • The sources should be a non-recursive glob of all Java files in the directory.

  • Tests should be in a matching directory under src/test and depend on this library.

Creating new rules for advanced Java builds

Note: Creating new rules is for advanced build and test scenarios. You do not need it when getting started with Bazel.

The following modules, configuration fragments, and providers will help you extend Bazel’s capabilities when building your Java projects:

Configuring the Java toolchains

Bazel uses two types of Java toolchains: - execution, used to execute and test Java binaries, controlled with --java_runtime_version flag - compilation, used to compile Java sources, controlled with --java_language_version flag

Execution toolchains

Execution toolchain is the JVM, either local or from a repository, with some additional information about its version, operating system, and CPU architecture.

Java execution toolchains may added using local_java_repository or remote_java_repository rules in the WORKSPACE file. Adding the rule makes the JVM available using a flag. When multiple definitions for the same operating system and CPU architecture are given, the first one is used.

Example configuration of local JVM:

load("@bazel_tools//tools/jdk:local_java_repository.bzl", "local_java_repository")

local_java_repository(
  name = "additionaljdk",          # Can be used with --java_runtime_version=additionaljdk or --java_runtime_version=11
  version = 11,                    # Optional, if not set it is autodetected
  java_home = "/usr/lib/jdk-15/",  # Path to directory containing bin/java
)

Example configuration of remote JVM:

load("@bazel_tools//tools/jdk:remote_java_repository.bzl", "remote_java_repository")

remote_java_repository(
  name = "openjdk_canary_linux_arm",
  prefix = "openjdk_canary", # Can be used with --java_runtime_version=openjdk_canary_11
  version = "11",            # or --java_runtime_version=11
  exec_compatible_with = [   # Specifies constraints this JVM is compatible with "@platforms//cpu:arm",
    "@platforms//os:linux",
  ],
  urls = ...,               # Other parameters are from http_repository rule.
  sha256 = ...,
  strip_prefix = ...
)

Compilation toolchains

Compilation toolchain is composed of JDK and multiple tools that Bazel uses during the compilation and that provides additional features, such as: Error Prone, strict Java dependenciess, header compilation, Android desugaring, coverage instrumentation, and genclass handling for IDEs.

You can reconfigure the compilation by adding default_java_toolchain macro to a BUILD file and registering it either by adding register_toolchain rule to the WORKSPACE file or by using --extra_toolchains flag.

Example toolchain configuration:

load(
  '@bazel_tools@bazel_tools//tools/jdk:default_java_toolchain.bzl',
  "default_java_toolchain", "DEFAULT_TOOLCHAIN_CONFIGURATION", "JDK9_JVM_OPTS", "DEFAULT_JAVACOPTS"
)

default_java_toolchain(
  name = "repository_default_toolchain",
  configuration = DEFAULT_TOOLCHAIN_CONFIGURATION, # One of predefined configurations
                                                   # Other parameters are from java_toolchain rule:
  java_runtime = "//tools/jdk:remote_jdk11",       # JDK to use for compilation
  jvm_opts = JDK9_JVM_OPTS + ["--enable_preview"], # Additional JDK options
  misc = DEFAULT_JAVACOPTS + ["--enable_preview"], # Additional javac options
  source_version = "9",
)

Predefined configurations:

  • DEFAULT_TOOLCHAIN_CONFIGURATION: all features, supports JDK versions >= 9
  • VANILLA_TOOLCHAIN_CONFIGURATION: no additional features, supports all JDKs
  • JVM8_TOOLCHAIN_CONFIGURATION: all features, JDK version 8
  • PREBUILT_TOOLCHAIN_CONFIGURATION: same as default, but only use prebuilt tools (ijar, singlejar)
  • NONPREBUILT_TOOLCHAIN_CONFIGURATION: same as default, but all tools are built from sources (this may be useful on operating system with different libc)