30 ideas to reduce your Gradle Build times
Reduce your Gradle build times on Android & non-android projects with these great ideas
Is your Gradle build taking too long time? These 30 ideas can help you to optimize your slow Gradle build on Android and non-android projects.
1. Use the latest dependencies versions
If you stay up to date with your dependencies, you will get all the enhancements and optimizations they have to offer. Using the latest Gradle, Android Gradle plugin & Kotlin Gradle plugin versions can help a lot to reduce your build speed. But other dependencies upgrades, mainly Gradle plugins, are also important.
Keeping your Gradle project dependencies up to date can be a huge manual task if you have a big project. It’s a bit tedious for developers to manually check for dependencies upgrades, causing a lot of waste of time. The Releases Hub Gradle Plugin helps developers to keep their dependencies up to date.
You can read more about the plugin, in this post:
2. Enable Gradle Build scans
A build scan is a shareable record of a build that provides insights into what happened and why.
Enable Gradle Build scans for all your builds (local & ci builds), so you can analyze the reports and find potential issues on your build.
3. Avoid mixing Java & Kotlin code
Mixing Java & Kotlin code in the same module is bad for build times.
Here you can find a Uber post that talks about the topic.
You should pick any of both languages (I would vote for Kotlin) and migrate 100% of your code.
4. Stop generating the BuildConfig on your Android modules [Android only]
Generating the BuildConfig class for Android library modules is a bad idea. It causes build time penalties and also stimulates bad code patterns.
Since AGP 8.0, the generation is disabled by default !!!!
If you call the
BuildConfigclass from your module code, you need to enablebuildConfigin theandroid {}block in your module’sbuild.gradle.ktsfile.
You can read this post for more details:
5. Deactivate Android Gradle Plugin unused features [Android only]
Android Gradle plugin 4.0.0 introduced a way to control which build features you want to enable and disable, such as View Binding and Data Binding. When new features are added, they will be disabled, by default.
You can enable only the features you want, and it helps you optimize the build performance for your project.
You can specify the default setting for these features across all modules in a project by including one or more of the following in your project’s gradle.properties file, as shown below.
# Flag to enable AIDL compilation. Determines whether to generate
# binder classes for your AIDL files
android.defaults.buildfeatures.aidl=true
# Flag to enable/disable generation of the BuildConfig class
android.defaults.buildfeatures.buildconfig=true
# Flag to enable Compose feature
android.defaults.buildfeatures.compose=false
# Flag to enable Data Binding
# Note that the dataBinding.enabled property is now deprecated
android.defaults.buildfeatures.dataBinding=false
# Flag to enable/disable import of Prefab dependencies from AARs
android.defaults.buildfeatures.prefab=false
# Flag to enable RenderScript compilation
android.defaults.buildfeatures.renderscript=true
# Flag to enable Resource Values generation. Determines whether to
# support injecting custom variables into the module's R class.
android.defaults.buildfeatures.resvalues=true
# Flag to enable Shader AOT compilation
android.defaults.buildfeatures.shaders=true
# Flag to enable View Binding
# Note that the dataBinding.enabled property is now deprecated
android.defaults.buildfeatures.viewBinding=falseYou can use the buildFeatures block in the module-level build.gradle file to override these project-wide default settings.
android {
// The default value for each feature is shown below.
buildFeatures {
aidl = true // false since AGP 8.0
buildConfig = true // false since AGP 8.0
compose = false
dataBinding = false
prefab = false
renderscript = true // false since AGP 8.0
resvalues = true
shaders = true
viewBinding = false
}
}6. Remove unused code
If you have unused code in your project, each developer is wasting time understanding and compiling it.
Removing your unused code should be one of your main technical priorities.
7. Remove unused resources [Android only]
Similar to having unused code, if you have unused resources in your project, each developer computer is wasting time processing them.
This incredible plugin can help you automatically remove unused Android resources from your project.
You can configure your CI tool to regularly send a pull request with all the removed resources.
8. Apply Gradle Doctor plugin
The Gradle Doctor plugin analyzes your Gradle configuration and automatically detects issues on build time.
If you apply it, you can get some useful warnings related to build speed problems.
9. Disable Jetifier [Android only]
Having Jetifier enabled on your project (android.enableJetifier = true on your gradle.properties) impacts on the build speed, because the Android Gradle Plugin has to transform all the support-library-dependent artifacts to AndroidX. So, disabling jetifier is a good idea for your android project if you want to speed up your build speed. But doing that is not always a trivial task. In this article, you will find a list of 6 steps to follow, so you can fully migrate your app to AndroidX and then safely disable Jetifier.
10. Apply Android cache fix Gradle plugin [Android only]
Some Android plugin versions have issues with Gradle’s build cache feature. When applied to an Android project, the Android cache fix Gradle plugin applies workarounds for these issues based on the Android plugin and Gradle versions.
11. Disable Gradle plugins
Every time you apply a plugin to a project, you are increasing the Gradle configuration time.
Given that lots of plugins are only needed by certain environments, applying them wisely is a good idea to speed up your build times.
This 5 steps guide, shows you how to easily enable/disable plugins for each environment, so you can improve your Gradle configuration time.
12. Switch to Gradle Binary Distribution on your CI environment
The Gradle distribution zip file comes in two flavors:
- Binary-only (
-bin.zip) - Complete, with docs and sources (
-all.zip)
In your development environment, is a good idea to download the complete distribution, so you can see the sources while developing and debugging your Gradle scripts.
So, your gradle/wrapper/gradle-wrapper.properties file should look like this:
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/distsBut you don’t need the complete distribution on your CI environment. It takes more time to download and use more disk storage. So, you can execute the following simple script before running any Gradle task on your CI environment, to switch to Gradle Binary Distribution:
sed -i -e 's/-all.zip/-bin.zip/' gradle/wrapper/gradle-wrapper.properties13. Use the Gradle Daemon
A Gradle Daemon is a long-lived background process that executes your builds much more quickly than would otherwise be the case. Gradle accomplish this by avoiding the expensive bootstrapping process as well as leveraging caching, by keeping data about your project in memory.
The Gradle Daemon is enabled by default starting with Gradle 3.0, so you don’t have to do anything to benefit from it. So, try to avoid:
- using the
--no-daemonflag on command-line - defining
org.gradle.daemon=falseon yourgradle.propertiesfile
14. JDK consistency
Using a different JDK for command-line builds vs Android Studio / IntelliJ builds will cause a new Gradle daemon to spawn. This will instantly double the memory being used by Gradle.
Also, sometimes the Gradle build cache doesn’t work properly across different major JDK versions. For example, two builds targeting JDK 8 and 11 respectively will produce incompatible cache entries.
Having JDK consistency on all the places you run a Gradle task is recommended. Some common places:
- Command-line (local & CI environment)
- Android Studio / IntelliJ IDEA
- GIT clients like Sourcetree (if you have git hooks that run Gradle tasks)
You should set your JAVA_HOME environment variable and then use this JAVA_HOME as the JDK used by Android Studio or IntelliJ.
Configure JDK on Android Studio
- Go to
Android Studio->Preferences->Build, Execution, Deployment->Build Tools->Gradle - Click on
Gradle JDK - Pick the same JDK referenced by your
JAVA_HOMEenvironment variable
15. Filters on Gradle Repositories declarations
If you declared multiple repositories on your Gradle configuration, by default each dependency will be searched on all of them. This causes some delays when resolving the dependencies because Gradle will search for some of them on repositories that don’t have them.
Gradle provides an API that lets you declare that a repository exclusively includes an artifact. If you do so:
- an artifact declared in a repository can’t be found in any other
- exclusive repository content must be declared in extension (just like for repository-level content)
For example:
repositories {
// This repository will not be searched for artifacts with group "my.company"
// despite being declared first
mavenCentral()
exclusiveContent {
forRepository {
maven {
url = uri("https://repo.mycompany.com/maven2")
}
}
filter {
// this repository only contains artifacts with group "my.company"
includeGroup("my.company")
}
}
}16. Modularize your app
Modularization is a big topic and gives us multiple benefits. One of them is the reduction of your build times. If you have multiple decoupled modules, Gradle can run their tasks in parallel, saving a lot of time.
When possible, having java or kotlin only modules (I mean, without Android dependencies) is a good idea because you can save some time not applying the Android Gradle plugin to that module.
17. Avoid enabling R8 on debug [Android only]
The Android Gradle Plugin works with the R8 compiler (instead of Proguard since v3.4.0) to handle the following compile-time tasks:
- Code shrinking (or tree-shaking): detects and safely removes unused classes, fields, methods, and attributes
- Resource shrinking: removes unused resources
- Obfuscation: shortens the name of classes and members
- Optimization: inspects and rewrites your code to further reduce the size of your app’s DEX files
R8 consumes a lot of time optimizing your code. So, verify that you are not enabling it on your debug build type. TheminifyEnabled & shrinkResources properties shouldn’t appear on your debug build type or they could appear but with false value.
18. Slower From Cache (Negative Savings)
If you have a remote Gradle Build Cache server running, you will find some cases where downloading the cache takes more time than executing the task locally. Sometimes this is caused by your internet connection speed but in other cases, the problem is the task itself. Here there are two excellent articles that talk about this topic more deeply:
The Gradle Doctor plugin can help you to detect those tasks:
Once you find those tasks, you can add something like this to your root build.gradle to disable the caching for them:
def tasksToNotCache = [
"compile.*JavaWithJavac",
"compile.*LibraryResources",
"package.*Resources",
"strip.*DebugSymbols",
"mergeDebugAndroidTestNativeLibs",
"generateDebugRFile",
"dataBindingMergeDependencyArtifacts.*",
"dataBindingMergeGenClasses.*",
"bundleLibRuntimeToJarDebug",
"bundleLibCompileToJarDebug",
"parse.*LocalResources"
]
allprojects {
afterEvaluate {
tasks.matching {
task -> tasksToNotCache.any { task.name ==~ it }
}.configureEach {
outputs.cacheIf { false }
}
}
}19. Migrate your images resources to WEBP [Android only]
WebP is an image file format from Google that provides lossy compression (like JPEG) as well as transparency (like PNG) but can provide better compression than either JPEG or PNG. Lossy WebP images are supported in Android 4.0 (API level 14) and higher, and lossless and transparent WebP images are supported in Android 4.3 (API level 18) and higher.
Migrating your images on your Android project to WEBP is a good idea for reducing your APK size. And as a side effect, you can also save a bit of time when installing your APK on your device during development.
20. Use specific dependencies versions
A deterministic build is one that can be run “live” at commit time, tomorrow, and even next month and end with the exact same results. You can imagine taking a “fingerprint” of a build from when it first ran, and doing so again on a re-run. They should match exactly.
You can read more about deterministic builds here:
Using dynamic versions (such as 1.0.+), or snapshot versions (such as 1.0.0-SNAPSHOT) is a bad practice because you can’t guarantee deterministic builds. They also force Gradle to contact the remote repository to find out whether there’s a new version or snapshot available. Instead, use fixed version, so your builds are faster and more deterministic.
You can find all dependencies with dynamic versions via build scans:
21. Upgrade your development tools
If you stay always up to date with your development tools, you will get all the latest performance improvements. Just be sure you are using the latest version of the different development tools you use, like:
- Android Studio
- Kotlin plugin on Android Studio
- Android SDK Tools
- Gradle
- JVM
22. API vs implementation
A lot of the time, you are only changing internal implementation details of your code, e.g. editing a method body. These so-called ABI-compatible changes no longer trigger the recompilation of downstream projects. This especially improves build times in large multi-project builds with deep dependency chains.
You can clearly separate which dependencies are part of your api and which are only implementation details. Implementation dependencies are not leaked into the compile classpath of downstream projects, which means that they will no longer be recompiled when such an implementation detail changes.
dependencies {
api(project("my-utils"))
implementation("com.google.guava:guava:21.0")
}This is not only a performance tip, you also improve your code quality by doing this.
You can read more about this in the official documentation of compile avoidance.
23. Avoid compiling unnecessary resources [Android only]
Avoid compiling and packaging resources that you aren’t testing (such as additional language localizations). You can do that by only specifying one language resource and screen density for your development environment but keep all of them on your CI environment, as shown in the following sample:
android {
defaultConfig {
if (System.getenv("CI") != "true") {
resConfigs = project.property("resConfigsOnLocalEnv")
}
}
}Then you can define the following on your gradle.properties file
resConfigsOnLocalEnv = "en"24. Non-transitive R classes [Android only — AGP < 8.0]
If you are using an Android Gradle Plugin version older than 8.0, then set android.nonTransitiveRClass=true on your gradle.properties file. From AGP 8.0, it’s true when not specified, and thus becomes the default.
This flag enables namespacing of each library’s R class so that its R class includes only the resources declared in the library itself and none from the library’s dependencies, thereby reducing the size of the R class for that library.
You can automatically refactor your project on Android Studio, running Refactor -> Migrate to Non-transitive R Classes
25. Remove unused dependencies
Having unused dependencies declared on your grade configuration files increases your build times. You can use the gradle-lint-plugin to detect those unused dependencies. Just add the following configuration to your root build.gradle file:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.netflix.nebula:gradle-lint-plugin:{VERSION}'
}
}allprojects {
apply plugin: 'nebula.lint'
gradleLint.rules = ['all-dependency']
}And then run the lintGradle task. You will see a report with all the unused dependencies, as well as other useful Gradle warnings.
26. Parallel execution
By adding the following line to your gradle.properties you can force Gradle to execute tasks in parallel as long as those tasks are in different projects.
org.gradle.parallel=true27. Use static build config values with your debug build [Android only]
Always use static/hard-coded values for properties that go in the manifest file or resource files for your debug build type.
For example, using dynamic version codes, version names, resources, or any other build logic that changes the manifest file requires a full app build every time you want to run a change — even though the actual change might otherwise require only a hot swap. If your build configuration requires such dynamic properties, then isolate them to your release build variants and keep the values static for your debug builds.
28. Enable Gradle Configuration Cache
The configuration cache is a feature that significantly improves build performance by caching the result of the configuration phase and reusing this for subsequent builds. Using the configuration cache, Gradle can skip the configuration phase entirely when nothing that affects the build configuration, such as build scripts, has changed.
You can read more about this in the official documentation.
29. Disable unused variants [Android only]
Every time you create a new variant on an Android project, Gradle creates lots of new tasks related to that variant, increasing the configuration time. Some variants are not needed in development environments, so you can disable them and only enable them when needed.
For example:
buildTypes {
if (project.property("instrumentationBuildTypeEnabled") == true) {
instrumentation {
...
}
}
}30. Disable PNG crunching [Android only]
If you can’t (or don’t want to) convert your PNG images to WebP, you can still speed up your build by disabling automatic image compression every time you build your app.
PNG crunching is disabled by default for only the debug build type. To disable this optimization for other build types, add the following to your build.gradle file:
android {
buildTypes {
release {
// Disables PNG crunching for the release build type.
crunchPngs false
}
}
}Support Us
There are different ways to support our work:

