Build performance tuning

Gradle · Expert · 12 min

Slow builds cost teams hours a day; most slowness has a diagnosable cause.

Measure first. ./gradlew build --scan publishes a build scan (Develocity) with a timeline of tasks, configuration time, cache hits and dependency downloads; --profile writes a local HTML report to build/reports/profile. Look for: long configuration phase, tasks that are never up-to-date, tests dominating, and serial execution.

Settings in gradle.properties (project or ~/.gradle):

  • org.gradle.parallel=true runs independent subprojects' tasks concurrently; org.gradle.workers.max caps workers.
  • org.gradle.caching=true enables the build cache; add a remote cache so CI and developers share outputs.
  • org.gradle.configuration-cache=true eliminates configuration time on repeated builds.
  • org.gradle.jvmargs=-Xmx4g -XX:+UseParallelGC sizes the daemon; too little memory shows as long GC pauses, too much starves compilers and test JVMs.
  • org.gradle.vfs.watch=true (default) keeps file system state between builds.

Structural fixes often matter more: split large modules so compile avoidance and caching bite, keep configuration-phase code cheap (no file I/O or network), use tasks.register not tasks.create, prefer implementation over api to limit recompilation cascades, and run slow integration tests in their own task.

For tests, maxParallelForks, forkEvery and JUnit 5's parallel execution help. Keep the daemon alive in CI (or use --no-daemon with a persistent cache); cold JVMs and dependency downloads dominate CI time, so cache ~/.gradle/caches and ~/.gradle/wrapper.

Upgrading Gradle, the JDK and plugins regularly brings free performance work.

Example

# gradle.properties
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configuration-cache=true
org.gradle.jvmargs=-Xmx3g -XX:+UseParallelGC -XX:MaxMetaspaceSize=1g
org.gradle.workers.max=8

# settings.gradle.kts (remote cache shared with CI)
# buildCache {
#     local { isEnabled = true }
#     remote<HttpBuildCache> {
#         url = uri("https://cache.acme.com/cache/")
#         isPush = System.getenv("CI") == "true"
#         credentials { username = System.getenv("CACHE_USER"); password = System.getenv("CACHE_TOKEN") }
#     }
# }

# Diagnose
./gradlew build --scan                 # timeline, cache hits, configuration time
./gradlew build --profile              # build/reports/profile/*.html
./gradlew build --info | grep -E "Task .* (is not up-to-date|UP-TO-DATE|FROM-CACHE)"
./gradlew build --configuration-cache  # one-off, surfaces incompatibilities

Remember

  • Measure with --scan or --profile before changing anything
  • parallel, caching and configuration cache are the big three switches
  • Size the daemon JVM sensibly and cache ~/.gradle in CI
  • Modularize and keep configuration cheap for structural wins

Watch out for

  • Giving the daemon a huge heap and wondering why test JVMs and the OS are swapping.

Check yourself

  1. A build scan shows every compileJava task running on every build despite no code changes. What's the likely cause?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.gradle.org/current/userguide/performance.html

Builds on

Unlocks

Nothing builds on this yet.