Bazel 8.0.0: What Changed and How to Upgrade Without Surprises

Posted on in Programming

Bazel 8.0.0 was not just another build-tool release. It was the release where Bazel stopped politely suggesting that teams should modernize their dependency management and started making the new world harder to ignore.

The headline changes were Bzlmod becoming the default, WORKSPACE being disabled by default, and more of Bazel's previously built-in language support moving into external Starlark rulesets. That combination matters because it changes how teams should think about build ownership. Bazel is less of a single large binary that quietly brings all the rules with it, and more of a build platform where the core tool, language rules, module graph, remote execution settings, and repository policy all need to be managed deliberately.

That is a good direction. It is also the sort of change that can make a casual upgrade branch unexpectedly exciting in the least fun sense of the word.

If you are reading this in 2026, Bazel 8 should be understood as the transition release between the Bazel 7 era and the Bazel 9 world. Bazel 9 has already removed WORKSPACE support and expects teams to be living with Bzlmod and explicit external rules. So even if you are not stopping on Bazel 8 for long, the Bazel 8 upgrade is where many repositories need to do the real cleanup.

For teams with large monorepos, mixed-language builds, remote caching, remote execution, and a pile of custom Starlark, Bazel 8 is worth treating as an engineering project, not a version bump.

The Short Version

Bazel 8.0.0 is important because it pushes three major build-system transitions:

  • Bzlmod becomes the normal dependency-management path.
  • WORKSPACE is disabled by default, though it can still be enabled in Bazel 8.
  • Built-in rules continue moving out of Bazel and into separately versioned Starlark rulesets.

The practical result is that your build has to become more explicit. You need to declare your external dependencies in MODULE.bazel, load rules from their rulesets, and pay attention to ruleset versions as part of the Bazel upgrade.

That sounds like bookkeeping. In a small repository, maybe it is. In a serious engineering organization, it is dependency governance for the build itself.

If you want the deeper background on why this matters, start with Hermeticity in Software Development: A Comprehensive Guide and The Benefits of Hermeticity in Modern Code Repositories. Bazel's modernization work is not happening in isolation. It is part of a broader move toward builds that are more explicit, reproducible, cacheable, and less dependent on whatever happens to be installed on one developer's laptop.

Bzlmod Is the Real Center of the Release

The most important Bazel 8 change is that Bzlmod is enabled by default and legacy WORKSPACE support is disabled by default.

In Bazel 7, many teams could treat Bzlmod as something to experiment with when they had time. In Bazel 8, the default flips. If your repository still depends on WORKSPACE, you can temporarily opt back in with --enable_workspace, but that should be treated as a migration aid, not a strategy.

The old WORKSPACE model let repositories accumulate dependency behavior over time. That was flexible, but it also made it too easy to hide important build state inside repository macros, transitive setup functions, and order-dependent configuration. Anyone who has debugged an external dependency issue in a mature Bazel monorepo knows the feeling: there is always one more macro, one more repository rule, one more indirect version pin hiding around the corner.

Bzlmod is an attempt to put more of that state into a module graph that Bazel can understand. Dependencies are declared in MODULE.bazel. Transitive module resolution is more structured. Overrides are explicit. Lockfiles become part of the conversation. The goal is not that every build becomes simple. The goal is that complexity is represented in a form the build system can reason about.

That distinction matters.

For a team migrating to Bazel 8, I would make MODULE.bazel the center of the upgrade branch. Do not treat it as an afterthought after you change .bazelversion. Your module file, module lockfile, ruleset versions, and remaining WORKSPACE compatibility flags are the upgrade.

If your repository is still early in the migration, the Slaptijack guide on migrating a Bazel WORKSPACE project to Bzlmod is the more focused place to start.

WORKSPACE Is Not Gone in Bazel 8, But the Direction Is Clear

Bazel 8 still gives teams an escape hatch. You can enable legacy WORKSPACE support while you migrate. That is useful, and for a large repository it may be necessary.

But I would be careful with the language teams use around that flag. "We are temporarily enabling WORKSPACE while we finish the Bzlmod migration" is a healthy statement. "Bazel 8 still supports WORKSPACE, so we are done" is how technical debt gets a longer lease with worse terms.

The reason is simple: Bazel 9 removes WORKSPACE support. That means every shortcut you take in Bazel 8 becomes part of the Bazel 9 migration. If you are already touching the build, write down what is still on the old path and why.

At minimum, track:

  • Which external repositories are still initialized through WORKSPACE.
  • Which repository macros need module-extension equivalents.
  • Which rulesets are blocked on Bzlmod support or local migration work.
  • Which CI jobs require --enable_workspace.
  • Which developer workflows still assume the old external repository layout.

That last point is easy to miss. Developers do not only interact with Bazel by running bazel build //.... They use IDE integrations, generated project files, language servers, code search links, remote cache debugging tools, and local scripts that may have assumptions about external repositories. A clean Bazel 8 upgrade includes those edges.

