Build Artifact Caching
A large Java monorepo takes 45 minutes to build from scratch. 90% of the codebase hasn’t changed since the last build. Build artifact caching reuses outputs from previous builds when inputs haven’t changed, turning a 45-minute build into a 3-minute build. At scale, this is one of the highest-leverage infrastructure investments an engineering organization can make.
Content-Addressed Build Cache#
The key insight: build outputs are deterministic given their inputs. If the input files and compiler version are identical to a previous build, the output is identical. Cache the output; skip the recompilation.
Cache key: a hash of all inputs for a build target. For a Java module: hash of source files + hash of dependencies + compiler version + build flags. If the hash matches a cached output, download the cached artifact and skip compilation.
This is content-addressable storage applied to build outputs. The content hash is the key; the compiled artifact is the value.
Remote vs Local Cache#
Local cache: stored on the CI machine. Useful for repeated builds on the same machine (developer laptop, long-lived CI agent). Hit rate depends on how often the same machine builds the same module.
Remote cache: stored in object storage (S3, GCS) shared across all CI machines and developers. Any build anywhere contributes to and benefits from the cache. Hit rates are much higher because one developer’s build populates cache for all others. The first person to build after a dependency update pays the compilation cost; everyone else gets the cache hit.
Cache Invalidation and Correctness#
The correctness guarantee: if the cache key matches, the output is safe to reuse. This requires all inputs to be included in the key. Missing an input (environment variable, external tool version) creates a correctness bug: wrong cached output gets used.
Hermetic builds: explicitly declare all inputs. Build tools (Bazel, Buck, Pants) enforce this: they only see declared inputs, fail on undeclared dependencies. Hermetic builds make remote caching safe.
Non-hermetic builds (Gradle, Maven without strict configuration) may have undeclared inputs. Remote caching requires careful analysis of what’s actually an input to each target.
Cache Storage at Scale#
A large organization might cache hundreds of GB of build artifacts. Artifacts have natural expiry: anything not accessed in 30 days is unlikely to be needed again (branches are deleted, modules are replaced). LRU eviction on the remote cache store keeps storage costs bounded.
At Oracle#
Oracle’s internal build infrastructure for Java microservices used Gradle with a remote build cache backed by Nexus. Build times for CI pipelines dropped from 22 minutes to 6 minutes average after enabling the remote cache. Cache hit rate settled at 78% — meaning 78% of build targets were served from cache rather than recompiled. The remaining 22% were targets that actually changed or had dependency changes. Developer local builds saw similar improvement.
What I’m Learning#
Build artifact caching is high-leverage because the correctness guarantee is strong (same inputs always produce same outputs) and the hit rate is high (most code doesn’t change between builds). The operational challenge is hermetic builds: getting the cache key right so you never serve a stale artifact.
Have you implemented remote build caching and what was the biggest obstacle to achieving high hit rates?