Fixing React Native Android Builds Failing With "Duplicate Class Found" Errors From AndroidX and Jetifier Conflicts
A React Native Android build that was working fine suddenly fails with a wall of "Duplicate class ... found in modules" errors right after adding a new native dependency — even though nothing about the project's own code changed. The real cause is almost always two different dependencies pulling in both the old support-library and the new AndroidX version of the same class, and Gradle refusing to pick one for you.
The Problem
An Android build that compiled cleanly a moment ago starts failing the instant a new native module is added — or even just after bumping a dependency's version — with a long list of errors shaped like Duplicate class androidx.loader.content.Loader found in modules jetified-support-core-ui ... and jetified-core-1.x.x .... Nothing in the app's own source changed; the failure happens entirely during Gradle's dependency resolution and class-merging step, before a single line of app code is even compiled.
Why It Happens
Android's old support library and its AndroidX replacement define classes under different package names, but some classes still collide
The migration from the legacy android.support.* packages to androidx.* was meant to be a clean rename, but a handful of transitive dependencies in the native module ecosystem still pull in old support-library artifacts directly, while the rest of the project pulls in their AndroidX equivalents. Gradle's Jetifier tool rewrites old support-library bytecode to its AndroidX equivalent at build time specifically to bridge this gap — but when both the original and the jetified version of the same class end up on the classpath simultaneously, the build has two definitions of one class with no way to know which one is correct.
A new or updated native dependency often resolves a different, incompatible version of a shared transitive dependency
Most React Native native modules depend on common Android libraries — AndroidX Core, AppCompat, Lifecycle, Fragment — but they don't all pin the exact same version. Adding or upgrading just one native module can shift which version of a shared transitive dependency Gradle resolves project-wide, and if that shift reintroduces a module still shipping the legacy support-library artifact, the duplicate-class conflict appears even though the module just added looks unrelated to the error.
Gradle's dependency resolution picks one version per coordinate, but can still end up with both legacy and AndroidX artifacts present
Gradle deduplicates by resolving a single version for each exact dependency coordinate (group:artifact), but a legacy support-library artifact and its AndroidX counterpart are different coordinates entirely — com.android.support:support-core-ui versus androidx.legacy:legacy-support-core-ui, for instance. Gradle has no way to know these two coordinates define overlapping classes, so its normal version-resolution logic doesn't catch the overlap; only the later class-merging step, which actually inspects compiled class names, does.
The Fix
1. Confirm android.useAndroidX and android.enableJetifier are both enabled in gradle.properties
# android/gradle.properties
android.useAndroidX=true
android.enableJetifier=true
Both flags need to be true together — useAndroidX alone doesn't rewrite the legacy dependencies that other libraries still pull in, and enableJetifier without useAndroidX has nothing to rewrite them to. Nearly every current React Native template sets both by default, so if either is missing or was disabled while debugging something else, this is the first thing to restore.
2. Identify exactly which two artifacts are colliding using Gradle's dependency insight task
cd android
./gradlew :app:dependencies --configuration releaseRuntimeClasspath > deps.txt
grep -B5 "support-core-ui|legacy-support" deps.txt
The error message names the conflicting class and the two jetified module names, but tracing those module names back to the actual dependency declarations that pulled them in requires the dependency tree. This confirms which specific native module (or which version of it) is the one still resolving a legacy artifact, rather than guessing from the error text alone.
3. Force-exclude the legacy artifact once the source dependency is identified
// android/app/build.gradle
dependencies {
implementation("com.somevendor:native-module:1.2.3") {
exclude group: "com.android.support", module: "support-core-ui"
}
}
Excluding the specific legacy group and module from the dependency that's pulling it in removes the duplicate without needing to patch or fork the native module itself — Jetifier and the rest of the AndroidX ecosystem then supply the equivalent class through the normal AndroidX artifact already present in the project.
4. As a last resort, force a single resolved version across the whole project with a resolutionStrategy
// android/build.gradle
allprojects {
configurations.all {
resolutionStrategy {
force "androidx.core:core:1.13.1"
}
}
}
When several unrelated dependencies each pull in slightly different versions of the same AndroidX artifact, forcing one version project-wide resolves the ambiguity directly, though it should be used narrowly — only for the specific artifact actually in conflict — since forcing versions broadly can mask real incompatibilities between a native module and a newer AndroidX release.
Why This Works
The duplicate-class error isn't a sign of broken code; it's Gradle correctly refusing to guess between two different artifacts that happen to define the same class under the same name. Enabling Jetifier closes most of this gap automatically for the ecosystem at large, but a lingering legacy dependency buried a few levels down in a native module's own dependency tree can still slip through — finding and excluding that one specific artifact, or forcing a single resolved version where exclusion isn't practical, removes the ambiguity at its actual source rather than papering over the symptom.
Conclusion
A "Duplicate class found" error after adding or updating a native dependency is a dependency-resolution conflict between the legacy support library and AndroidX, not a sign anything in the app's own code is wrong. Verifying both AndroidX flags are enabled, tracing the exact colliding artifact through the dependency tree, and excluding or forcing a single version at the source turns an opaque wall of build errors into a one-line Gradle fix.