Starlarkification: Built-In Rules Are Moving Out

The other major Bazel 8 theme is Starlarkification, which is the continuing work to move rules that used to ship inside Bazel into external rulesets.

For example, Bazel 8 continues the shift of Android, Java, C++, Protobuf, Python, and shell-related functionality toward dedicated repositories such as rules_android, rules_java, rules_cc, protobuf, rules_python, and rules_shell.

This is a healthy architectural change. The Bazel binary should not have to carry every language ecosystem at the same pace forever. Language rules evolve on different timelines. Java, Python, C++, Android, and Protobuf each have their own communities, toolchains, compatibility problems, and release needs. Pulling rules into separately versioned modules lets those ecosystems move with more independence.

It also means the build owner has more visible work.

Under the old model, it was easier to forget that the rules were dependencies. They felt like part of Bazel. With Bazel 8, that illusion gets thinner. Ruleset versions become part of your dependency surface. You need to manage them the way you manage other important infrastructure dependencies.

That is especially true for polyglot monorepos. A repository with Java services, Python tooling, C++ libraries, Protobuf APIs, and container packaging has more than one migration path happening at once. Upgrading Bazel without upgrading the rulesets is a good way to get confusing failures where the error message seems to point at the application but the real problem is an outdated rule.

My default advice:

  • Upgrade Bazel and major rulesets in the same branch.
  • Keep the ruleset upgrade list explicit in the change description.
  • Run representative builds for each language family before merging.
  • Pay extra attention to custom macros that wrap old built-in rules.
  • Avoid hiding compatibility fixes in broad helper macros unless they have a clear owner and removal date.

This is one of those places where a little boring process saves a lot of late-night archaeology.

Explicit Loads Are a Feature, Not Just Churn

One practical consequence of Starlarkification is that teams need to get more serious about explicit load() statements. If your BUILD files or macros have been relying on built-in symbols, Bazel 8 is the warning shot. Bazel 9 makes more of this mandatory.

At first glance, explicit loads can feel like mechanical noise. I understand the temptation to see them that way. But for a large codebase, hidden build symbols are not free. They make it harder to understand where behavior comes from, which version owns it, and what has to be upgraded when something changes.

Explicit loads have several practical benefits:

  • Code search works better because rule usage points back to a real ruleset.
  • BUILD files become less dependent on implicit Bazel behavior.
  • Custom macros can be audited for old APIs more easily.
  • Ruleset upgrades have a clearer blast radius.
  • New engineers can learn the build by following imports instead of folklore.

I would not hand-edit thousands of BUILD files unless I had to. This is a good place for buildifier, migration scripts, targeted codemods, and staged cleanup. But the direction is right. A build graph should be explicit enough that a skilled engineer can follow it without needing tribal memory from 2017.

Symbolic Macros Are Worth Paying Attention To

Bazel 8 also introduced symbolic macros, which provide a more structured way to write macros with typed arguments similar to rule attributes.

This might sound like a ruleset-author feature, and in many ways it is. But teams with mature Bazel usage often have a surprising amount of custom macro code. Internal service templates, language wrappers, test helpers, container packaging, generated source handling, and cross-platform toolchain glue tend to accumulate over time.

Traditional macros are powerful, but they can also become loose bags of arguments and conventions. Symbolic macros give Bazel a better foundation for understanding macro interfaces. Typed arguments reduce ambiguity. Better structure should also help future work around lazy evaluation and tooling.

I would not rewrite every macro on day one of a Bazel 8 migration. That would be the build-system version of repainting the house while the kitchen is on fire. But I would start using symbolic macros for new shared build APIs, especially where a macro is intended to become a durable interface for many teams.

Good candidates include:

  • Internal service or library templates.
  • Shared test wrappers.
  • Code generation entry points.
  • Container or deployment package macros.
  • Macros with a long list of optional arguments.

The key is to treat macros as APIs. If half the company depends on a macro, it deserves the same care you would give a widely used library function.

Remote Execution and Caching: Less Glamorous, Very Real

Bazel releases often include changes that look minor unless you operate builds at scale. Bazel 8 had several improvements around remote execution, build event reporting, disk cache behavior, and repository handling that fit this category.

These are not always the changes that make the release announcement headline, but they matter in day-to-day engineering productivity. In a large repository, developer experience is often shaped less by one dramatic feature and more by whether builds are predictable, cache hits are understandable, and CI failures can be debugged without summoning the one person who remembers how the remote execution cluster was configured.

The big thing to watch during a Bazel 8 upgrade is whether cache behavior changes expose assumptions you had forgotten about. That can include:

  • Repository rules that are not as reproducible as they should be.
  • Tools that read from the host environment without declaring inputs.
  • Generated files that vary by platform or path.
  • Tests that pass locally because of undeclared machine state.
  • Remote cache keys that reveal differences between CI and developer laptops.

These are not "Bazel broke my build" problems in the deepest sense. They are often "Bazel made my build's hidden state visible" problems. That can be annoying, but it is also valuable.

If this is the kind of issue your team is fighting, read Overcoming Challenges to Achieve Hermeticity in Large Codebases. The hard part is rarely knowing that hermeticity is good. The hard part is getting from "we agree in principle" to "our actual build graph behaves that way."

A Practical Bazel 8 Upgrade Plan

Here is how I would approach Bazel 8 in a real engineering organization.

First, create a dedicated upgrade branch and pin the Bazel version explicitly. Do not test this through whichever Bazel happens to be installed on a laptop. Use .bazelversion, Bazelisk, or your normal version-management path so every developer and CI job is testing the same tool.

Second, make a dependency inventory. List the major rulesets, module extensions, toolchains, repository rules, and custom macros that are likely to care about the Bazel upgrade. This does not have to be a six-week audit. A one-page inventory is enough to keep the work from turning into a fog bank.

Third, move Bzlmod to the center of the migration. If you still need --enable_workspace, use it intentionally and track what remains. Do not let it become a permanent line in .bazelrc that everyone forgets.

Fourth, upgrade language rulesets alongside Bazel. For each major stack, run at least one representative target that exercises normal compilation, tests, code generation, and packaging. A green //some/tiny:target does not mean your build is healthy.

Fifth, run the migration through CI early. Remote execution, sandboxing, platform differences, and repository cache behavior are exactly where "works on my machine" becomes expensive.

Sixth, keep compatibility flags visible. If the branch needs temporary flags, put them somewhere obvious, comment why they exist, and create follow-up work to remove them. Compatibility flags are useful tools. They are also easy to turn into sediment.

Finally, write a short upgrade note for the repository. Include the Bazel version, ruleset versions, known behavior changes, required local setup changes, and rollback plan. This does not need to be formal. It just needs to exist.

What I Would Watch in Code Review

Bazel upgrade reviews can be awkward because the diff often contains a mix of mechanical changes, generated lockfile updates, ruleset bumps, and real build logic edits. That makes it easy for important details to slide by.

When reviewing a Bazel 8 upgrade, I would look for a few specific things.

Are ruleset versions being changed intentionally? A MODULE.bazel diff should not be treated like a random package-lock churn file. The rulesets are core build infrastructure.

Are old WORKSPACE paths still required? If so, is that documented? The right answer may be "yes, temporarily," but the team should know what is temporary.

Are custom macros still relying on old built-in symbols? This is where many surprises live. Search for wrappers around Java, C++, Python, shell, Protobuf, and Android rules.

Are CI and local builds using the same flags? It is common for CI to have a more realistic configuration than developer laptops. That can hide problems until after the merge.

Are remote cache and remote execution failures being investigated rather than worked around? If Bazel 8 exposes undeclared inputs or platform-sensitive repository behavior, fixing the build is better than disabling the thing that noticed.

The goal of the review is not to prove that every build file is beautiful. The goal is to make sure the repository is moving toward a build that the team can understand, operate, and upgrade again.

Should You Upgrade to Bazel 8 Now?

If you are still on Bazel 7 or older, yes, you should have a plan. The exact timing depends on the repository, but avoiding the migration does not make it smaller. It just moves more work into the eventual Bazel 9 jump.

If you are already on Bazel 8, the bigger question is whether you are using it as a real transition point or just carrying compatibility flags. A Bazel 8 repository with Bzlmod, explicit ruleset dependencies, cleaned-up loads, and a healthy CI matrix is in a much better position to move to Bazel 9. A Bazel 8 repository that still behaves like Bazel 7 with extra flags is not.

If you are starting a new Bazel project, I would not design around legacy WORKSPACE patterns at all. Start with Bzlmod. Use current rulesets. Keep custom macros small until you have real repetition to abstract. And invest in hermeticity early, because retrofitting it after the build grows teeth is much less pleasant.

For C++ teams specifically, it is also worth reading Best Build System for C++: Why Bazel Stands Out and Bazel for C++: A Practical Introduction. Bazel's strengths show up most clearly when language tooling, dependency boundaries, and remote execution are all taken seriously.

Final Take

Bazel 8.0.0 is best understood as a cleanup release with consequences. It makes the modern Bazel model much harder to postpone: Bzlmod for external dependencies, explicit ruleset ownership, Starlark-based rule evolution, and more pressure toward hermetic builds.

That is good engineering direction. It is also real migration work.

The teams that handle Bazel 8 well will not be the ones that simply bump the version and hope. They will be the teams that treat the build as production infrastructure: versioned, reviewed, tested in CI, documented enough for the next person, and honest about temporary compatibility flags.

That sounds a little fussy until the build breaks for 200 engineers. Then it sounds like leadership.

Slaptijack's Koding Kraken