How to fix "Gradle build daemon disappeared unexpectedly" in React Native & Expo
Eliminating Gradle Daemon OOM Failures in React Native Release Pipelines
Current Situation Analysis
React Native and Expo developers frequently encounter a deceptive failure mode during the transition from development to production. Debug builds often execute flawlessly, yet the moment a release artifact (AAB/APK) is requested via eas build or a local release task, the build process terminates with the generic error: "Gradle build daemon disappeared unexpectedly."
This error is notoriously misleading. It suggests a corruption issue, a missing dependency, or an Android Studio configuration error. In reality, it is almost exclusively a resource starvation event. Modern React Native applications bundle extensive JavaScript code, process high-resolution assets, and compile native modules simultaneously during the release phase. This workload creates a massive memory spike.
The Android Gradle daemon runs on the Java Virtual Machine (JVM). By default, the JVM allocates a conservative heap size (often 256MB to 512MB). When the bundling process exceeds this limit, the operating system's Out-Of-Memory (OOM) killer intervenes to protect system stability, forcibly terminating the daemon. The result is a silent crash that leaves no stack trace, only the disappearance of the process. This problem is often overlooked because developers focus on code errors rather than infrastructure tuning, and because the error message provides no diagnostic data.
WOW Moment: Key Findings
The critical insight is that the "daemon disappeared" error is a symptom of memory eviction, not a code defect. Tuning the JVM and CI resources transforms a 100% failure rate into a stable pipeline. The following comparison illustrates the impact of proper resource allocation versus default configurations.
| Configuration | Heap Allocation | CI Resource Class | Build Outcome | Diagnostic Clarity |
|---|---|---|---|---|
| Default / Untuned | ~512MB | medium |
OOM Crash | None (Silent Kill) |
| Tuned Local | 6GB | N/A | Success | High (Heap dumps enabled) |
| Tuned CI (EAS) | 6GB | large |
Success | High (Structured logs) |
| Over-Provisioned | 16GB on 8GB RAM | large |
Swap Thrashing | Low (Extreme Latency) |
Why this matters: Understanding that this is a resource management problem allows teams to implement proactive tuning rather than reactive debugging. By allocating sufficient heap space and matching CI resource classes to app complexity, release builds become deterministic and reliable.
Core Solution
Resolving OOM failures requires a three-pronged approach: tuning the local JVM, scaling CI resources, and ensuring state hygiene. The following implementation uses modern best practices for memory management.
1. JVM Heap and Garbage Collection Tuning
Navigate to android/gradle.properties. The default configuration often lacks the headroom required for release bundling. You must increase the maximum heap size (-Xmx) and configure the garbage collector for large heaps.
Implementation:
# android/gradle.properties
# Enable the daemon for performance
org.gradle.daemon=true
# JVM Arguments for Release Stability
# -Xmx6g: Max heap 6GB (Adjust based on physical RAM)
# -Xms2g: Initial heap 2GB (Reduces resizing overhead)
# -XX:+UseG1GC: G1 Garbage Collector (Optimized for large heaps)
# -XX:+HeapDumpOnOutOfMemoryError: Generates dump for analysis
# -XX:MaxMetaspaceSize=1g: Limits class metadata space
org.gradle.jvmargs=-Xmx6g -Xms2g -XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError -XX:MaxMetaspaceSize=1g -Dfile.encoding=UTF-8
# Parallel execution (Optional: Disable if memory is tight)
org.gradle.parallel=false
Rationale:
-Xmx6g: Provides 6GB of heap space. This accommodates heavy dependency trees and asset processing. Adjust this value based on your machine's physical RAM; never allocate more than 75% of available RAM.-Xms2g: Sets the initial heap size. This prevents the JVM from frequently expanding the heap during the build, reducing latency.-XX:+UseG1GC: The G1 Garbage Collector is designed for applications with large heaps and low latency requirements. It is superior to the default collector for build processes.-XX:MaxMetaspaceSize=1g: Modern JDKs use Metaspace for class metadata. If this fills, an OOM occurs even if the heap has space. Explicitly limiting this prevents unbounded growth.org.gradle.parallel=false: Parallel builds multiply memory usage per worker. Disabling this ensures the single build process has exclusive access to the allocated heap.
2. EAS Build Resource Scaling
When using Expo Application Services, the remote build environment has fixed resource limits. The default resource class may not provide sufficient memory for large applications. You must explicitly request a larger instance.
Implementation:
// eas.json
{
"cli": {
"version": ">= 3.0.0"
},
"build": {
"production": {
"android": {
"resourceClass": "large",
"buildType": "app-bundle",
"gradleCommand": ":app:bundleRelease"
}
},
"preview": {
"android": {
"resourceClass": "medium",
"buildType": "apk"
}
}
}
}
Rationale:
resourceClass: "large": Requests a builder with increased CPU and RAM. This is essential for apps with many native modules or large asset bundles.- Profile Segmentation: Use
largefor production andmediumfor previews to optimize cost and speed. - Explicit Gradle Command: Specifying
gradleCommandensures the build task is unambiguous.
3. State Hygiene and Daemon Reset
After modifying configuration, stale daemon processes or corrupted caches can interfere with the new settings. A clean reset ensures the build starts with the updated parameters.
Implementation:
# Stop all running Gradle daemons
./gradlew --stop
# Clean build artifacts
./gradlew clean
# Clear the build cache
./gradlew cleanBuildCache
# Verify configuration
./gradlew properties | grep jvmargs
Rationale:
--stop: Terminates zombie daemons that may be holding onto old memory limits.cleanBuildCache: Removes cached artifacts that might conflict with new build configurations.- Verification: Checking properties confirms the JVM args are applied correctly.
Pitfall Guide
Avoid these common mistakes that lead to persistent build failures or performance degradation.
| Pitfall | Explanation | Fix |
|---|---|---|
| Heap Over-Provisioning | Setting -Xmx higher than available physical RAM causes the OS to swap to disk, resulting in extreme latency or crashes. |
Set -Xmx to β€75% of physical RAM. Monitor system memory usage during builds. |
| Ignoring Metaspace Limits | Modern JDKs use Metaspace for class metadata. If unbounded, it can exhaust memory even with a large heap. | Add -XX:MaxMetaspaceSize=1g (or appropriate value) to jvmargs. |
| Node.js Memory Starvation | The React Native bundler runs on Node.js, which has its own memory limit separate from Gradle. Node OOM can crash the build. | Set export NODE_OPTIONS="--max-old-space-size=4096" in your shell or CI environment. |
| Parallel Build Memory Multiplication | Enabling org.gradle.parallel=true creates multiple workers, each consuming heap space. This can multiply memory usage beyond limits. |
Disable parallel execution (false) or reduce heap size per worker. |
| CI/Local Mismatch | Builds succeed locally but fail in CI because the CI runner has less memory or different resource class. | Ensure eas.json resource class matches local tuning. Test CI builds frequently. |
| Stale Daemon State | Old Gradle daemons may persist with outdated configuration, ignoring new gradle.properties changes. |
Run ./gradlew --stop before every build after config changes. |
| Missing Heap Dumps | Without heap dumps, diagnosing OOM failures is guesswork. | Always include -XX:+HeapDumpOnOutOfMemoryError to capture diagnostic data. |
Production Bundle
Action Checklist
- Tune JVM Args: Update
android/gradle.propertieswith-Xmx6g,-Xms2g, and G1GC flags. - Set Metaspace Limit: Add
-XX:MaxMetaspaceSize=1gto prevent metadata OOM. - Scale CI Resources: Configure
eas.jsonwithresourceClass: "large"for production builds. - Configure Node Memory: Export
NODE_OPTIONS="--max-old-space-size=4096"in CI environment. - Disable Parallel Builds: Set
org.gradle.parallel=falseto prevent memory multiplication. - Enable Heap Dumps: Include
-XX:+HeapDumpOnOutOfMemoryErrorfor diagnostic capability. - Reset State: Run
./gradlew --stopand./gradlew cleanafter configuration changes. - Verify Configuration: Check
./gradlew propertiesto confirm JVM args are applied.
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|---|---|---|
| Local Development | -Xmx4g, medium resources |
Balances performance and system responsiveness. | Free |
| Small App CI | -Xmx4g, medium resource class |
Sufficient for apps with few native modules. | Low |
| Large App CI | -Xmx6g, large resource class |
Required for heavy dependency trees and assets. | Moderate |
| Memory-Constrained CI | -Xmx4g, disable parallel, increase timeout |
Reduces peak memory usage at the cost of speed. | Low |
Configuration Template
Copy these templates to standardize your build configuration.
android/gradle.properties
org.gradle.daemon=true
org.gradle.jvmargs=-Xmx6g -Xms2g -XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError -XX:MaxMetaspaceSize=1g -Dfile.encoding=UTF-8
org.gradle.parallel=false
org.gradle.caching=true
android.useAndroidX=true
eas.json
{
"build": {
"production": {
"android": {
"resourceClass": "large",
"buildType": "app-bundle"
},
"env": {
"NODE_OPTIONS": "--max-old-space-size=4096"
}
},
"preview": {
"android": {
"resourceClass": "medium",
"buildType": "apk"
}
}
}
}
Quick Start Guide
- Edit Properties: Open
android/gradle.propertiesand apply the JVM args from the template. - Update EAS Config: Modify
eas.jsonto setresourceClass: "large"for production. - Stop Daemons: Run
./gradlew --stopto clear old processes. - Clean Build: Execute
./gradlew cleanand./gradlew cleanBuildCache. - Run Build: Trigger your release build. Monitor memory usage to ensure stability.
Mid-Year Sale β Unlock Full Article
Base plan from just $4.99/mo or $49/yr
Sign in to read the full article and unlock all tutorials.
Sign In / Register β Start Free Trial7-day free trial Β· Cancel anytime Β· 30-day money-back
