The Pre-Migration Reality Check Nobody Wants to Give You

Before you start the Bazel migration that will consume the next 12-18 months of your life, let's do the math on whether you actually need this nightmare.

The Real Questions Your PM Won't Ask

Do you have 500+ developers? No? Don't migrate. Seriously. Bazel's benefits only kick in at Google scale. Your team of 30 engineers will spend more time fighting Bazel than benefiting from it.

Is your current build genuinely broken? "It takes 5 minutes" is not broken. "It randomly fails and we can't figure out why" might be. But if Gradle works fine and your biggest complaint is waiting for tests, you're solving the wrong problem.

Do you have a dedicated build engineer? Bazel isn't something you set up once and forget. You need someone who understands Starlark, can debug remote execution failures, and won't quit when builds break for the third time this month.

The Pinterest Timeline That Scared Everyone

Pinterest's migration took 18 months with a dedicated team and they're considered a success story. Here's what they didn't tell you in their blog post:

  • First 3 months: Learning Starlark and figuring out why nothing compiles
  • Month 4-6: Rewriting every BUILD file and discovering dependency hell
  • Month 7-12: Fighting with remote execution and blaming AWS
  • Month 13-15: Migrating from WORKSPACE to Bzlmod because Google deprecated WORKSPACE
  • Month 16-18: Actually getting builds working consistently

Their senior engineer quit in month 10. The quote was "I'd rather debug Jenkins than deal with another Bazel error message."

What "Hermetic Builds" Actually Means

Bazel promises hermetic builds - your build only uses inputs you declare. Sounds great until you realize:

Your build depends on shit you didn't know about. That script that calls curl to download something? Dead. The test that reads /etc/hostname? Dead. The binary that expects to find java in PATH? Also dead.

System dependencies are everywhere. You'll spend weeks hunting down every place your code assumes GNU vs BSD tools, different Python versions, or that which gcc returns something useful.

The sandboxing breaks everything creative. Any clever build hack your team did - running Docker in Docker, calling out to external tools, generating code by shelling out - all broken.

I watched a team spend 2 weeks debugging the stupidest shit. Tests passed locally, failed in Bazel CI with:

FATAL ERROR in native method: No timezone info found for America/New_York

Turned out the tests were reading timezone data from /usr/share/zoneinfo and the sandbox doesn't include system directories. The "hermetic" fix? Vendor the entire fucking timezone database into their repo. 47MB of timezone data just to make tests pass.

This complexity is what makes Bazel migrations so painful - every hidden dependency becomes a sandbox violation you have to track down and fix.

The WORKSPACE → Bzlmod Migration Tax

Here's the fun part: WORKSPACE is dead. Bazel 9 removes it entirely. So if you migrate to Bazel now using WORKSPACE (because it's easier), you get to migrate again to Bzlmod within 2 years.

The Bzlmod migration breaks everything again:

  • All your load() statements change
  • Repository names change
  • Module resolution conflicts with your carefully crafted dependency versions
  • Every BUILD file needs updates

Teams are literally migrating twice. First WORKSPACE, then Bzlmod 18 months later when the first migration is barely stable.

The Infrastructure Bill Nobody Budgeted

Remote execution isn't optional at scale - it's the only way Bazel makes sense. But nobody tells you the real costs:

Compute costs explode. You're not just running builds on your laptop anymore. Every build hits a cluster of machines. I've seen AWS bills jump from $2k/month to $15k/month overnight.

Network bandwidth matters. Uploading build artifacts to remote cache for every commit adds up fast. One team I know hit their corporate 10Gbps limit after 3 weeks of Bazel remote execution. They had to explain to their IT director why the build system was consuming more bandwidth than their entire customer-facing website.

Cache storage costs. The remote cache that makes everything fast? It needs petabytes of storage. Budget $5k+/month for cache infrastructure that actually works.

Dedicated ops team. Remote execution clusters need babysitting. Machines crash, caches corrupt, network partitions happen. You need someone on call for your build system.

When Migration Actually Makes Sense

