Speeding Up Large Codebases with Bazel's Incremental Builds
Learn how Bazel's action graph caching and hermetic sandboxing give truly incremental builds, see a concrete Java example, and understand the trade-offs before adopting it in your monorepo.
28 Feb 2026, 13:12 UTC

The Problem: Slow Rebuilds in a Monorepo
In a large repository shared by many teams, a small change to a common library can trigger a rebuild of dozens of downstream targets when using traditional Make‑ or Gradle‑based scripts. This wastes developer time and slows CI pipelines.
How Bazel Achieves Incremental Builds
Bazel describes builds with Starlark files (BUILD and .bzl) that declare inputs, outputs, and the commands that produce them. Each command becomes an action in a directed acyclic graph. Bazel hashes the exact contents of every input file and the action’s command line; if the hash matches a previous execution, the action’s output is retrieved from the local or remote cache instead of being recomputed. Because actions are sandboxed, hidden dependencies cannot affect the hash, guaranteeing that a cache hit is safe.
Worked Example: Changing a Java Library
# WORKSPACE
# (empty for this simple demo)
# BUILD file for a java_library target
java_library(
name = "util",
srcs = ["src/main/java/com/example/Util.java"],
deps = ["//third_party:guava"],
)
Run the initial build to populate the cache:
bazel build //...
After editing src/main/java/com/example/Util.java (for instance, adding a log statement), run the same command again:
bazel build //...
Bazel will report that the util target and any targets that depend on it are rebuilt, while all other targets appear as “UP‑TO‑DATE”. Only the changed source file’s hash differs, so only the corresponding actions are re‑executed.
Trade‑offs and Practical Considerations
- Migrating existing Make or Gradle builds to Bazel requires writing Starlark rules and refactoring dependencies, which can be a significant upfront investment.
- Remote execution and caching improve latency but introduce a reliance on network bandwidth and the availability of the remote cluster; if the cache is stale or unavailable, builds may fall back to local execution, affecting reproducibility.
- The sandbox that ensures hermeticity can make debugging harder because actions run in an isolated filesystem; you need to enable verbose logging to see what files were accessed.
Getting Started: Try Bazel Locally
- Install Bazel (version 7.x or newer) from .
- Create a directory for the demo and place the WORKSPACE and BUILD files shown above.
- Add a simple Java source file at
src/main/java/com/example/Util.javawith apublic class Util {}. - Run
bazel build //...to download dependencies and build the target. - Modify the Java file (e.g., add
System.out.println("changed");) and runbazel build //...again. - Observe the output: only the
utiltarget and its dependents are recompiled; other targets show “UP‑TO‑DATE”. - To inspect the sandbox, first clean the cache with
bazel clean --expunge, then runbazel build --verbose_failures //.... The log will list the temporary sandbox directory used for each action, confirming that inputs are isolated.
By starting with a small, self‑contained example you can verify Bazel’s incremental behavior before deciding whether to invest in a full migration.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.