AI agent skill · by @hanamizuki
Gradle Build Performance
Debug and optimize Android/Gradle build performance. Use when builds are slow, investigating CI/CD performance, analyzing build scans, or identifying compilation bottlenecks.
Archived 2026-10-23 · Source updated 2026-08-16 · License: MIT. Check the original repository for the latest version.
SKILL.md instructions
# Gradle Build Performance
## When to Use
- Build times are slow (clean or incremental)
- Investigating build performance regressions
- Analyzing Gradle Build Scans
- Identifying configuration vs execution bottlenecks
- Optimizing CI/CD build times
- Enabling Gradle Configuration Cache
- Reducing unnecessary recompilation
- Debugging kapt/KSP annotation processing
## Example Prompts
- "My builds are slow, how can I speed them up?"
- "How do I analyze a Gradle build scan?"
- "Why is configuration taking so long?"
- "Why does my project always recompile everything?"
- "How do I enable configuration cache?"
- "Why is kapt so slow?"
---
## Workflow
1. **Measure Baseline** — Clean build + incremental build times
2. **Generate Build Scan** — `./gradlew assembleDebug --scan`
3. **Identify Phase** — Configuration? Execution? Dependency resolution?
4. **Apply ONE optimization** — Don't batch changes
5. **Measure Improvement** — Compare against baseline
6. **Verify in Build Scan** — Visual confirmation
---
## Quick Diagnostics
### Generate Build Scan
```bash
./gradlew assembleDebug --scan
```
### Profile Build Locally
```bash
./gradlew assembleDebug --profile
# Opens report in build/reports/profile/
```
### Build Timing Summary
```bash
./gradlew assembleDebug --info | grep -E "^\:.*"
# Or view in Android Studio: Build > Analyze APK Build
```
---
## Build Phases
| Phase | What Happens | Common Issues |
|-------|--------------|---------------|
| **Initialization** | `settings.gradle.kts` evaluated | Too many `include()` statements |
| **Configuration** | All `build.gradle.kts` files evaluated | Expensive plugins, eager task creation |
| **Execution** | Tasks run based on inputs/outputs | Cache misses, non-incremental tasks |
### Identify the Bottleneck
```
Build scan → Performance → Build timeline
```
- **Long configuration phase**: Focus on plugin and buildscript optimization
- **Long execution phase**: Focus on task caching and parallelization
- **Dependency resolution slow**: Focus on repository configuration
---
## 12 Optimization Patterns
### 1. Enable Configuration Cache
Caches configuration phase across builds (AGP 8.0+):
```properties
# gradle.properties
org.gradle.configuration-cache=true
org.gradle.configuration-cache.problems=warn
```
### 2. Enable Build Cache
Reuses task outputs across builds and machines:
```properties
# gradle.properties
org.gradle.caching=true
```
### 3. Enable Parallel Execution
Build independent modules simultaneously:
```properties
# gradle.properties
org.gradle.parallel=true
```
### 4. Increase JVM Heap
Allocate more memory for large projects:
```properties
# gradle.properties
org.gradle.jvmargs=-Xmx4g -XX:+UseParallelGC
```
### 5. Use Non-Transitive R Classes
Reduces R class size and compilation (AGP 8.0+ default):
```properties
# gradle.properties
android.nonTransitiveRClass=true
```
### 6. Migrate kapt to KSP
KSP is 2x faster than kapt for Kotlin:
```kotlin
// Before (slow)
kapt("com.google.dagger:hilt-compiler:2.51.1")
// After (fast)
ksp("com.google.dagger:hilt-compiler:2.51.1")
```
### 7. Avoid Dynamic Dependencies
Pin dependency versions:
```kotlin
// BAD: Forces resolution every build
implementation("com.example:lib:+")
implementation("com.example:lib:1.0.+")
// GOOD: Fixed version
implementation("com.example:lib:1.2.3")
```
### 8. Optimize Repository Order
Put most-used repositories first:
```kotlin
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
google() // First: Android dependencies
mavenCentral() // Second: Most libraries
// Third-party repos last
}
}
```
### 9. Use includeBuild for Local Modules
Composite builds are faster than `project()` for large monorepos:
```kotlin
// settings.gradle.kts
includeBuild("shared-library") {
dependencySubstitution {
substitute(module("com.example:shared")).using(project(":"))
}
}
```
### 10. Enable Incremental Annotation Processing
```properties
# gradle.properties
kapt.incremental.apt=true
kapt.use.worker.api=true
```
### 11. Avoid Configuration-Time I/O
Don't read files or make network calls during configuration:
```kotlin
// BAD: Runs during configuration
val version = file("version.txt").readText()
// GOOD: Defer to execution
val version = providers.fileContents(file("version.txt")).asText
```
### 12. Use Lazy Task Configuration
Avoid `create()`, use `register()`:
```kotlin
// BAD: Eagerly configured
tasks.create("myTask") { ... }
// GOOD: Lazily configured
tasks.register("myTask") { ... }
```
---
## Common Bottleneck Analysis
### Slow Configuration Phase
**Symptoms**: Build scan shows long "Configuring build" time
**Causes & Fixes**:
| Cause | Fix |
|-------|-----|
| Eager task creation | Use `tasks.register()` instead of `tasks.create()` |
| buildSrc with many dependencies | Migrate to Convention Plugins with `includeBuild` |
| File I/O in build scripts | Use `providers.fileContents()` |
| Network calls in plugins | Cache results or use offline mode |
### Slow Compilation
**Symptoms**: `:app:compileDebugKotlin` takes too long
**Causes & Fixes**:
| Cause | Fix |
|-------|-----|
| Non-incremental changes | Avoid `build.gradle.kts` changes that invalidate cache |
| Large modules | Break into smaller feature modules |
| Excessive kapt usage | Migrate to KSP |
| Kotlin compiler memory | Increase `kotlin.daemon.jvmargs` |
### Cache Misses
**Symptoms**: Tasks always rerun despite no changes
**Causes & Fixes**:
| Cause | Fix |
|-------|-----|
| Unstable task inputs | Use `@PathSensitive`, `@NormalizeLineEndings` |
| Absolute paths in outputs | Use relative paths |
| Missing `@CacheableTask` | Add annotation to custom tasks |
| Different JDK versions | Standardize JDK across environments |
---
## CI/CD Optimizations
### Remote Build Cache
```kotlin
// settings.gradle.kts
buildCache {
local { isEnabled = true }
remote<HttpBuildCache> {
url = uri("https://cache.example.com/")
isPush = System.getenv("CI") == "true"
credentials {
username = System.getenv("CACHE_USER")
password = System.getenv("CACHE_PASS")
}
}
}
```
### Gradle Enterprise / Develocity
For advanced build analytics:
```kotlin
// settings.gradle.kts
plugins {
id("com.gradle.develocity") version "3.17"
}
develocity {
buildScan {
termsOfUseUrl.set("https://gradle.com/help/legal-terms-of-use")
termsOfUseAgree.set("yes")
publishing.onlyIf { System.getenv("CI") != null }
}
}
```
### Skip Unnecessary Tasks in CI
```bash
# Skip tests for UI-only changes
./gradlew assembleDebug -x test -x lint
# Only run affected module tests
./gradlew :feature:login:test
```
---
## Android Studio Settings
### File → Settings → Build → Gradle
- **Gradle JDK**: Match your project's JDK
- **Build and run using**: Gradle (not IntelliJ)
- **Run tests using**: Gradle
### File → Settings → Build → Compiler
- **Compile independent modules in parallel**: ✅ Enabled
- **Configure on demand**: ❌ Disabled (deprecated)
---
## Verification Checklist
After optimizations, verify:
- [ ] Configuration cache enabled and working
- [ ] Build cache hit rate > 80% (check build scan)
- [ ] No dynamic dependency versions
- [ ] KSP used instead of kapt where possible
- [ ] Parallel execution enabled
- [ ] JVM memory tuned appropriately
- [ ] CI remote cache configured
- [ ] No configuration-time I/O
---
## References
- [Optimize Build Speed](https://developer.android.com/build/optimize-your-build)
- [Gradle Configuration Cache](https://docs.gradle.org/current/userguide/configuration_cache.html)
- [Gradle Build Cache](https://docs.gradle.org/current/userguide/build_cache.html)
- [Migrate from kapt to KSP](https://developer.android.com/build/migrate-to-ksp)
- [Gradle Build Scans](https://scans.gradle.com/)