背景最近、レガシーなAndroidプロジェクトを最新のサードパーティSDKに対応させるためにアップグレードしていた際、壁にぶつかりました。コードエディタ上では問題なさそうに見えましたが、ビルドを実行した瞬間、Gradleが「Duplicate class」という厄介なエラーで失敗したのです。この特定の問題は、Kotlin 1.8.0のリリース以降、頻繁に発生するようになりました。これは、JetBrainsがkotlin-stdlib-jdk7とkotlin-stdlib-jdk8の機能をメインのkotlin-stdlibに統合することを決定したためです。
プロジェクトの依存関係に、古いライブラリ(分割されたartifactを参照)と新しいライブラリ(統合されたartifactを参照)が混在していると、Gradleは同じクラスが2つの異なる場所にあると認識し、ランタイムエラーを防ぐためにビルドを停止させます。
エラーメッセージビルド出力は通常、以下のようになります。
Duplicate class kotlin.collections.jdk8.CollectionsJDK8Kt found in modules kotlin-stdlib-jdk8 (org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.21) and kotlin-stdlib (org.jetbrains.kotlin:kotlin-stdlib:1.8.10)
Duplicate class kotlin.jvm.jdk8.JvmRepeatableKt found in modules kotlin-stdlib-jdk8 (org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.21) and kotlin-stdlib (org.jetbrains.kotlin:kotlin-stdlib:1.8.10)
...
依存関係グラフのデバッグ修正に取り掛かる前に、どのライブラリが依然として古いkotlin-stdlib-jdk8を引き込んでいるかを確認する必要がありました。Android StudioのターミナルでGradleのdependenciesコマンドを使用しました。
./gradlew :app:dependencies
出力結果を「kotlin-stdlib-jdk8」で検索(Ctrl+F)しました。その結果、あるネットワークライブラリの古いバージョンが、推移的(transitively)にKotlin 1.6.21を読み込んでいることがわかりました。一方で、私のプロジェクトはKotlin 1.8.10を使用するように設定されていました。1.8.10にはすでにそれらのクラスが含まれているため、競合は避けられませんでした。
解決策これに対処する方法はいくつかありますが、私が最も堅牢だと感じた方法は、GradleにすべてのKotlin stdlibモジュールを同じバージョンで解決させるか、冗長なものを完全に除外することです。
オプション1:依存関係の制約(Dependency Constraints)を使用する(推奨)これが最もクリーンなアプローチです。これにより、特定のモジュールが見つかった場合に特定のバージョンを使用するようGradleに指示し、古いライブラリからの推移的な依存関係を効果的に上書きします。これをapp/build.gradle(複数のモジュールがある場合はルートのbuild.gradle)に追加してください。
dependencies {
constraints {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0") {
because("kotlin-stdlib-jdk7は現在kotlin-stdlibの一部であるため")
}
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0") {
because("kotlin-stdlib-jdk8は現在kotlin-stdlibの一部であるため")
}
}
}
オプション2:グローバルにモジュールを除外する何らかの理由で制約(constraints)が機能しない場合は、ビルド設定全体から古いモジュールを強制的に除外できます。以下のブロックをプロジェクトレベルのbuild.gradleファイルに追加してください。
subprojects {
project.configurations.all {
resolutionStrategy.eachDependency { details ->
if (details.requested.group == 'org.jetbrains.kotlin') {
if (details.requested.name == 'kotlin-stdlib-jdk7' || details.requested.name == 'kotlin-stdlib-jdk8') {
details.useTarget "org.jetbrains.kotlin:kotlin-stdlib:${details.requested.version}"
}
}
}
}
}
オプション3:Kotlinのバージョンを更新するプロジェクトレベルのKotlinバージョンがすべてのモジュールで一貫していることを確認するだけで、問題が解決する場合もあります。build.gradle(プロジェクトレベル)で以下のように設定します。
plugins {
id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
}
また、app/build.gradleでimplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"を手動で定義していないことを確認してください。単にimplementation "org.jetbrains.kotlin:kotlin-stdlib"を使用するか、Kotlin Gradleプラグインに自動で処理させてください。
検証手順変更を適用した後、修正が確実であることを確認するために以下の手順を行いました。
- プロジェクトのクリーン:
Build > Clean Projectを実行します。- キャッシュの破棄:File > Invalidate Caches... > Invalidate and Restartを実行します。これにより、古いビルド成果物が残っていないことを確実にします。- ビルドの実行: ターミナルから./gradlew assembleDebugを実行します。- 依存関係の再確認:./gradlew :app:dependencies | grep kotlin-stdlibを実行し、1つのバージョンのみが使用されていることを確認します。## 学んだ教訓Kotlin 1.7から1.8への移行で標準ライブラリのパッケージ化方法が変更されました。これが、現代のAndroid開発における「Duplicate class」エラーの90%の原因です。jdk7/jdk8のartifactとベースのstdlibの間で競合が発生している場合、そのほとんどはバージョンの不一致が原因です。現在、私はconstraintsを使用することを推奨しています。なぜなら、依存関係ツリーを過剰に操作することなく、上書きが必要な理由をドキュメント化できるからです。

