Godot 4.5 Android Export Fails at Gradle MergeReleaseJavaResource or Duplicate Class After AGP 8.7 Template Bump - How to Fix
Problem: Godot 4.5 Android export (often with Use Custom Build enabled and Godot Android plugins) reaches Gradle and stops with errors such as Duplicate class (usually naming androidx.*, Kotlin runtime fragments, Play libraries, or Firebase artifacts), failures around :mergeReleaseJavaResource, DexArchiveMerger, or manifest merger complaints about conflicting package / applicationId attributes after your Android Gradle Plugin (AGP) template jumped toward 8.7.x and newer Kotlin Gradle Plugin pins.
Who is affected: Jam teams and micro studios shipping 2026 Google Play builds where target API rows moved forward, Gradle pulled newer transitive libraries, or two plugins each vendor the same .aar under different paths. Desktop-only projects rarely see this until Android export turns on.
Fastest safe fix: Prove whether the conflict is duplicate artifacts on disk versus version skew on one classpath. Inventory android/plugins for overlapping .aar / .jar copies, run one Gradle dependency report on the exported project, then remove or exclude exactly one owner of each duplicated class—never both.
How to confirm success: ./gradlew :app:assembleRelease (or Godot’s equivalent invoked export step) completes twice after gradlew clean, and installable AAB/APK launches past splash without ClassNotFoundException from stripped libraries.
Direct answer: mid-2026 AGP and Kotlin bumps tighten classpath merging. Godot plugins that were “fine” under older templates often collide because two modules now resolve the same AndroidX or billing artifact at different versions, or because a plugin ships a fat .aar that already embeds classes your Gradle dependency block pulls again.
Why this spikes now
Google Play policy rows and Godot 4.5 export templates encourage staying current on compile/target SDK bands. AGP 8.7 lines assume JDK 17+ defaults (teams on mismatched JDK 21 tooling sometimes see secondary failures), and R8 / resource merging became stricter about duplicate packaged libraries. The failure is rarely Godot game scripts—it is Android packaging hygiene.
Official anchors worth keeping open:
- Godot Android documentation for export prerequisites and custom build flow
- Android Gradle Plugin release notes for breaking changes around your pinned AGP
- Gradle JVM toolchain docs when Editor JDK and CI JDK disagree
For packaging mistakes unrelated to duplicate classes (keystore, permissions, min SDK), start with Godot 4.4 Android Export Crashes on Launch - Mobile Export Fix. For Vulkan instant-close after install, use Godot 4.5 Android Export Crashes on Startup with Vulkan.
Root causes (pick buckets before you delete gameplay code)
- Duplicate physical artifacts — two plugins copied the same
.aarintoandroid/pluginsunder different names. - Duplicate Gradle coordinates —
implementationlines resolve androidx / play-services / billing artifacts at conflicting versions; merger sees two definitions of the same class. - Embedded dependency inside plugin AAR plus explicit Gradle dependency for the same library (classic Firebase + Play combos).
namespacemissing on older Android library modules after AGP 8.x expectations—shows up as merger / manifest errors adjacent to resource merge failures.- JDK / Gradle wrapper skew — wrapper too old for AGP 8.7 or wrong JVM running Gradle, producing misleading cascade errors after the first real fault.
Step 1 - Capture the exact Gradle failure text once
- Run export from Godot with verbose logging if available; otherwise open the generated Android project directory Godot uses for custom build.
- Re-run the failing Gradle task from a terminal so the first
Caused by:chain is visible. - Highlight the fully qualified class name repeated in
Duplicate classmessages—that tells you which library family to reconcile.
Verification checkpoint: you can name whether the duplicate is AndroidX, Kotlin, Google Play, Firebase, or a third-party SDK.
Step 2 - Inventory plugin binaries for literal duplicates
- List
res://android/plugins(and any documented alternate paths your plugins use). - Search for duplicate basenames and suspicious pairs (
play-billingvariants,firebasefolders copied twice). - If two plugins ship the same vendor SDK, pick one authoritative plugin build or merge configs—do not keep both full SDK drops.
Common mistake: copying an .aar manually while the plugin’s gradle snippet also downloads the same artifact remotely—Gradle sees two sources.
Step 3 - Generate a dependency report for the app module
From the exported Android project root (adjust module path if not :app):
./gradlew :app:dependencies --configuration releaseRuntimeClasspath > deps.txt
Open deps.txt and search for the artifact family tied to your duplicate class (example: androidx.core, kotlin-stdlib, play-services-base, billing).
Verification checkpoint: you see two version branches converging on different revisions—note the requested vs selected lines.
Step 4 - Resolve Gradle conflicts with a single authority per coordinate
Preferred order:
- Upgrade both plugins to maintainer builds tested against AGP 8.7 / Godot 4.5—often eliminates duplicates outright.
- If one plugin is abandoned, exclude its transitive duplicate explicitly in the plugin Gradle DSL Godot merges—exclude modules, not random packages.
- Apply
resolutionStrategy.forceonly as a temporary pin with a ticket to remove it—forced pins hide upstream fixes until they explode on the next bump.
Example pattern (illustrative—adapt names to your tree):
configurations.all {
resolutionStrategy {
force "androidx.core:core-ktx:1.12.0"
}
}
Use force only after dependencies shows exactly which coordinate pair fights.
Step 5 - Namespace and manifest merger hygiene for AGP 8.x
If errors mention package attribute in AndroidManifest.xml library manifests or Namespace not specified:
- Ensure each Android library module declares
namespacein itsbuild.gradleas required by AGP 8+. - Update plugins from vendors—Godot plugin repos increasingly ship
namespacefixes specifically for AGP 8. - Avoid editing generated manifests blindly; fix at plugin source so export regeneration does not wipe edits.
Step 6 - Align JDK, Gradle wrapper, and AGP trio
- Confirm Android Studio or standalone Gradle uses JDK 17 minimum for AGP 8.7-class lines (many teams run JDK 21 fine—pick one documented JDK for CI and local).
- Bump Gradle wrapper only along Godot template recommendations—too-new Gradle with older plugins triggers unrelated errors.
- After changes, run
./gradlew --stopthen./gradlew cleanbefore retrying export.
Step 7 - Clear caches only after duplicates are understood
Deleting ~/.gradle/caches is a last resort because it costs time and can mask configuration bugs.
Safer sequence:
./gradlew cleaninside exported project- Remove
android/buildintermediates Godot recreated - Only then consider narrowing cache deletes (single repo cache) under timeboxed troubleshooting
Step 8 - Map Godot Android export modes to Gradle surfaces
Godot exposes multiple Android paths; duplicates usually appear only when custom build merges plugin Gradle snippets into a full Android project.
- Export preset sanity — Open Project → Export → Android and confirm Use Custom Build matches what your plugins document. Some plugins require custom build even when one-click deploy worked without it on older templates.
- Gradle fragment discovery — Each enabled plugin may contribute
build.gradlesnippets underandroid/plugins/<plugin>/or merge hooks documented by the vendor. Collect those filenames before editing generated output so you return fixes to the plugin copy in repo. - Library versus source embedding — Decide whether a dependency must ship as Maven coordinate only or as local binary. Mixing both without exclusions is the duplicate-class express lane.
gradle.propertiespins — Alignandroid.namespace,compileSdkVersion,targetSdkVersion, andminSdkVersionwith Play policy rows and plugin minimums. Mismatched SDK pins rarely emit “duplicate class” directly but trigger tooling upgrades that expose latent duplicates.
Reading duplicate-class logs like a dependency detective
Gradle prints duplicates as Duplicate class a.b.c.ClassName found in modules X and Y. Treat that triplet as ground truth:
- Extract
ClassName— searchdeps.txtfor the owning artifact (often AndroidX core or Kotlin runtime fragments). - Extract module paths
XandY— note whether one path isexternalcoordinates while the other isfiles(...)pointing intoandroid/plugins. - Prefer deleting redundant
files(...)wiring before you exclude Maven transitives—local duplicates are visible on disk and revert cleanly.
If Gradle lists kotlin.Metadata duplicates, you almost always have two Kotlin runtime streams. Align Kotlin Gradle Plugin versions across plugins rather than deleting random Kotlin classes.
Common Gradle error strings tied to this failure family
| Log phrase | Usually means | First move |
|---|---|---|
Duplicate class |
Two artifacts define identical bytecode | Dependency report + remove one owner |
Execution failed for task ':app:mergeReleaseJavaResource' |
Resource packaging clash after classpath duplication | Same as duplicate class; fix JVM libs first |
Manifest merger failed with package= warnings |
AGP 8 namespace migration incomplete | Add namespace per library module |
Unable to find Gradle tasks |
Wrong working directory or stale wrapper | Run from exported Android root |
Minimum supported Gradle version is X |
Wrapper too old for AGP | Bump wrapper per Godot template guidance |
Alternative fixes and branches
Branch A - Duplicate Kotlin metadata classes
Often means two kotlin-stdlib revisions—upgrade plugins together or exclude the older stdlib transitively.
Branch B - Play Billing duplicate
Ensure only one billing client dependency exists—either plugin-packaged or Gradle-declared, not both at mismatched versions.
Branch C - MultiDex noise disguising duplicates
Fix duplicates first. Only enable MultiDex if method count legitimately requires it—Dex merger errors sometimes list duplicates ahead of limit breaches.
Verification checklist before you ship
./gradlew :app:assembleReleasesucceeds from cold shell twiceadb installsmoke test on arm64 device reaches gameplay scene- No
Duplicate classlines return after./gradlew :app:lintwhen you run lint in CI - Dependency report archived beside release tuple if governance packets require build reproducibility evidence
Prevention tips for the next template bump
- Maintain a plugin acceptance checklist: forbid vendoring
.aarfiles that duplicate Gradle-managed coordinates - Pin Godot export template revision and Gradle plugin template revision in version control
- Run
assembleReleaseon CI using the same JDK major as artists’ Editor exports - After adding any monetization SDK, re-run
dependenciesimmediately—do not wait until submission week
FAQ
Does disabling Custom Build fix duplicates?
Sometimes it avoids Gradle merges because fewer plugins apply—but you lose capabilities plugins require. Treat disable as a diagnostic toggle, not the production fix.
Will exporting APK instead of AAB dodge Dex merging?
No. Duplicate classes fail during merge regardless of distribution format.
Should I downgrade AGP below 8.7?
Only as a temporary sandbox experiment. Play policy pressure usually pushes you back upward—fix classpath hygiene instead.
Why does ./gradlew :app:dependencies look fine but export still fails?
Godot may regenerate Gradle files between attempts. Capture deps.txt immediately after the failing export and compare checksums of settings.gradle / build.gradle across attempts. Regeneration often reintroduces stale implementation lines unless fixes live in plugin repos.
Can Proguard or R8 hide duplicates?
Shrinking can mask some collisions until release minification runs. Fix duplicates in debuggable assemble paths first—otherwise your CI release lane fails while editor deploy looked “lucky.”
Related links
- Godot 4.4 Android Export Crashes on Launch - Mobile Export Fix
- Godot 4.5 Android Export Crashes on Startup with Vulkan
- Godot docs — Exporting for Android
Bookmark this page the next time Play submission week coincides with a fresh export template pull—the duplicate class list is annoying, but the fix path is systematic once you read Gradle’s dependency truth.