You should migrate to Bazel if you look like Google:

  • 1000+ engineers touching the same codebase
  • Multi-language monorepo (Go + Java + Python + JavaScript)
  • Build times measured in hours, not minutes
  • Cross-language dependencies everywhere
  • Dedicated platform team with Bazel expertise

Everyone else should probably stick with language-native tools and save themselves 18 months of pain.

The question isn't "Should we use Bazel?" It's "Are we big enough that the 18-month migration cost is justified?"

For most teams, the answer is no.

Migration Blockers - The Errors You'll Actually See

Q

My build fails with "No such file or directory" but the file exists locally. What the hell?

A

Welcome to sandboxing hell. Bazel can't see files you didn't declare as inputs. The error looks like:

ERROR: error executing command: No such file or directory: 'src/main/proto/user.proto'

But ls src/main/proto/user.proto shows the file exists. Bazel's sandboxing that file away from your rule.

Quick fix: ls -la in your build directory. Find the missing file. Add it to srcs or deps in your BUILD file.

Nuclear option that actually works: --spawn_strategy=local --genrule_strategy=local disables sandboxing entirely. Use this to get builds working, then spend 3 hours fixing the deps properly.

Q

Error: "cannot find symbol" for code that compiles fine with Maven/Gradle

A

Bazel's Java compilation is stricter about classpath dependencies. Maven lets you transitively depend on anything. Bazel makes you declare every dependency explicitly.

Copy this command: bazel query "deps(//your/target)" --output=build

Look for the missing JAR in the output. Add it to your deps list explicitly.

Q

WORKSPACE loading failed: "name 'java_common' is not defined"

A

This broke in Bazel 8. The Java symbols moved out of core Bazel into rules_java.

Add this to the top of your failing .bzl file:

load("@rules_java//java/common:java_common.bzl", "java_common")
load("@rules_java//java/common:java_info.bzl", "JavaInfo")

Minimum rules_java version: 7.5.0. Update your WORKSPACE or MODULE.bazel file.

Q

Bzlmod migration fails with "No such repository" errors

A

Repository names changed between WORKSPACE and Bzlmod. Your @maven_dep became @maven.dep or some other mangled name.

Nuclear fix: bazel query --show_repo_name "deps(//...)" to see all repository names.

Use --repo_env=BZLMOD_ENABLED=1 to enable verbose Bzlmod logging and see what names Bazel actually knows about.

Q

Remote execution fails with "Error downloading outputs"

A

Network issues, disk space, or your remote execution cluster is having a bad day.

Immediate workaround: --remote_download_minimal or --remote_download_outputs=false

Check your remote execution service logs. 90% of the time it's:

  1. Disk space full on execution nodes
  2. Network timeout (increase --remote_timeout=600)
  3. Authentication expired (refresh your tokens)
Q

My Starlark rule works in WORKSPACE but breaks in Bzlmod

A

Repository context changed. Your hardcoded repository names are wrong.

Replace @external_repo//path:target with Label("@external_repo//path:target") in Starlark code.

Use repository_ctx.workspace_name instead of hardcoded names in repository rules.

Q

Tests pass locally but fail on remote execution with "No such file"

A

Test data files aren't uploaded to remote execution.

Add test files to data attribute:

py_test(
    name = "my_test",
    srcs = ["test.py"],
    data = ["testdata.json", "//path/to:test_files"],
)

For generated test files, make them explicit deps of the test target.

Q

Build crashes with "OutOfMemoryError" during migration

A

Bazel's Java compilation uses different memory settings than Maven/Gradle. You'll see:

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
    at com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder.build

The default JVM heap is too small for large projects.

