PC パソコン

Android Studio「ApplicationExtensionImpl cannot be cast to BaseExtension」の原因と解決方法

Android Gradle Plugin(AGP)9.0以降へ更新すると、Gradle Sync中に「ApplicationExtensionImpl cannot be cast to BaseExtension」というClassCastExceptionが発生することがあります。

これはAGP 9でAndroid DSLの実装が新しい公開インターフェースへ切り替わった一方、プロジェクトの独自ビルドロジックや外部Gradleプラグインが、廃止予定のcom.android.build.gradle.BaseExtensionへキャストしていることが原因です。

代表的なエラー

java.lang.ClassCastException:
class com.android.build.gradle.internal.dsl.ApplicationExtensionImpl
cannot be cast to class com.android.build.gradle.BaseExtension

エラーはプロジェクトのコンパイル前、Gradleの設定フェーズで発生することが多く、build.gradleの読み込みやプラグイン適用中に停止します。

なぜAGP 9で発生するのか

AGP 7.x~8.xでは、Android DSLの実装が旧BaseExtensionと新しい公開インターフェースの両方を実装していました。そのため、古いプラグインが次のように取得しても動作していました。

val android = extensions.getByType(
    com.android.build.gradle.BaseExtension::class.java
)

AGP 9では新しい公開DSLインターフェースだけが使われ、内部実装は非公開の型へ変更されました。このため、ApplicationExtensionImplを旧BaseExtensionとして扱おうとするとキャストに失敗します。

キャッシュ破損ではなくAPI互換性の問題です。「Invalidate Caches」やgradlew cleanだけでは根本的に解決しません。

解決方法1:原因となる外部プラグインを更新する

スタックトレースに外部プラグイン名が表示されている場合は、そのプラグインをAGP 9対応版へ更新します。静的解析、Firebase関連、Flutter/React Nativeプラグイン、コード生成、APK名変更など、Android DSLへアクセスするプラグインが原因になることがあります。

./gradlew :app:tasks --stacktrace
./gradlew :app:assembleDebug --stacktrace

スタックトレース内で、最初に現れる自分のパッケージ名または外部プラグインのパッケージ名を確認します。AGP内部クラスの直前に表示される呼び出し元が手掛かりです。

解決方法2:独自Gradleプラグインを新DSLへ移行する

自作のConvention PluginやbuildSrcがBaseExtensionを参照している場合は、モジュール種別に対応した公開インターフェースへ変更します。

アプリモジュール

import com.android.build.api.dsl.ApplicationExtension

val android = extensions.getByType(
    ApplicationExtension::class.java
)

android.compileSdk = 37

ライブラリモジュール

import com.android.build.api.dsl.LibraryExtension

val android = extensions.getByType(
    LibraryExtension::class.java
)

android.compileSdk = 37

複数のAndroidモジュールへ共通設定を適用する場合も、内部実装クラスへキャストせず、AGPが公開しているApplicationExtension、LibraryExtensionなどを使用してください。

解決方法3:applicationVariantsをandroidComponentsへ移行する

旧BaseExtensionと一緒にapplicationVariants、libraryVariants、testVariantsなどを使用している場合は、新しいVariant APIへ移行します。

androidComponents {
    onVariants(selector().all()) { variant ->
        println("Variant: " + variant.name)
    }
}

ビルドタイプを無効化する処理は、旧variantFilterではなくbeforeVariantsを使用します。

androidComponents {
    beforeVariants(
        selector().withBuildType("release")
    ) { variantBuilder ->
        variantBuilder.enable = false
    }
}

Variant APIは設定タイミングが旧APIと異なるため、単純な型名の置換だけでなく、公式のAGP Recipesも確認してください。

一時的な回避策:android.newDsl=false

必要な外部プラグインがまだAGP 9へ対応していない場合、AGP 9では旧DSL実装を一時的に有効化できます。

# gradle.properties
android.newDsl=false

これは移行期間用の回避策です。Googleは旧DSLへのオプトアウトをAGP 10.0で削除すると案内しています。プラグイン更新またはビルドロジック移行を進め、対応後はこの設定を削除してください。

Built-in Kotlinにも未対応のプラグインがある場合、別途android.builtInKotlin=falseが必要になるケースがありますが、両者は別の機能です。必要性を確認せず同時に無効化しないでください。

原因箇所が見つからない場合

  1. プロジェクト全体でBaseExtensionを検索します。
  2. buildSrc、build-logic、Convention Pluginを確認します。
  3. applicationVariantsやlibraryVariantsも検索します。
  4. --stacktraceで外部プラグインを特定します。
  5. 一時的にプラグインを一つずつ外して原因を切り分けます。
./gradlew help --stacktrace
./gradlew :app:assembleDebug -Pandroid.debug.obsoleteApi=true

修正後の確認手順

  1. BaseExtensionへの参照が残っていないことを確認します。
  2. 外部プラグインがAGP 9対応版であることを確認します。
  3. Android StudioでGradle Syncを実行します。
  4. ./gradlew clean :app:assembleDebugを実行します。
  5. DebugだけでなくRelease、テスト、商品フレーバーもビルドします。
  6. android.newDsl=falseを外した状態で成功することを確認します。

注意点

AGPの内部型であるApplicationExtensionImplを直接参照する方法は避けてください。内部クラスは公開APIではなく、マイナーバージョン更新でも変更される可能性があります。

また、AGPを8系へ戻す方法は緊急回避にはなりますが、根本解決ではありません。利用しているプラグイン、Gradle、JDKの対応表を確認しながら新APIへ移行します。

まとめ

「ApplicationExtensionImpl cannot be cast to BaseExtension」は、AGP 9の新DSL実装を旧BaseExtensionへキャストしたために発生します。外部プラグインを更新するか、自作ビルドロジックをApplicationExtension・LibraryExtensionとandroidComponentsへ移行してください。android.newDsl=falseは一時的な回避策としてのみ使用します。

参考:Android Gradle Plugin 9.0 release notes/Write Gradle plugins/Android Gradle Plugin Recipes