Android builds can become slow as a project grows. A build that works quickly on a small application may take much longer after adding multiple modules, plugins, generated code, and complex build logic.
Gradle provides a configuration cache to reduce some of this overhead. Instead of configuring the entire project again for every build, Gradle can save the result of project configuration and reuse it when the build inputs have not changed.
However, enabling the configuration cache does not guarantee that every build will reuse it. Some plugins and custom build scripts perform operations that Gradle cannot safely cache. Others depend on changing values or use APIs that are incompatible with configuration caching.
Understanding why cache reuse fails is the first step toward improving Android build performance.
What Is the Gradle Configuration Cache?
Gradle builds generally involve two major phases:
- Configuration: Gradle evaluates build scripts, configures projects and tasks, and determines what work must run.
- Execution: Gradle executes the selected tasks.
The configuration cache stores the result of the configuration phase so that Gradle can reuse it for a later build when the relevant inputs remain compatible.
For an Android project, this can reduce repeated configuration work when developers run tasks such as assembleDebug, testDebugUnitTest, or other supported tasks.
The configuration cache is different from the build cache.
| Feature |
Configuration cache |
Build cache |
| What it stores |
Results of project configuration |
Outputs of eligible tasks |
| Main purpose |
Avoid repeating configuration work |
Avoid repeating task work when outputs can be reused |
| Typical benefit |
Faster startup and configuration for repeated builds |
Less compilation or processing work |
| Common problem |
Incompatible build logic or changing configuration inputs |
Task outputs cannot be reused because inputs differ or caching is unsupported |
Both caches can help, but they solve different problems. A project can reuse task outputs while failing to reuse its configuration cache.
Enabling the Configuration Cache
You can enable the configuration cache in your project's gradle.properties file:
org.gradle.configuration-cache=true
For an initial diagnostic run, it can be useful to enable the cache directly from the command line:
./gradlew assembleDebug --configuration-cache
On Windows, use the Gradle wrapper batch file:
.\gradlew.bat assembleDebug --configuration-cache
Run the same task again:
./gradlew assembleDebug --configuration-cache
The first run may calculate and store the configuration state. The second run can reuse it if the build is compatible and the relevant inputs have not changed.
If Gradle reports that the configuration cache cannot be reused, inspect the diagnostic output instead of assuming the cache itself is broken.
Step 1: Read the Configuration Cache Report
Gradle reports configuration-cache problems when a build uses incompatible APIs or performs unsupported operations during configuration.
Start with a clean diagnostic run:
./gradlew assembleDebug \
--configuration-cache \
--configuration-cache-problems=warn
The warn setting is useful during migration because it lets you identify problems without immediately making every reported issue a build failure. The exact behavior depends on the Gradle version and project configuration.
For stricter validation, use the default failure behavior or explicitly configure the build to fail on configuration-cache problems.
After the build, inspect the report generated by Gradle. It identifies the problem, the relevant build logic, and the location when available.
Common causes include:
- Reading a project property from a task action.
- Accessing
Project or other configuration-time objects during task execution.
- Using mutable global state.
- Capturing unsupported objects in task actions.
- Plugins that have not been updated for configuration-cache compatibility.
- Reading environment variables or external files in ways that are not correctly represented as configuration inputs.
Do not attempt to fix every warning before understanding which component owns it. A problem may come from an Android Gradle Plugin, a third-party plugin, or custom build logic maintained by your team.
Step 2: Fix Project Access Inside Task Actions
One common problem occurs when a custom task accesses the Gradle Project object during task execution.
Consider this simplified task:
tasks.register("printAppVersion") {
doLast {
println("Version: ${project.version}")
}
}
The task action accesses project while the task is executing. This pattern can cause configuration-cache problems because the task action depends on a configuration object that is not intended to be used this way.
A better approach is to capture the required value during configuration and expose it through a task property.
For example:
import org.gradle.api.DefaultTask
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
abstract class PrintVersionTask : DefaultTask() {
@get:Input
abstract val appVersion: Property<String>
@TaskAction
fun printVersion() {
println("Version: ${appVersion.get()}")
}
}
Register the task like this:
tasks.register<PrintVersionTask>("printAppVersion") {
appVersion.set(project.version.toString())
}
The task receives the value through a declared property rather than reading the project model during execution.
The @Input annotation tells Gradle that the value is an input to the task. This helps Gradle track the task's configuration and execution requirements.
The example illustrates the general pattern. Real tasks may need additional input and output declarations, and their design should follow the APIs supported by the project's Gradle version.
Step 3: Replace Eager Task Configuration
Build scripts often configure tasks eagerly when they do not need to.
For example:
tasks.create("generateBuildInfo") {
doLast {
println("Generating build information")
}
}
A lazy registration approach is generally preferable:
tasks.register("generateBuildInfo") {
doLast {
println("Generating build information")
}
}
The register method allows Gradle to defer task realization until the task is needed.
Lazy task registration is good build design, but it does not automatically make a task configuration-cache compatible. The task action and any plugins it uses must still follow the configuration-cache requirements.
For custom task types, use managed properties and declared inputs and outputs where appropriate.
Step 4: Check Third-Party Plugins
A build can fail configuration-cache reuse even when the application's own build scripts are correct.
For example, a plugin may read project state during task execution or use an API that does not support configuration caching.
To identify the source:
- Read the configuration-cache report.
- Find the task or plugin associated with the reported problem.
- Check whether a newer plugin release addresses configuration-cache compatibility.
- Review the plugin's documentation and release notes.
- Create a minimal reproduction if the issue persists.
- Report the issue to the plugin maintainer when necessary.
Do not update every dependency at once. Change one plugin or build component at a time, then rerun the same task to determine whether the problem has been resolved.
For Android projects, keep the Android Gradle Plugin, Gradle wrapper, and Kotlin Gradle Plugin versions compatible with one another. Follow the compatibility requirements for the versions in use rather than choosing each version independently.
Step 5: Handle Environment Variables and External Files Carefully
Build configuration often depends on environment variables, local configuration files, or generated metadata.
For example, a build script might read an environment variable to determine a service endpoint:
val serviceEndpoint =
System.getenv("SERVICE_ENDPOINT") ?: "https://localhost"
Reading external values is not inherently incompatible with the configuration cache. The important question is whether Gradle can correctly track the value as a configuration input and invalidate the cache when the value changes.
Problems arise when build logic depends on changing external state that is not represented correctly in the configuration model.
For example, a local file might be edited without Gradle understanding that its contents affect configuration. Gradle could then reuse configuration that no longer reflects the intended build inputs.
Prefer supported provider APIs and explicit input modeling where applicable. For environment variables, Gradle provides APIs such as providers.environmentVariable(...). For file inputs, use appropriate file providers and task properties rather than relying on hidden dependencies.
When debugging, check whether changing the environment variable or file should invalidate the cache. If it does not, investigate how the value is accessed and tracked.
Step 6: Verify Cache Reuse Correctly
After fixing a reported issue, run the same task twice:
./gradlew assembleDebug --configuration-cache
Then run it again:
./gradlew assembleDebug --configuration-cache
Look for Gradle's configuration-cache status messages. The second run should indicate that the configuration cache was reused when no relevant input changed and the task is compatible.
If Gradle reports that the cache entry was discarded, check the reason. Common explanations include:
- A relevant build input changed.
- A build script or plugin changed.
- The selected task requires a different configuration.
- A reported incompatibility prevented reuse.
- The cache entry was invalidated or could not be loaded.
A cache miss is not always a defect. If a build input genuinely changes, Gradle may need to calculate configuration again.
For more useful results, compare repeated runs using the same task, command-line options, environment, and project state.
Step 7: Add Configuration Cache Checks to CI
Once the build is compatible, keep it that way by running configuration-cache checks in continuous integration.
For example:
./gradlew assembleDebug --configuration-cache
You can also run other important tasks, such as unit tests, using the same approach:
./gradlew testDebugUnitTest --configuration-cache
Test the tasks your team regularly runs rather than assuming that compatibility of one task guarantees compatibility of every task.
During migration, teams may temporarily use warning mode to identify remaining problems. After addressing them, use stricter validation so new incompatible build logic does not go unnoticed.
If the repository contains multiple modules or custom plugins, consider a dedicated CI job that exercises representative tasks and records the configuration-cache outcome.
Common Mistakes to Avoid
Deleting the cache after every failure
Deleting Gradle caches may temporarily change the behavior of a build, but it does not correct incompatible build logic. Inspect the diagnostic report before removing cached data.
Assuming lazy task registration solves everything
Using tasks.register is good practice, but tasks can still be incompatible if their actions access unsupported objects or depend on untracked state.
Upgrading Gradle without checking compatibility
A newer Gradle version may introduce useful fixes, but it must be compatible with the Android Gradle Plugin and other build components. Upgrade deliberately and test the full build.
Testing only one task
A successful assembleDebug run does not guarantee that custom tasks, tests, code generation, or release builds can reuse the configuration cache.
Ignoring warnings permanently
Warning mode is useful during migration. Leaving known issues unresolved can prevent consistent cache reuse and make future upgrades harder.
Best Practices for Android Teams
Use these practices to keep configuration caching reliable:
- Pin compatible Gradle and plugin versions.
- Prefer lazy task registration and configuration avoidance.
- Implement custom tasks with managed properties and explicit inputs and outputs.
- Avoid accessing configuration-time objects from task actions.
- Track external inputs through supported Gradle APIs.
- Test the tasks used by developers and CI.
- Keep diagnostic reports available when configuration-cache checks fail.
- Review plugin compatibility before adopting new build tooling.
Summary
Gradle's configuration cache can reduce repeated configuration work in Android projects, but it depends on build logic that Gradle can safely cache and reuse.
When reuse fails, start with the configuration-cache report. Identify whether the problem comes from a custom task, a build script, an external input, or a third-party plugin. Then correct the underlying issue and verify the result by running the same task again.
A reliable configuration-cache setup requires more than enabling a property. It requires compatible plugins, well-designed custom tasks, correctly tracked inputs, and automated checks that catch regressions before they slow down the development workflow.
Join the conversation! Your thoughts help the community grow.