Copy this to your .bazelrc (create it if it doesn't exist):

build --host_jvm_args=-Xmx8g
build --host_jvm_args=-Xms2g
test --test_timeout=300,1200,3600,7200

If you're on Mac M1/M2 and it's still crashing: --host_jvm_args=-XX:+UseMTScalableGC

Still failing? Try --host_jvm_args=-Xmx16g because fuck it, RAM is cheap.

Q

Every BUILD file gives "target not declared" errors

A

You're missing basic rule definitions. Bazel doesn't include language rules by default anymore.

Add these to WORKSPACE:

load("@rules_java//java:repositories.bzl", "rules_java_dependencies", "rules_java_toolchains")
rules_java_dependencies()
rules_java_toolchains()

Or in MODULE.bazel:

bazel_dep(name = "rules_java", version = "8.0.0")
Q

Migration stalled - team says "builds are too slow"

A

Classic migration resistance. Bazel builds are slow until remote caching works.

Survival tactics:

  1. Get remote cache working first: --remote_cache=http://your-cache-server
  2. Show incremental build improvements with metrics
  3. Set up local cache: --disk_cache=/path/to/cache
  4. Accept that first 2-3 months will be slower

The reality: builds will be slower for months during migration. Plan for it.

Q

How do I convince management this isn't a disaster?

A

Show them the Pinterest/Uber success stories, but be honest about timeline:

  • Month 1-3: Learning curve, things get slower
  • Month 4-8: Major breakages and fixes
  • Month 9-12: Gradual improvement
  • Month 12+: Actually faster builds (maybe)

Budget 18 months and don't promise faster builds in the first year.

The WORKSPACE → Bzlmod Migration That Nobody Talks About

Here's the shit sandwich: you migrate to Bazel using WORKSPACE, spend 12 months getting it stable, then Google says "WORKSPACE is deprecated, migrate to Bzlmod." So you get to migrate again.

Why WORKSPACE is Dead (And Why That Hurts)

WORKSPACE files are disabled in Bazel 8 and completely removed in Bazel 9. The writing's on the wall - if you use WORKSPACE, you're living on borrowed time.

But Bzlmod isn't just a simple find-and-replace. It's a completely different dependency resolution system that breaks assumptions your WORKSPACE build relied on.

The Real Bzlmod Migration Experience

I talked to the team at a mid-size startup who went through this twice. First WORKSPACE migration took 8 months. They thought they were done. Six months later, Bazel team announces WORKSPACE deprecation.

Month 1-2: Repository name chaos

Every external dependency changes names. @com_google_protobuf becomes @protobuf. @maven_//:guava becomes @maven.guava. Hundreds of BUILD files to update.

Month 3-4: Version conflicts everywhere

Minimal Version Selection picks different versions than your WORKSPACE did. Suddenly your carefully tested dependency matrix is fucked.

Their quote: "We spent 2 weeks debugging why protobuf generated different code. Turns out Bzlmod picked protobuf 3.21 while WORKSPACE used 3.19. Generated files were completely incompatible. Build passed, tests failed with:

java.lang.NoSuchMethodError: com.google.protobuf.GeneratedMessageV3$Builder.setUnknownFieldsProto3

Every protobuf class in the codebase needed regeneration."

Month 5-6: Module extension hell

Every macro you wrote for WORKSPACE needs conversion to module extensions. The concepts don't map cleanly.

WORKSPACE lets you call my_deps() anywhere. Bzlmod module extensions run once globally with specific tag syntax. You rewrite everything.

The Specific Breakages That Will Ruin Your Day

BUILD file repository references break:

## WORKSPACE style - dead
deps = ["@com_google_protobuf//java:protobuf"]

## Bzlmod equivalent - different name
deps = ["@protobuf//java:protobuf"]

Every BUILD file with external deps needs manual updates.

Macro loading paths change:

## WORKSPACE loads
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

## Bzlmod module extensions
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")
## Different functions, different semantics

Version resolution conflicts:

Your WORKSPACE pinned guava to 28.0 because version 30 broke your code. Bzlmod's MVS picks 31.1 from a transitive dependency. Your builds break.

Override with single_version_override:

single_version_override(
    module_name = "rules_jvm_external",
    version = "4.3",
)

But now you're maintaining overrides for every dependency that has conflicts.

The Java Symbol Migration That Breaks Everything

Here's a specific clusterfuck: Java symbols (JavaInfo, java_common) moved from Bazel core to rules_java. Bazel 9 removes them entirely.

Your .bzl files that worked fine suddenly break with this cryptic bullshit:

ERROR: /Users/dev/project/BUILD:12:34: in java_binary rule //src/main:app:
Traceback (most recent call last):
  File "BUILD", line 12, column 34, in <toplevel>
  File "rules.bzl", line 45, column 12, in _java_binary_impl
Error: name 'JavaInfo' is not defined

No explanation of what JavaInfo is or where it went. Just "not defined" and good luck figuring it out.

The fix every Java rule needs:

load("@rules_java//java/common:java_info.bzl", "JavaInfo")
load("@rules_java//java/common:java_common.bzl", "java_common")

But this requires rules_java 7.5.0+. If your Bzlmod resolution picks an older version, you're fucked until you override it.

Local Repository Paths Break Everything

WORKSPACE local_repository used relative paths:

local_repository(
    name = "shared_lib",
    path = "../shared-library",
)

Bzlmod local_path_override needs absolute paths:

local_path_override(
    module_name = "shared_lib",
    path = "/absolute/path/to/shared-library",
)

Every developer needs different absolute paths. There's no clean solution - you end up with per-developer Bzlmod files or complex scripts to generate them.

The Testing Nightmare

Here's what nobody tells you: during WORKSPACE → Bzlmod migration, you need to test both systems in parallel. Your CI needs to:

  1. Run WORKSPACE builds (existing system)
  2. Run Bzlmod builds (new system)
  3. Compare outputs and make sure they're identical
  4. Debug every difference

We set up parallel CI pipelines. WORKSPACE passed, Bzlmod failed for 3 months straight. Every failure was a 4-hour debugging session to figure out why the two systems produced different outputs.

The Migration Strategy That Actually Works

Don't migrate everything at once. Create a MODULE.bazel file alongside your existing WORKSPACE. Use --enable_bzlmod flag to test Bzlmod builds while keeping WORKSPACE as fallback.

Start with minimal dependencies. Get basic builds working in Bzlmod before migrating complex dependency macros.

Expect repository name breakage. Budget 2-3 weeks just updating repository references in BUILD files.

Plan for version conflicts. Every dependency will need version investigation and potential single_version_override entries.

Keep WORKSPACE working during transition. You'll be switching back when Bzlmod builds break.

The timeline is 6+ months if you're methodical, 12+ months if you hit major version conflicts or complex macro conversions.

Why Teams Are Waiting

Smart teams are watching this shitshow and waiting. Why migrate to WORKSPACE when you know you'll migrate again to Bzlmod? Why migrate to Bzlmod when it's still breaking people?

The rational strategy: wait until Bzlmod is actually stable and WORKSPACE removal is imminent. Let other teams be the guinea pigs.

But Google's forcing the issue. WORKSPACE is gone in Bazel 9. If you want current Bazel versions, you migrate or you're stuck on Bazel 8 forever.

3am Debugging Survival Kit

Q

Build breaks with cryptic error. How do I get ANY useful information?

A

Bazel's default error messages are completely useless. You'll get shit like:

ERROR: Build failed. Not all targets were built

Thanks, Bazel. Very fucking helpful.

Copy this: bazel build //your/target --verbose_failures --sandbox_debug

This gives you the actual command that failed instead of Bazel's useless summary.

For Java errors: --javacopt=-verbose shows you exactly which classpath is missing instead of just "compilation failed".

Q

Remote execution is down and I need to build NOW

A

--spawn_strategy=local forces everything to run locally. Builds will be slower but they'll work.

If that's not enough: --disk_cache=/tmp/bazel-cache --remote_cache= disables remote entirely but keeps local cache.

Q

Bazel says "Up-to-date" but my code changes aren't reflected

A

Cache corruption - Bazel thinks nothing changed when you just modified 3 files. Classic Bazel fuckery.

Nuclear option that always works:
bazel clean --expunge && rm -rf ~/.cache/bazel

This deletes EVERYTHING and starts fresh. Takes 30 minutes to rebuild from scratch but fixes 90% of weird caching issues.

Yeah, it sucks. You'll do this dance at least once a week during migration.

Q

BUILD file syntax error and I can't figure out what's wrong

A

bazel query //your/package:all --output=build shows you what Bazel thinks your BUILD file looks like.

Common gotcha: missing commas in lists. Bazel concatenates strings without commas:

## Wrong - becomes "file1.javafile2.java"
srcs = [
    "file1.java"
    "file2.java"  # Missing comma
]
Q

Test fails only in Bazel, passes in IDE/command line

A

Test can't find data files. Quick fix:

  1. find . -name "*.txt" -o -name "*.json" -o -name "test*" to find test data
  2. Add everything to data = [...] in your test rule
  3. Use $(location filename) in test code instead of hardcoded paths
Q

Memory errors during build - "Cannot allocate memory"

A

Your build is trying to do too much in parallel. Copy this to .bazelrc:

build --local_ram_resources=HOST_RAM*.5
build --local_cpu_resources=HOST_CPUS*.75
test --local_test_jobs=4

On Mac with M1/M2, also add: --host_jvm_args=-XX:+UseMTScalableGC

Q

Starlark rule fails with "depset iteration is forbidden"

A

Common mistake - you can't iterate over depsets directly.

## Wrong
for f in depset: print(f)

## Right  
for f in depset.to_list(): print(f)

But only call .to_list() when you actually need individual items. Keep using depsets for deps.

Q

Build works fine, `bazel test` fails with bizarre errors

A

Test sandbox is stricter than build sandbox. Add these to get tests working:

test --test_env=HOME
test --test_env=USER  
test --test_tmpdir=/tmp

For tests that need network: --test_env=http_proxy --test_env=https_proxy

Q

"Error: could not get default registry URL" in Bzlmod

A

Your network/firewall is blocking the Bazel registry.

Immediate fix: --registry=file:///dev/null --lockfile_mode=off

Proper fix: Ask IT to whitelist registry.bazel.build or set up a local registry mirror.

Q

Everything was working yesterday, now nothing builds

A

Check what Bazel version you're running: bazel version

If it changed automatically (thanks Bazelisk), pin to the working version:
echo "7.4.0" > .bazelversion

Roll back the Bazel version, get builds working, then investigate what the new version broke.

Q

I broke something and need to undo all my BUILD file changes

A

You tried to be clever with a BUILD file and now nothing compiles. Time for the panic button.

git checkout HEAD -- **/BUILD.bazel **/BUILD restores all BUILD files to last commit.

For WORKSPACE/MODULE.bazel: git checkout HEAD -- WORKSPACE MODULE.bazel

If you weren't committing frequently, you're completely fucked. This is why you commit BUILD changes separately. Learn this lesson the hard way like everyone else.

Q

Bazel daemon is acting weird - commands hang or give strange errors

A

Kill everything and restart:

bazel shutdown
ps aux | grep bazel | kill -9
rm -rf ~/.cache/bazel/_bazel_*

This nukes the Bazel daemon completely. Next command will start fresh.

Migration Survival Resources

Related Tools & Recommendations

compare
Recommended

Python vs JavaScript vs Go vs Rust - Production Reality Check

What Actually Happens When You Ship Code With These Languages

java
/compare/python-javascript-go-rust/production-reality-check
100%
news
Recommended

Meta Slashes Android Build Times by 3x With Kotlin Buck2 Breakthrough

Facebook's engineers just cracked the holy grail of mobile development: making Kotlin builds actually fast for massive codebases

Technology News Aggregation
/news/2025-08-26/meta-kotlin-buck2-incremental-compilation
82%
tool
Similar content

Bazel: Google's Powerful Build System for Monorepos | Overview

Google's open-source build system for massive monorepos

Bazel
/tool/bazel/overview
71%
alternatives
Recommended

Maven is Slow, Gradle Crashes, Mill Confuses Everyone

competes with Apache Maven

Apache Maven
/alternatives/maven-gradle-modern-java-build-tools/comprehensive-alternatives
71%
compare
Similar content

Nx vs Lerna vs Rush vs Bazel vs Turborepo: Monorepo Tools Compared

Which monorepo tool won't make you hate your life

Nx
/compare/nx/lerna/rush/bazel/turborepo/monorepo-tools-comparison
69%
tool
Recommended

JavaScript - The Language That Runs Everything

JavaScript runs everywhere - browsers, servers, mobile apps, even your fucking toaster if you're brave enough

JavaScript
/tool/javascript/overview
67%
pricing
Recommended

Should You Use TypeScript? Here's What It Actually Costs

TypeScript devs cost 30% more, builds take forever, and your junior devs will hate you for 3 months. But here's exactly when the math works in your favor.

TypeScript
/pricing/typescript-vs-javascript-development-costs/development-cost-analysis
67%
tool
Recommended

Android Studio - Google's Official Android IDE

Current version: Narwhal Feature Drop 2025.1.2 Patch 1 (August 2025) - The only IDE you need for Android development, despite the RAM addiction and occasional s

Android Studio
/tool/android-studio/overview
62%
troubleshoot
Recommended

Docker Won't Start on Windows 11? Here's How to Fix That Garbage

Stop the whale logo from spinning forever and actually get Docker working

Docker Desktop
/troubleshoot/docker-daemon-not-running-windows-11/daemon-startup-issues
60%
tool
Similar content

Python 3.13 SSL Changes & Enterprise Compatibility Analysis

Analyze Python 3.13's stricter SSL validation breaking production environments and the predictable challenges of enterprise compatibility testing and migration.

Python 3.13
/tool/python-3.13/security-compatibility-analysis
57%
tool
Similar content

Python 3.13 Team Migration Guide: Avoid SSL Hell & CI/CD Breaks

For teams who don't want to debug SSL hell at 3am

Python 3.13
/tool/python-3.13/team-migration-strategy
55%
pricing
Recommended

Enterprise Git Hosting: What GitHub, GitLab and Bitbucket Actually Cost

When your boss ruins everything by asking for "enterprise features"

GitHub Enterprise
/pricing/github-enterprise-bitbucket-gitlab/enterprise-deployment-cost-analysis
49%
tool
Similar content

Gatsby to Next.js Migration: Costs, Timelines & Gotchas

Real costs, timelines, and gotchas from someone who survived the process

Gatsby
/tool/gatsby/migration-strategy
45%
howto
Similar content

AWS to GCP Production Migration Guide: Real-World Strategies & Lessons

Skip the bullshit migration guides and learn from someone who's been through the hell

Google Cloud Migration Center
/howto/migrate-aws-to-gcp-production/complete-production-migration-guide
45%
tool
Recommended

JetBrains IntelliJ IDEA - The IDE for Developers Who Actually Ship Code

The professional Java/Kotlin IDE that doesn't crash every time you breathe on it wrong, unlike Eclipse

IntelliJ IDEA
/tool/intellij-idea/overview
44%
tool
Recommended

PyCharm - The IDE That Actually Understands Python (And Eats Your RAM)

The memory-hungry Python IDE that's still worth it for the debugging alone

PyCharm
/tool/pycharm/overview
44%
tool
Recommended

Llama.cpp - Run AI Models Locally Without Losing Your Mind

C++ inference engine that actually works (when it compiles)

llama.cpp
/tool/llama-cpp/overview
44%
tool
Recommended

Python 3.13 Performance - Stop Buying the Hype

integrates with Python 3.13

Python 3.13
/tool/python-3.13/performance-optimization-guide
44%
integration
Recommended

Get Alpaca Market Data Without the Connection Constantly Dying on You

WebSocket Streaming That Actually Works: Stop Polling APIs Like It's 2005

Alpaca Trading API
/integration/alpaca-trading-api-python/realtime-streaming-integration
44%
tool
Recommended

MongoDB Atlas Enterprise Deployment Guide

integrates with MongoDB Atlas

MongoDB Atlas
/tool/mongodb-atlas/enterprise-deployment
44%

Recommendations combine user behavior, content similarity, research intelligence, and SEO optimization