What’s wrong with Bazel in 2025
What’s wrong with Bazel in 2025
I’m an unofficial Developer Relations volunteer in the Bazel ecosystem. I spend a bunch of time finding those 💎 hidden in releases or far-flung repos to amplify the message, explain the value and highlight real-world applications.
Bazel is great. It’s getting a lot of earned adoption this year. However I think it also needs an intervention, coming from a place of loving supporters. As someone with the good fortune to see the internal workings across a lot of parts of the ecosystem, a developer relations professional should sometimes put down the cheerleader pom-poms. Today isn’t 💎, it’s 💩. And it’s on my personal blog so it’s going to be…
… “No Holds Barred”
First of all this is no one’s fault. Every single human I’ve worked with in this ecosystem is doing good work, many of them with little or no compensation.
Everything here is a problem on day one for a new user. These are not obscure bugs only encountered far from the well-traveled path by companies doing something unusual.
There’s something for everyone. You won’t recognize every problem on the list, but the ones that affect you will produce a strong burning sensation between the eyes. Instead of a boring, exhaustive list, I’ll give you what I see as the P0 problem for that language or runtime.
If you’re a Director of EngProd or maybe a VP Engineering, I’d suggest you skip to the last Summary section. If you’re not, maybe you’ll want to paste a link to your Director/VP – this matters more than just to product engineers trying to ship features. Spoiler alert however: volunteers can’t fix these on nights and weekends. These are efforts that need resourcing and staffing.
I’ll try to be sensitive with information that was given to me with an understanding of privacy. I’m not a journalist, but I imagine that’s something they do too.
And with that, let’s start with the A’s!
Android
Big companies with serious apps have had to fork the Bazel rules. That’s despite a big escalation two years ago to a Google VP (one who goes on-stage at Google I/O to sell the big ticket Google-ware). You might expect more from two technologies that come from the same company, but they literally do not work together as-shipped. It’s getting to the point that some of those companies would benefit from sharing a fork.
At the same time, the politics inside Google are questionable (though currently positive) about whether even the current support level will continue. There’s some shifting of what team ought to support the combo of Android and Bazel, with the two obvious teams that might own such an overlap.
C/C++
Despite assurances from the BazelCon 2019 BoF about Bzlmod, it has in fact become a package registry for C and C++ libraries. However the accident is quite apparent, because the BUILD files donated for these packages are hilariously specific to the one person who formulated them.
They don’t work across a majority of triples (operating systems, libc versions, or CPUs). They encode arbitrary flag choices about how the library should be built, based on the bias originating from the application that was under development. They also encode arbitrary features, like “do I use mbedTLS?” which leads to some hidden dependencies between modules in the registry. Like you can’t just change mbedTLS flags if you want libarchive and therefore tar.bzl to continue working.
The alternative is just to wrap cmake or whatever the library ships with, using rules_foreign_cc. This is so slow and brittle that it defeats much of the purpose of adopting Bazel.
Other C++ package managers have a great deal of effort applied, including from package maintainers themselves, to configure the build, select flags based on the target platform, and so on. Bazel should be leveraging those work products, not recreating them.
Java
Because the incumbents like Gradle and Maven don’t provide lockfiles, you have to literally run those tools in order to reproduce the exact versions of transitive dependencies that should be installed based on the user’s provided constraints, for example their pom.xml file.
rules_java never provided support for third-party code other than the “draw the rest of the owl” rule http_jar and java_import.
rules_jvm_external fills the gap, but had its own package resolver based on Coursier which can give different results than your legacy build.
That means migrating to Bazel is unsafe because your application may behave differently despite running everything with the same sources and matching versions.
Editors and IDEs
There are now four different forks of the JetBrains plugins. The new one announced recently sounds great, but there’s still a different one for CLion, a different one for Android Studio, and then Google internally has at least one fork they don’t intend to converge.
Because the JetBrains cooperation is so strong, the Special Interest Group formed ONLY talks about that IDE, and no other editor has any coherent community group.
JavaScript
Node.js resolution algorithm has been my nemesis for many years. Because it walks up the filesystem from the require site, every npm package that wanted to resolve locally within the repo used npm link which just creates a symlink.
That means EVERY JS tool, even if it’s not written in JavaScript, MUST always walk symlinks it finds while resolving inputs.
This is true even for the newest tools we are all waiting for, like the TypeScript type-checker rewrite in Go.
However Bazel’s sandbox is… well if it were a physical one, the sand doesn’t stay in, and you can access sand from the rest of the beach, so don’t let your children play in there. Because it’s based on flimsy (while also slow!) symlink forests out into the source tree, all JS tools do the wrong thing and become non-hermetic, and frequently produce wrong results.
We have a hacky shim around the require implementation, but never quite fixed it for the newer ESM import semantics.
And none of these help us as tools like OXC are written in Rust so the hack was in the wrong layer.
We need a reliable sandbox, or stop using it altogether.
Python
On your first day, you’re likely to install a third-party package from PyPI on your shiny Macbook, then run a Bazel build to produce a docker container. Most of the time THIS CANNOT POSSIBLY WORK! rules_python (and pip, the tool it’s based on) can only ever install packages for the host platform, but it’s common in Python to have native C dependencies. If you download pre-built binary “wheels” for your dependencies, Bazel gives you the wrong one. If you instead build from source, you end up building for the host platform. In both cases your docker container fails to run because a darwin-format executable is found where an ELF format is required. This is the “cross-build problem”.
This is fixable thanks to better lockfiles like the one in UV. And in fact, aspect_rules_py recently added experimental support for it. However this brings us to the other problem in Bazel python: fragmentation. There have long been a pile of rules to choose from.
Documentation
Even if you never read it, it’s important to train AI models. Note the comically incorrect answers you get from any model when you ask for a non-trivial BUILD file.
Oh where to even start? It’s ALL quite bad, and needs a dedicated Tech Writing function to dig out from years of neglect and then make improvements.
I’ll just point to this one: let’s say you’re getting started, so you
- Navigate to https://bazel.build and click “Getting Started” - seems reasonable so far?
- Scroll down to
- 🤔 but TypeScript, Python, and Rust are the most popular languages so maybe I should “keep an eye out”?
- That message is from August 16, 2022. Three years of keeping an eye out makes my eyes cry.
That’s not cherry-picking a worst case. It’s typical for the upkeep the docs have had since the last Tech Writer was dropped from the team.
Semver & LTS
So much of the Bazel ecosystem is unironically zero-ver. Look through your own MODULE.bazel file for versions starting with a 0.
Did you intend for your production build system that powers engineering productivity to rely on unstable pre-releases of your tools?
Maybe the maintainers just don’t believe in semantic versioning, and in a couple cases I could call one of these 0.* releases “production-quality”.
At minimum any serious user should audit these and determine for yourselves that it’s a stable dependency.
REv2
The remote execution protocol we have today is quite flawed.
It’s very chatty - in particular the getMissingBlobs function is a joke among maintainers.
If I have the story right, the client says “here are 100,000 inputs, which do you have” and the server replies “I have these 95,000 of them, upload me the other 5,000”
(If I don’t have the story right, Cunningham’s Law will save me)
However the effort to produce a “version 3” of the protocol to fix these problems didn’t survive. Why? I recently learned that it’s attributable in part to a lack of commercial benefit. From conversations I’ve had, my understanding is that the companies that sell REv2 solutions don’t have an incentive to build the improved protocol because it’s work for them and cost savings only for their customers.
Go
It’s probably the best supported language under Bazel, but has a GLARING flaw.
It grew up in Google with the first reliable BUILD file generator called “glaze” which was later ported to Gazelle. It works great - except in some cases like code with CGo dependencies. That could be okay for your own BUILD files.
The problem is that any third-party packages in your go.mod file are fetched without BUILD files, and then Gazelle is run on them in the background. When it fails, you can either
- try the
patchesattribute to override, except oops, the patches are applied after Gazelle runs so you have to go to great lengths to find generated code, then modify the generated code and produce a patch file from it. If Gazelle produces different BUILD files in a later release, you have to produce new patches even if the underlying library is the same version. - Try a hard-coded, occasionally-maintained list of the “worst offenders” There are only eleven commits in 2025 and there are a lot more problem packages than that.
Docker
This is a sad churn problem.
Let’s say you started your Bzlmod migration as soon as I blogged about how to do it in December 2021. You were using rules_docker.
16 months later, rules_oci hit a 1.0 milestone. That’s cool, rules_docker was never available in the Bazel Central Registry, so now you’re unblocked to migrate it.
But before Bazel 9 drops, you don’t have enough urgency to complete the migration away from WORKSPACE.
You’re close… but then at BazelCon 2025 you learn about rules_img to discover that… rules_oci has been replaced.
In the time you were doing one Bazel migration, you had two docker rules migrations. As someone very involved in rules_oci, I take my share of responsibility for this, and rules_img should be a simple migration – but the remediation can’t just be that the next volunteer manages to avoid the next fragmentation.
Swift
It doesn’t even fetch a hermetic swiftc binary.
After this talk I asked the speaker from the Swift team if there’s any reason we couldn’t have Bazel manage the toolchain, and he confirmed that it totally could.
Bazel itself
It has bad cold builds, yet most CI setups are on ephemeral runners or pods. I was hoping that Skycache is the answer but after this year’s talk it seems to be a long time coming to Google-internal (Blaze) and not clear it’s ever worth externalizing the services around it to Bazel, since it would need a new protocol.
Summary
The business case is simple:
- If Bazel and its ecosystem have such fundamental flaws, and they continue or worsen, that hurts adoption.
- It makes new entrants say “I could use Bazel but I’d rather use something else”, and such a desire is magnified by capitalism into Demand for Something Else.
- As soon as that viable Something Else hits the market, Bazel slows and contracts.
- That leaves your company holding an expensive Total Cost of Ownership in an ecosystem that looks like a long-term “stranded on the Wrong Island”.
- In that worst-case scenario, the company can’t justify investing in ANOTHER invasive build system overhaul, but isn’t getting the promised benefit from the longevity and ecosystem around the one they are using.
- Look forward a year or two, and this is what Directors and VPs get fired for
Is someone already fixing it?
No.
Volunteers, including some engineers on your team, are sometimes curiously passionate about the low-level plumbing of your developer platform. They are not equipped to solve the problems above, because they aren’t nights-and-weekend solo-preneur problems. They need staffing and resources.
How to avoid getting fired? Don’t let this happen!
By plea is for budget-managing stakeholders of engineering productivity to ask your engineers if something on my list is a significant risk for their delivery pipeline. Then consider one or both of:
- Staff one of your talented engineers onto an OSS effort. Have them attend one of the SIG meetings under Bazel to get plugged into the virtual “team” who works on these things. There’s no conflict of interest if they show up with the agenda to fix a specific thing that’s burning your engineering teams.
- Email
foundation@bazel.buildand come to the first meeting on December 4 2025 where companies will shape a governance group for actual DOLLARS they’ll donate to the BUILD foundation.