Publishing & Deployment Issues May 10, 2026 14 min read

Godot 4.5 Android Export Fails at Gradle MergeReleaseJavaResource or Duplicate Class After AGP 8.7 Template Bump - How to Fix

Resolve Godot 4.5 Android custom export Gradle failures after AGP 8.7 template updates—duplicate classes, mergeReleaseJavaResource conflicts, manifest merger noise, and JDK 21 mismatches—without deleting plugins blindly.

By GamineAI Team

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:

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)

  1. Duplicate physical artifacts — two plugins copied the same .aar into android/plugins under different names.
  2. Duplicate Gradle coordinatesimplementation lines resolve androidx / play-services / billing artifacts at conflicting versions; merger sees two definitions of the same class.
  3. Embedded dependency inside plugin AAR plus explicit Gradle dependency for the same library (classic Firebase + Play combos).
  4. namespace missing on older Android library modules after AGP 8.x expectations—shows up as merger / manifest errors adjacent to resource merge failures.
  5. 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

  1. Run export from Godot with verbose logging if available; otherwise open the generated Android project directory Godot uses for custom build.
  2. Re-run the failing Gradle task from a terminal so the first Caused by: chain is visible.
  3. Highlight the fully qualified class name repeated in Duplicate class messages—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

  1. List res://android/plugins (and any documented alternate paths your plugins use).
  2. Search for duplicate basenames and suspicious pairs (play-billing variants, firebase folders copied twice).
  3. 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:

  1. Upgrade both plugins to maintainer builds tested against AGP 8.7 / Godot 4.5—often eliminates duplicates outright.
  2. If one plugin is abandoned, exclude its transitive duplicate explicitly in the plugin Gradle DSL Godot merges—exclude modules, not random packages.
  3. Apply resolutionStrategy.force only 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:

  1. Ensure each Android library module declares namespace in its build.gradle as required by AGP 8+.
  2. Update plugins from vendors—Godot plugin repos increasingly ship namespace fixes specifically for AGP 8.
  3. 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

  1. 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).
  2. Bump Gradle wrapper only along Godot template recommendations—too-new Gradle with older plugins triggers unrelated errors.
  3. After changes, run ./gradlew --stop then ./gradlew clean before 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:

  1. ./gradlew clean inside exported project
  2. Remove android/build intermediates Godot recreated
  3. 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.

  1. 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.
  2. Gradle fragment discovery — Each enabled plugin may contribute build.gradle snippets under android/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.
  3. 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.
  4. gradle.properties pins — Align android.namespace, compileSdkVersion, targetSdkVersion, and minSdkVersion with 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:

  1. Extract ClassName — search deps.txt for the owning artifact (often AndroidX core or Kotlin runtime fragments).
  2. Extract module paths X and Y — note whether one path is external coordinates while the other is files(...) pointing into android/plugins.
  3. 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:assembleRelease succeeds from cold shell twice
  • adb install smoke test on arm64 device reaches gameplay scene
  • No Duplicate class lines return after ./gradlew :app:lint when 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 .aar files that duplicate Gradle-managed coordinates
  • Pin Godot export template revision and Gradle plugin template revision in version control
  • Run assembleRelease on CI using the same JDK major as artists’ Editor exports
  • After adding any monetization SDK, re-run dependencies immediately—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

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.