On this page · 10 sections
- What AGP 9.0 actually changed
- What breaks in a Flutter project specifically
- The configuration changes, exactly
- Testing AGP 10.0 behaviour before AGP 10.0 ships
- What AGP 10.0 removes, and why it decides your sequencing
- The two dates that disagree
- The upgrade decision
- India-specific considerations
- A migration checklist that survives contact with a real codebase
- FAQ
Summary. Android Gradle plugin 9.0, released as 9.0.1 in January 2026, removed support for applying the Kotlin Gradle Plugin, and that single change is what breaks Flutter Android builds. Flutter 3.44 handled it defensively, writing two opt-out flags into your project, android.builtInKotlin=false and android.newDsl=false, so builds kept working while the plugin ecosystem caught up. That holding pattern ended on 12 August 2026, when Flutter 3.47.0 reached the stable channel (3.47.1 followed on 19 August). Flutter's own breaking-change guide now states it plainly: "Flutter 3.47 added support for enabling built-in Kotlin (android.builtInKotlin=true) once an app and all its plugins have migrated." The migration you could only prepare for two weeks ago is now one you can finish. The floors moved with it. Flutter 3.47 is verified against a different dependency matrix than the one AGP 9.0's own release notes describe: Java 17, Kotlin Gradle Plugin 2.4.0, AGP 9.1.0 and Gradle 9.3.1. If you planned your upgrade against AGP 9.0's KGP 2.2.10 and Gradle 9.1.0 figures, your CI images are already a step behind.
This guide covers what actually fails, the exact configuration changes, what Flutter 3.47 moved, how to test AGP 10.0 behaviour early, and the timing call for teams shipping production Flutter apps.
What AGP 9.0 actually changed
Two changes in AGP 9.0 matter to Flutter teams, and they are separate.
The first is built-in Kotlin. Before AGP 9.0, an Android module that contained Kotlin source had to apply the Kotlin Android plugin, org.jetbrains.kotlin.android (also written as kotlin-android). AGP 9.0 compiles Kotlin itself and enables that behaviour by default. As the Android build documentation puts it, "Starting with Android Gradle plugin (AGP) 9.0, support for applying the Kotlin Gradle Plugin (KGP) has been removed." Applying it now is not redundant — it is an error.
The second is the DSL. AGP versions 7.x and 8.x still exposed the old DSL types such as BaseExtension, which also implemented the newer public interfaces. AGP 9.0 uses the new interfaces exclusively, and the implementing classes are fully hidden. Access to the old variant API goes with them. If a third-party Gradle plugin in your build still reaches for the old types, you get a specific failure:
java.lang.ClassCastException: class com.android.build.gradle.internal.dsl.ApplicationExtensionImpl$AgpDecorated_Decorated cannot be cast to class com.android.build.gradle.BaseExtension
Both changes have an opt-out flag, and Flutter sets both for you.
Compatibility floors you cannot negotiate
AGP 9.0's compatibility table is short and strict:
Component | Minimum version | Default version | Practical consequence for Flutter teams ---|---|---|--- Gradle | 9.1.0 | 9.1.0 | A Gradle wrapper upgrade lands before anything else works SDK Build Tools | 36.0.0 | 36.0.0 | CI images pinned to older build-tools need rebuilding NDK | Not applicable | 28.2.13676358 | Native plugins with pinned NDK versions need a look JDK | 17 | 17 | Build agents still on JDK 11 or 17-only images must move to 17 Max compile SDK | — | API level 36.1 | AGP 9.0 will not compile against a higher API level
AGP 9.0 also pulls KGP 2.2.10 as a runtime dependency. You no longer declare a KGP version, and if your build declares one below 2.2.10, Gradle upgrades it automatically. KSP moves to 2.2.10-2.0.2 to match. You can only downgrade KGP if you have opted out of built-in Kotlin, and the floor for a downgrade is KGP 2.0.0.
The floors moved on 12 August 2026
Those numbers come from AGP 9.0's own release notes, and they are no longer the ones a Flutter team should build CI images against. The Flutter 3.47 release post publishes a separate, higher verified matrix:
Component | AGP 9.0 release notes | Flutter 3.47 verified matrix ---|---|--- Java | 17 | 17 Kotlin Gradle Plugin | 2.2.10 (runtime dependency) | 2.4.0 Android Gradle plugin | 9.0.1 | 9.1.0, described as the newest compatible with KGP 2.4.0 Gradle | 9.1.0 | 9.3.1, the minimum required for AGP 9.1.0
Read the right-hand column as the target if you are on Flutter 3.47. A Gradle wrapper pinned to 9.1.0 satisfies AGP 9.0 and not AGP 9.1.0, and that is the kind of gap that surfaces as a red CI job rather than a helpful error. Flutter also recommends using the standard API level variables the SDK vends in your build files rather than hardcoding levels, so future releases move them for you.
What breaks in a Flutter project specifically
Flutter's Android tooling sits between your app and AGP, so the failure modes are not identical to a native Android project.
The app 's own `build.gradle` breaks first. A Flutter app created before 2026 almost certainly applies kotlin-android in android/app/build.gradle or build.gradle.kts, and sets jvmTarget inside a kotlinOptions block. Under built-in Kotlin, both have to go, replaced by a kotlin { compilerOptions { … } } block.
Plugins break second, and they break harder. A Flutter app pulls in Android build logic from every plugin in its dependency tree. Flutter tracks this in issue #181383, "Flutter plugins should support AGP 9.0.0". Community plugins hit the same wall — wakelock_plus filed issue #117, titled "AGP 9 incompatibility — kotlin-android plugin must be removed". You do not control this code, and a single unmigrated transitive plugin is enough to block the whole build once built-in Kotlin is on.
Add-to-app projects break silently. Flutter's migrator tool writes the two opt-out flags into gradle.properties when you run flutter run or flutter build apk. It cannot do that for an add-to-app Android host, because the host is a pure native Android project and the Flutter tool never runs during its build. Those teams have to add the flags by hand.
KMP modules need a different plugin entirely. If any module applies org.jetbrains.kotlin.multiplatform alongside com.android.library or com.android.application, that combination is no longer compatible with the new DSL. AGP 9.0 introduces com.android.kotlin.multiplatform.library for multiplatform modules, and an app module that mixes KMP with the Android application plugin has to be split into a separate subproject.
The warning you are probably already seeing
Teams that upgraded to Flutter 3.44 report a build-time warning naming every plugin that still applies KGP, with the line that future Flutter versions will fail the build if the app uses such plugins. Treat that warning as your migration backlog. It is the only inventory of your problem that anyone is going to hand you.
The configuration changes, exactly
Flutter's stable default today is the opt-out. Both flags belong in <flutter-project>/android/gradle.properties:
android.newDsl=false android.builtInKotlin=false
The Flutter migrator adds these automatically on the next flutter run or flutter build apk, and building through Android Studio tooling adds them too. The mechanism is tracked in Flutter issue #183910. Add-to-app hosts get them manually. If your app never applied kotlin-android at all, Flutter's documentation is explicit that you only need android.newDsl=false and no further migration steps.
When you are ready to move off the opt-out, the app-side change is small. Before, in android/app/build.gradle.kts:
plugins { id("com.android.application") id("kotlin-android") // ... }
android { // ... kotlinOptions { jvmTarget = JavaVersion.VERSION_17.toString() } // ... }
After, with the plugin and the kotlinOptions block removed and compiler options moved into the kotlin {} block:
plugins { id("com.android.application") // ... }
android { // ... }
kotlin { compilerOptions { jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 } }
The Groovy form is the same edit against apply plugin: 'kotlin-android'. An add-to-app host using version catalogs drops alias(libs.plugins.kotlin.android) from its plugins block and makes the same kotlin {} addition.
Then, and only after every plugin in the tree is migrated, flip the flag:
android.builtInKotlin=true
Flutter's version note on that step used to be the whole timing story: enabling built-in Kotlin requires Flutter 3.47 or later, and 3.47 was not stable. It is now. Flutter 3.47.0 landed on the stable channel on 12 August 2026 with Dart 3.13.0, and 3.47.1 followed on 19 August with Dart 3.13.1. Setting android.builtInKotlin=true is a migration you can complete today, provided the plugin tree is clear.
One guard rail before you reach for the flag. Flutter's guide carries an explicit warning that the migration applies only to apps and plugins that already use KGP: if your project never applied the Kotlin Gradle Plugin, do not migrate to built-in Kotlin at all. In that case android.newDsl=false is the only property you need, and there is nothing further to do.
Testing AGP 10.0 behaviour before AGP 10.0 ships
The useful part of Google's roadmap is that you can enforce the future's strictness on a current AGP 9.x release. Set both flags to true in gradle.properties and your build validates against the API surface AGP 10.0 will require:
For a large modular codebase, all-or-nothing is impractical, so AGP 9.4.0-alpha04 added per-subproject opt-out:
android.newDsl=true android.newDsl.optOut=:lib
Google is clear that android.newDsl.optOut is a temporary aid for the 9.x line and disappears with android.newDsl in AGP 10.0. There is a matching module-level control for Kotlin — setting enableKotlin = false inside a module's android {} block removes the Kotlin compiler task for modules with no Kotlin code, which also trims build time and avoids a Kotlin standard library dependency.
For a Flutter app, run this experiment in a branch, not in your release pipeline. The point is to produce the list of plugins that fail, not to ship from it.
What AGP 10.0 removes, and why it decides your sequencing
AGP 10.0 completes a multi-year move to a fully lazy, configuration-cache-compatible build model. The legacy BaseVariant API was eager and task-centric; the androidComponents {} API is lazy and artifact-centric, and removes all references to Task and TaskProvider. The removals are what make this a hard deadline rather than a suggestion:
Removed in AGP 10.0 | Replacement | Who this hits in a Flutter build ---|---|--- applicationVariants, libraryVariants, testVariants, unitTestVariants | androidComponents.onVariants() | Custom flavour logic in android/app/build.gradle variantFilter block | androidComponents.beforeVariants() with selectors | Teams disabling debug or flavour combinations android.newDsl, android.builtInKotlin opt-out flags | None — flags removed, modern DSL and built-in Kotlin enforced | Every Flutter app currently relying on the Flutter-written defaults Transform API | Artifacts API plus AsmClassVisitorFactory | Bytecode-rewriting plugins such as older analytics or obfuscation tooling registerJavaGeneratingTask(), registerResGeneratingTask() | variant.sources.java.addGeneratedSourceDirectory(...) | Codegen wired into the Android build sdkDirectory, ndkDirectory, bootClasspath, adbExecutable | androidComponents.sdkComponents | Custom CI tasks that shell out to adb buildConfigField(), resValue() eager mutation | variant.buildConfigFields.put(...), variant.manifestPlaceholders.put(...) | Build-time config injection, very common in Flutter apps with flavours
That last row is the one that catches Flutter teams, because per-flavour buildConfigField entries are how most apps inject API base URLs and environment names into the Android side.
Google's recommended sequence is to fix AGP 9.x deprecation warnings first and reach a state where the build is warning-free without either opt-out flag, then move to 10.0. The AGP Upgrade Assistant in Android Studio (Tools > AGP Upgrade Assistant) automates part of it, and there are agent skills for the upgrade in the Android skills repository and a Kotlin Multiplatform equivalent from JetBrains. Google also runs a global tracking bug for Variant API migration blockers, worth checking before you file anything.
The two dates that disagree
Anyone planning a 2026 roadmap around this hits a documentation conflict, and it is worth naming because the two pages are both official and both current.
Source | Page last updated | What it says about AGP 10.0 ---|---|--- AGP 9.0.1 release notes | Current release-notes page for the January 2026 release | "The ability to opt-out will be removed in AGP 10.0 (mid-2026)." AGP DSL/API migration timeline | 22 July 2026 | Section headed "AGP 10.0 (late 2026)"; timeframes flagged as estimates JetBrains Kotlin blog | 15 May 2026 update | Opt-out "will no longer work in AGP 10.0, which is expected sometime in 2026"
The roadmap page carries the later revision date and explicitly labels its timeframes as estimates subject to change, so "late 2026" is the number to plan against. Mid-2026 has already passed without the release, which settles the question in practice. Plan for a release inside 2026 and a Flutter version that supports built-in Kotlin arriving before it.
The upgrade decision
Three positions are defensible right now, depending on what you ship.
Stay on the opt-out and prepare. Still correct for most production Flutter apps, but the reason has changed. Two weeks ago the blocker was the framework; now it is your dependency tree. Flutter 3.44 writes the flags and builds pass, so the pressure is low — but "we cannot do it yet" is no longer true, and the audit you deferred is now the only thing standing between you and a finished migration.
Test strict mode in a branch now. Correct for teams with heavy custom Gradle logic — flavour matrices, bytecode transforms, generated sources. Those are the AGP 10.0 removals with real engineering behind them, and finding out in a branch costs a week; finding out when the flags disappear costs a release.
Migrate the app-side configuration immediately. Correct for everyone. Removing kotlin-android from your own build.gradle and moving jvmTarget into kotlin { compilerOptions { … } } is a contained edit that is compatible with the opt-out still being in place. There is no reason to carry it.
Upgrade to 3.47 and finish, if your plugin list is short. New, and correct for apps with a small or fully first-party plugin tree. This is the first window in which the whole migration can be closed rather than staged, and closing it removes a flag you would otherwise have to remember when AGP 10.0 lands. Teams that already keep their plugin set tight get to be done. Most flutter app development work we see does not have that luxury, because the payments and analytics SDKs are the long pole — but it is worth checking before assuming.
What is not defensible is doing nothing until Flutter forces the issue, and the argument for delay got materially weaker on 12 August. Flutter's own wording is that KGP support was added temporarily "while apps and plugins migrate", and will be removed in a future version. The migration you control is small. The one you do not control — third-party plugins — is the one with the long tail, and it starts with filing issues, which is why Flutter ships a copy-paste issue template for exactly that.
Márton Braun, Kotlin Developer Advocate at JetBrains, put the timing plainly in the Kotlin team's migration post: "We recommend making these configuration changes in your existing projects as soon as possible to ensure smooth upgrades to the latest versions of AGP in the future."
What this costs in CI
The compute cost of the migration is small and it is routinely overestimated. Running a parallel strict-mode Android job alongside your existing pipeline roughly doubles the Android leg of the build, not the whole matrix — a second run of one job, not a second run of everything. Hosted-runner rates vary by operating system, core count and plan, and they are re-priced often enough that any figure quoted in an article is a liability rather than a help; read them off your own billing page before you build a business case. What the number will tell you when you do is that the runner minutes are noise. The real cost is engineering hours spent inventorying plugins and chasing maintainers, and no amount of CI budget shortens that.
India-specific considerations
Indian product teams and offshore engineering groups carry two extra constraints here.
Build agents are frequently self-managed on cloud VMs rather than GitHub-hosted runners, which means the JDK 17 and Gradle floors — 9.1.0 for AGP 9.0, 9.3.1 for the AGP 9.1.0 that Flutter 3.47 verifies against — need an image rebuild rather than a config change. That is a platform-team ticket, and it should be raised before the app-side migration, not after. Teams without a resident Android build owner tend to hire flutter developers who can carry the Gradle floor and the plugin tail together, because both land in the same week.
The plugin tail is also longer for teams shipping India-market apps, because payments, KYC and messaging SDKs are often distributed as Android libraries wrapped in thin Flutter plugins by the vendor. Those wrappers update on the vendor's schedule, not the ecosystem's. If your app depends on a payment gateway plugin, ask the vendor for their AGP 9 built-in Kotlin timeline now, in writing, and put the answer in your release plan. Where those SDKs also handle personal data, the same upgrade window is a reasonable moment to re-check consent and data-handling behaviour against the Digital Personal Data Protection Act 2023, since you are already touching the Android build and its dependency set.
For teams still working through the wider 3.44 upgrade, the Flutter 3.44 production upgrade guide covers the release as a whole, and the related Impeller default rollout on Android and iOS and the CocoaPods sunset and Swift Package Manager migration are the other two forced migrations in the same window. On the platform side, Android 17 API 37 behaviour changes will land in the same planning cycle as AGP 10.0, and the Material UI and Cupertino UI package migration affects the same pubspec.yaml you are already auditing.
A migration checklist that survives contact with a real codebase
1\. Record your current state: Flutter version, AGP version, Gradle wrapper version, JDK on every build agent.
1\. Confirm android.newDsl=false and android.builtInKotlin=false exist in android/gradle.properties; add them by hand for add-to-app hosts.
1\. Remove kotlin-android and the kotlinOptions block from your own module, add kotlin { compilerOptions { jvmTarget = … } }, and confirm the build is unchanged.
1\. Capture the KGP warning output and turn the named plugins into a tracked list.
1\. For each plugin, check its changelog for a built-in Kotlin release; where none exists, file an issue using Flutter's template.
1\. In a branch, set both flags to true and record every failure. Use android.newDsl.optOut for modules you cannot fix yet.
1\. Audit custom Gradle logic against the AGP 10.0 removal table above, replacing applicationVariants with androidComponents.onVariants() and eager buildConfigField calls with variant.buildConfigFields.put(...).
1\. Rebuild CI images for JDK 17, SDK Build Tools 36.0.0, and the Gradle version your AGP line requires — 9.1.0 for AGP 9.0, 9.3.1 for AGP 9.1.0 under Flutter 3.47.
1\. Re-run the branch experiment monthly; the plugin ecosystem is the variable, not your code.
1\. Upgrade to Flutter 3.47, stable since 12 August 2026, and once the plugin list is clear flip android.builtInKotlin=true in the main branch. Skip this step entirely if your project never applied KGP.
The real cost here is usually the plugin audit, not the code.