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=trueruns independent subprojects' tasks concurrently;org.gradle.workers.maxcaps workers.org.gradle.caching=trueenables the build cache; add a remote cache so CI and developers share outputs.org.gradle.configuration-cache=trueeliminates configuration time on repeated builds.org.gradle.jvmargs=-Xmx4g -XX:+UseParallelGCsizes 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 incompatibilitiesRemember
- 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.