Flutter

Flutter Version Matrix: Which Xcode and Gradle Each Needs

Every Flutter upgrade drags a toolchain behind it, and the release notes rarely list all of it in one place. This Flutter version matrix is for developers who maintain an app across releases, run CI on pinned machines, or inherited a project and need to know what it will build with. It covers every stable minor from 3.0 to 3.47.7. For each one, it lists the Xcode version flutter doctor demands and the iOS deployment target the template sets. It also lists the Gradle, Android Gradle Plugin (AGP), Kotlin and Java versions the template ships. Finally, it gives the versions below which the Android build refuses to run at all. None of the numbers come from memory or from third-party blogs. A script read them from the Flutter source at each release tag, and the script and its raw output appear below.

How the Flutter Version Matrix Was Read

Flutter does not publish a compatibility table. Instead, it hardcodes its floors in four places inside flutter_tools, and each one answers a different question:

  • lib/src/macos/xcode.dart holds the Xcode version below which flutter doctor reports an error, plus a recommended version that only produces a warning
  • lib/src/macos/cocoapods.dart holds the same pair for CocoaPods
  • lib/src/android/gradle_utils.dart holds the versions flutter create writes into a new Android project
  • The Flutter Gradle plugin’s dependency checker holds the versions below which an Android build throws an error

The iOS deployment target comes from the Xcode project template that flutter create copies. The script below fetches all of these at one or more release tags and prints one line per tag. In practice, it is the fastest way to answer “what does this Flutter version expect” without installing it.

Checked 2026-10-14
Windows 11 Pro 25H2 (build 26200), Git Bash 5.2.37, curl 8.12.1
Source: github.com/flutter/flutter at each tag listed, raw files over HTTPS
Release index: storage.googleapis.com/flutter_infra_release/releases/releases_windows.json
Current stable on the day of checking: 3.47.7, released 2026-10-08
Apple requirements: developer.apple.com/news/upcoming-requirements, read 2026-10-14
Next scheduled re-check: 2027-01-14, or on the next stable minor release

The values are constants in source files, so they do not vary between runs. Only one run is reported. One limitation matters: the script reads what Flutter declares. It does not build an app with each version, so a combination that the checker accepts can still fail for other reasons, such as a plugin with its own floor.

The Script

#!/usr/bin/env bash
# Usage: ./flutter-floors.sh 3.47.7 [more tags...]
# Prints the Xcode, CocoaPods, iOS and Android floors that flutter_tools hardcodes at each tag.
for t in "$@"; do
  base="https://raw.githubusercontent.com/flutter/flutter/$t/packages/flutter_tools"
  get() { curl -sSf "$base/$1" 2>/dev/null; }
  xcode=$(get lib/src/macos/xcode.dart | grep -oE 'xcode(Required|Recommended)Version => Version\([0-9]+' \
    | sed -E 's/xcode(Required|Recommended)Version => Version\(/\1 /; s/Required/xcode-min/; s/Recommended/xcode-rec/' | tr '\n' ' ')
  pods=$(get lib/src/macos/cocoapods.dart | grep -oE "cocoaPodsMinimumVersion = Version.withText\([^']*'[0-9.]+'" | grep -oE "[0-9]+\.[0-9]+\.[0-9]+'" | tr -d "'")
  ios=""
  for p in app/ios.tmpl app/ios-swift.tmpl app_shared/ios-swift.tmpl; do
    ios=$(get "templates/$p/Runner.xcodeproj/project.pbxproj.tmpl" | grep -m1 -oE 'IPHONEOS_DEPLOYMENT_TARGET = [0-9.]+' | grep -oE '[0-9.]+$') && [ -n "$ios" ] && break
  done
  tmpl=$(get lib/src/android/gradle_utils.dart | grep -E "^const (String )?(templateDefaultGradleVersion|templateAndroidGradlePluginVersion|templateKotlinGradlePluginVersion|compileSdkVersionInt|compileSdkVersion|minSdkVersionInt|minSdkVersion) " \
    | grep -v '\$' | sed -E "s/^const (String )?//; s/templateDefaultGradleVersion/gradle/; s/templateAndroidGradlePluginVersion/agp/; s/templateKotlinGradlePluginVersion/kgp/; s/compileSdkVersion(Int)?/compileSdk/; s/minSdkVersion(Int)?/minSdk/; s/ = /=/; s/[';]//g" | tr '\n' ' ')
  floor="none"
  for p in gradle/src/main/kotlin/DependencyVersionChecker.kt gradle/src/main/kotlin_scripts/dependency_version_checker.gradle.kts gradle/src/main/kotlin/dependency_version_checker.gradle.kts; do
    c=$(get "$p") && { floor=$(echo "$c" | grep -oE 'error(Gradle|AGP|KGP|Java|MinSdk)Version[^=]*= [^/]*$' \
      | sed -E 's/^error//; s/Version[^=]*= /=/; s/(AndroidPlugin)?Version\(//; s/JavaVersion.VERSION_//; s/\)//; s/, /./g; s/ +$//' | tr '\n' ' '); break; }
  done
  echo "$t | ${xcode}pods=$pods ios=$ios | template: $tmpl| build fails below: $floor"
done

The file paths moved several times between 3.0 and 3.47, which is why the script tries more than one location. For example, the dependency checker started as a Kotlin script in 3.22, moved to a kotlin_scripts folder in 3.29, and became a compiled Kotlin class in 3.32.

The Raw Output

The script ran against the latest patch of every stable minor, which is the version most teams actually run:

./flutter-floors.sh 3.0.5 3.3.10 3.7.12 3.10.6 3.13.9 3.16.9 3.19.6 3.22.3 3.24.5 \
  3.27.4 3.29.3 3.32.8 3.35.7 3.38.10 3.41.9 3.44.9 3.47.7
3.0.5 | xcode-min 13 pods=1.9.0 ios=9.0 | template: gradle=7.4 agp=7.1.2 kgp=1.6.10 | build fails below: none
3.3.10 | xcode-min 13 pods=1.10.0 ios=11.0 | template: gradle=7.4 agp=7.1.2 kgp=1.6.10 compileSdk=31 minSdk=16 | build fails below: none
3.7.12 | xcode-min 13 pods=1.10.0 ios=11.0 | template: gradle=7.5 agp=7.2.0 kgp=1.7.10 compileSdk=31 minSdk=16 | build fails below: none
3.10.6 | xcode-min 13 pods=1.10.0 ios=11.0 | template: gradle=7.5 agp=7.3.0 kgp=1.7.10 compileSdk=31 minSdk=16 | build fails below: none
3.13.9 | xcode-min 14 pods=1.10.0 ios=11.0 | template: gradle=7.5 agp=7.3.0 kgp=1.7.10 compileSdk=33 minSdk=19 | build fails below: none
3.16.9 | xcode-min 14 pods=1.10.0 ios=12.0 | template: gradle=7.5 agp=7.3.0 kgp=1.7.10 compileSdk=33 minSdk=19 | build fails below: none
3.19.6 | xcode-min 14 pods=1.10.0 ios=12.0 | template: gradle=7.6.3 agp=7.3.0 kgp=1.7.10 compileSdk=34 minSdk=19 | build fails below: none
3.22.3 | xcode-min 14 xcode-rec 15 pods=1.10.0 ios=12.0 | template: gradle=7.6.3 agp=7.3.0 kgp=1.7.10 compileSdk=34 minSdk=21 | build fails below: Gradle=0.0.0 Java=1_1 AGP=0.0.0 KGP=0.0.0 
3.24.5 | xcode-min 14 xcode-rec 15 pods=1.10.0 ios=12.0 | template: gradle=8.3 agp=8.1.0 kgp=1.8.22 compileSdk=34 minSdk=21 | build fails below: Gradle=7.0.2 Java=1_1 AGP=7.0.0 KGP=1.7.0 
3.27.4 | xcode-min 14 xcode-rec 15 pods=1.10.0 ios=12.0 | template: gradle=8.3 agp=8.1.0 kgp=1.8.22 compileSdk=35 minSdk=21 | build fails below: Gradle=7.0.2 Java=1_1 AGP=7.0.0 KGP=1.7.0 
3.29.3 | xcode-min 14 xcode-rec 15 pods=1.10.0 ios=12.0 | template: gradle=8.10.2 agp=8.7.0 kgp=1.8.22 compileSdk=35 minSdk=21 | build fails below: Gradle=7.0.2 Java=1_1 AGP=7.0.0 KGP=1.7.0 
3.32.8 | xcode-min 14 xcode-rec 15 pods=1.10.0 ios=12.0 | template: gradle=8.12 agp=8.7.3 kgp=2.1.0 compileSdk=35 minSdk=21 | build fails below: Gradle=7.0.2 Java=1_1 AGP=7.0.0 KGP=1.7.0 MinSdk=1 
3.35.7 | xcode-min 14 xcode-rec 15 pods=1.10.0 ios=13.0 | template: gradle=8.12 agp=8.9.1 kgp=2.1.0 compileSdk=36 minSdk=24 | build fails below: Gradle=8.3.0 Java=11 AGP=8.1.1 KGP=1.8.10 MinSdk=23 
3.38.10 | xcode-min 14 xcode-rec 15 pods=1.10.0 ios=13.0 | template: gradle=8.14 agp=8.11.1 kgp=2.2.20 compileSdk=36 minSdk=24 | build fails below: Gradle=8.3.0 Java=17 AGP=8.1.1 KGP=1.8.10 MinSdk=23 
3.41.9 | xcode-min 14 xcode-rec 15 pods=1.10.0 ios=13.0 | template: gradle=8.14 agp=8.11.1 kgp=2.2.20 compileSdk=36 minSdk=24 | build fails below: Gradle=8.3.0 Java=17 AGP=8.1.1 KGP=1.8.10 MinSdk=23 
3.44.9 | xcode-min 15 xcode-rec 16 pods=1.10.0 ios=13.0 | template: gradle=9.1.0 agp=9.0.1 kgp=2.3.20 compileSdk=36 minSdk=24 | build fails below: Gradle=8.7.0 Java=17 AGP=8.6.0 KGP=2.0.0 MinSdk=23 
3.47.7 | xcode-min 15 xcode-rec 16 pods=1.10.0 ios=15.0 | template: gradle=9.3.1 agp=9.1.0 kgp=2.4.0 compileSdk=36 minSdk=24 | build fails below: Gradle=8.14.0 Java=17 AGP=8.11.1 KGP=2.2.20 MinSdk=23 

Three details in that output need translating. First, Java=1_1 is JavaVersion.VERSION_1_1, which means no real Java floor. Second, the 3.22 values of 0.0.0 mean the checker existed but could only warn. Third, 3.0.5 has no SDK levels because that release set them in the Groovy Gradle plugin rather than in gradle_utils.dart.

The same script also ran against the first patch of every minor. Every value matched the last patch, with one exception covered in the Android section below.

Flutter Version Matrix for Xcode, CocoaPods and iOS

This table is the iOS half. The release date is the first stable patch of each minor, from the release index. The “doctor” columns show what flutter doctor checks. The last column shows the IPHONEOS_DEPLOYMENT_TARGET that a new project gets.

FlutterFirst stableDoctor errors below XcodeDoctor warns below XcodeCocoaPods minimumNew project iOS target
3.02022-05-1113none1.9.09.0
3.32022-08-3013none1.10.011.0
3.72023-01-2413none1.10.011.0
3.102023-05-1013none1.10.011.0
3.132023-08-1614none1.10.011.0
3.162023-11-1514none1.10.012.0
3.192024-02-1514none1.10.012.0
3.222024-05-1314151.10.012.0
3.242024-08-0614151.10.012.0
3.272024-12-1114151.10.012.0
3.292025-02-1214151.10.012.0
3.322025-05-2014151.10.012.0
3.352025-08-1414151.10.013.0
3.382025-11-1214151.10.013.0
3.412026-02-1114151.10.013.0
3.442026-05-1815161.10.013.0
3.472026-08-1215161.10.015.0

The CocoaPods recommended version, which only triggers a warning, moved from 1.11.0 to 1.13.0 in 3.19 and to 1.16.2 in 3.29. The iOS 15 floor in 3.47 is not only a template default. The Flutter 3.47 announcement raised the minimum supported iOS from 13 to 15 and macOS from 10.15 to 12 “to support Xcode 27”. Similarly, the supported platforms page now lists iOS 14 and earlier as unsupported.

Why flutter doctor Accepts an Xcode the App Store Rejects

The doctor columns answer “will Flutter run”, not “can you ship”. Those are different questions, and Apple answers the second one. According to Apple’s upcoming requirements page, read on 2026-10-14, these upload rules apply to iOS apps:

SinceWhat App Store Connect requires for uploads
2023-04-25Built with Xcode 14.1 and the iOS 16.1 SDK or later
2024-04-29Built with Xcode 15 for iOS 17
2026-04-28Built with Xcode 26 or later, using an iOS 26 SDK
2026-09-09Apps must target iOS 13 or later

Put the two tables side by side and the gap is obvious. Flutter 3.41 still accepts Xcode 14, and 3.47 still accepts Xcode 15. However, any binary uploaded after 2026-04-28 must come from Xcode 26. In practice, that makes Xcode 26 the real floor for every Flutter version that ships to the App Store today, regardless of what the doctor says. The doctor check exists to catch a broken toolchain early, not to track Apple’s policy.

The Mac matters too. According to Apple’s Xcode system requirements, Xcode 26.0 through 26.3 need macOS Sequoia 15.6. Xcode 26.4 and later need macOS Tahoe 26.2, and Xcode 27 needs macOS Tahoe 26.6. Consequently, a CI runner image or an older office Mac can block an upgrade before Flutter even enters the picture.

Which Flutter Releases Fixed Xcode 26 and Xcode 27 Problems

The minimum is only half the story. The other half is whether a given Flutter version works with the newest Xcode, and the Flutter CHANGELOG at tag 3.47.7 records that through hotfixes. These are the entries that name Xcode 26, Xcode 27 or their iOS versions:

HotfixWhat it fixed
3.35.4Platform view gesture blocking failing on iOS 26
3.38.2IDE debugging on a physical iOS 26 device hanging on a white screen
3.38.6WebViews becoming unclickable after scrolling on iOS 26
3.41.4Simulator builds failing with Xcode 26 when a CocoaPod lacks arm64 support
3.41.7Debug apps crashing on physical devices with Xcode 26.4 and later
3.44.8macOS and iOS builds failing with the Xcode 27 toolchain on a lipo verification error
3.47.2Add-to-app SwiftPM builds failing on Xcode 27
3.47.4Debugging with Xcode 27 launching to a white screen for minutes
3.47.5Debug apps occasionally crashing on physical iOS 27 devices

Therefore, for day-to-day work on Xcode 26.4 or later, 3.41.7 is the oldest patch with the debug crash fix. For Xcode 27, the lipo fix arrived in 3.44.8, while the debugging fixes exist only on the 3.47 line. Note also that the iOS 27 SDK makes the UIScene lifecycle mandatory, so an app built with Xcode 27 without it fails at launch. The 3.47 CLI migrates most apps automatically.

Flutter Version Matrix for Gradle, AGP, Kotlin and Java

The Android half has two layers, and confusing them causes most upgrade pain. The first layer is what flutter create writes into a new project. The second layer is what the Flutter Gradle plugin refuses to build. An existing project keeps its old Gradle and AGP versions after a Flutter upgrade, because nothing rewrites settings.gradle.kts or the wrapper for you.

What flutter create Writes Into a New Project

FlutterGradleAGPKotlin (KGP)compileSdkminSdkNDK
3.07.47.1.21.6.10set in pluginset in pluginset in plugin
3.37.47.1.21.6.10311621.1.6352462
3.77.57.2.01.7.10311621.4.7075529
3.107.57.3.01.7.10311623.1.7779620
3.137.57.3.01.7.10331923.1.7779620
3.167.57.3.01.7.10331923.1.7779620
3.197.6.37.3.01.7.10341923.1.7779620
3.227.6.37.3.01.7.10342123.1.7779620
3.248.38.1.01.8.22342123.1.7779620
3.278.38.1.01.8.22352126.1.10909125
3.298.10.28.7.01.8.22352126.1.10909125
3.328.128.7.32.1.0352126.1.10909125
3.358.128.9.12.1.0362427.0.12077973
3.388.148.11.12.2.20362428.2.13676358
3.418.148.11.12.2.20362428.2.13676358
3.449.1.09.0.12.3.20362428.2.13676358
3.479.3.19.1.02.4.0362428.2.13676358

The target SDK follows the compile SDK in every release from 3.3 on, with one exception: 3.19 compiled against 34 but targeted 33. For reference, a project created with 3.47.7 gets exactly these plugin lines in android/settings.gradle.kts:

// android/settings.gradle.kts, as generated by flutter create on 3.47.7
plugins {
    id("dev.flutter.flutter-plugin-loader") version "1.0.0"
    id("com.android.application") version "9.1.0" apply false
    id("org.jetbrains.kotlin.android") version "2.4.0" apply false
}

The Gradle version itself lives in android/gradle/wrapper/gradle-wrapper.properties as a distributionUrl ending in gradle-9.3.1-all.zip. Those two files are what to compare against the table when an older project misbehaves.

Where the Android Build Fails: The Dependency Checker Floors

Since 3.22, the Flutter Gradle plugin checks the project’s Gradle, Java, AGP and Kotlin versions during configuration. It added a minSdk check in 3.32. Each check has two thresholds, and the comparison in the source is a strict “less than”. So the error value is the lowest version that still builds.

FlutterGradleAGPKotlin (KGP)JavaminSdk
3.0 to 3.19no checkno checkno checkno checkno check
3.22warns onlywarns onlywarns onlywarns onlyno check
3.24 to 3.297.0.27.0.01.7.0no floorno check
3.327.0.27.0.01.7.0no floorno floor
3.358.3.08.1.11.8.101123
3.38 and 3.418.3.08.1.11.8.101723
3.448.7.08.6.02.0.01723
3.478.14.08.11.12.2.201723

Read each cell as “the build fails below this”. Below the floor, the build stops with an error built from the message template in the 3.47.7 source. Filled in for a project still on Gradle 8.12, it reads:

Error: Your project's Gradle version (8.12) is lower than Flutter's minimum supported version of 8.14.0. Please upgrade your Gradle version.
Alternatively, use the flag "--android-skip-build-dependency-validation" to bypass this check.

The flag does exactly what it says. It skips all of these checks for one build, and it also skips the warnings. The Kotlin check runs only when the plugin can find a Kotlin Gradle Plugin version, so a project without one never fails on it. That matters from 3.44 on, because AGP 9 handles Kotlin natively. The Flutter 3.44 announcement warns that applying the separate Kotlin plugin manually now conflicts with AGP 9, and that Flutter’s temporary compatibility for it will go away.

Today’s Warning Is the Next Floor

The warning thresholds are the most useful forecast on this page. Here is the full set for 3.47.7:

curl -sSf https://raw.githubusercontent.com/flutter/flutter/3.47.7/packages/flutter_tools/gradle/src/main/kotlin/DependencyVersionChecker.kt \
  | grep -E 'internal val (warn|error)' | sed -E 's/^ +//; s/@VisibleForTesting //'
internal val warnGradleVersion: Version = Version(9, 1, 0)
internal val errorGradleVersion: Version = Version(8, 14, 0)
internal val warnJavaVersion: JavaVersion = JavaVersion.VERSION_17
internal val errorJavaVersion: JavaVersion = JavaVersion.VERSION_17
internal val warnAGPVersion: AndroidPluginVersion = AndroidPluginVersion(9, 0, 1)
internal val errorAGPVersion: AndroidPluginVersion = AndroidPluginVersion(8, 11, 1)
internal val warnKGPVersion: Version = Version(2, 3, 20)
internal val errorKGPVersion: Version = Version(2, 2, 20)
internal val warnMinSdkVersion: Int = 24
internal val errorMinSdkVersion: Int = 23

Now compare with 3.44.9. Its warnings sat at Gradle 8.14, AGP 8.11.1 and Kotlin 2.2.20, and those became the 3.47 error floors exactly. Likewise, the 3.35 warnings for Gradle and AGP became the 3.44 floors. That is a pattern in the history, not a promise. Even so, it is a good bet that Gradle 9.1, AGP 9.0.1 and Kotlin 2.3.20 become the floor in a coming release. A project that builds today with a warning has, at best, a few months left.

Java: Which JDK Flutter Actually Uses

The Java floor reached 11 in 3.35 and 17 in 3.38, and it is still 17 in 3.47. Flutter builds with the JDK bundled in Android Studio first, then JAVA_HOME, then whatever java is on the path, according to the Java and Gradle migration guide. As a result, two machines with the same Flutter version can build with different Java versions. If you split work between VS Code and Android Studio, check which JDK each one hands to Flutter.

# Show the JDK Flutter resolved, under the Android toolchain section
flutter doctor -v

# Pin one JDK for every Flutter project on this machine
flutter config --jdk-dir="/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home"

Pinning the JDK in flutter config removes the guesswork on CI. Gradle 9 itself also needs Java 17 or newer to run, per the Gradle compatibility matrix. So a 3.44 or 3.47 template project cannot fall back to an older JDK even with the dependency check skipped.

The 3.24.2 Template Change Inside a Patch Release

Every value in the matrix held steady across all the patches of its minor, with one exception. The Android template changed partway through 3.24:

./flutter-floors.sh 3.24.0 3.24.1 3.24.2 3.24.3 3.24.4 3.24.5
3.24.0 | xcode-min 14 xcode-rec 15 pods=1.10.0 ios=12.0 | template: gradle=7.6.3 agp=7.3.0 kgp=1.7.10 compileSdk=34 minSdk=21 | build fails below: Gradle=7.0.2 Java=1_1 AGP=7.0.0 KGP=1.7.0 
3.24.1 | xcode-min 14 xcode-rec 15 pods=1.10.0 ios=12.0 | template: gradle=7.6.3 agp=7.3.0 kgp=1.7.10 compileSdk=34 minSdk=21 | build fails below: Gradle=7.0.2 Java=1_1 AGP=7.0.0 KGP=1.7.0 
3.24.2 | xcode-min 14 xcode-rec 15 pods=1.10.0 ios=12.0 | template: gradle=8.3 agp=8.1.0 kgp=1.8.22 compileSdk=34 minSdk=21 | build fails below: Gradle=7.0.2 Java=1_1 AGP=7.0.0 KGP=1.7.0 
3.24.3 | xcode-min 14 xcode-rec 15 pods=1.10.0 ios=12.0 | template: gradle=8.3 agp=8.1.0 kgp=1.8.22 compileSdk=34 minSdk=21 | build fails below: Gradle=7.0.2 Java=1_1 AGP=7.0.0 KGP=1.7.0 
3.24.4 | xcode-min 14 xcode-rec 15 pods=1.10.0 ios=12.0 | template: gradle=8.3 agp=8.1.0 kgp=1.8.22 compileSdk=34 minSdk=21 | build fails below: Gradle=7.0.2 Java=1_1 AGP=7.0.0 KGP=1.7.0 
3.24.5 | xcode-min 14 xcode-rec 15 pods=1.10.0 ios=12.0 | template: gradle=8.3 agp=8.1.0 kgp=1.8.22 compileSdk=34 minSdk=21 | build fails below: Gradle=7.0.2 Java=1_1 AGP=7.0.0 KGP=1.7.0 

A project created with 3.24.0 or 3.24.1 got Gradle 7.6.3 and AGP 7.3.0. One created a week later with 3.24.2 got Gradle 8.3 and AGP 8.1.0. The 3.24.2 CHANGELOG entry does not mention the change. Consequently, two projects that both say “created with Flutter 3.24” can start on different major versions of Gradle.

What Is the Minimum Xcode for Flutter in 2026?

For any app that ships to the App Store, the minimum Xcode in 2026 is Xcode 26, because Apple has rejected uploads from older Xcode since 2026-04-28. Flutter’s own check is lower: 3.44 and 3.47 error only below Xcode 15. For Xcode 27, use Flutter 3.47.5 or later, the patch with the latest iOS 27 debugging fix.

How to Check a Project Against the Flutter Version Matrix

Checking an existing project takes five minutes and catches most upgrade failures before CI does:

  1. Run flutter --version and find your minor in the tables above
  2. Run flutter doctor -v and note the Xcode, CocoaPods and Java versions it reports
  3. Read the Gradle version from android/gradle/wrapper/gradle-wrapper.properties
  4. Read the AGP and Kotlin versions from the plugins block in android/settings.gradle.kts, or from android/build.gradle in older projects
  5. Compare each one with the floor of the Flutter version you are moving to, not the one you are on
  6. Fix anything below the warning threshold now, while it is still a warning

Step 5 is where most people slip, because the comparison has to run against the target release. The script also helps here, since it shows the target’s floors without installing it:

# What will Flutter 3.47.7 demand from a project currently on 3.38?
./flutter-floors.sh 3.38.10 3.47.7

# Then raise the Gradle wrapper to the new template version from inside android/
cd android && ./gradlew wrapper --gradle-version=9.3.1

After raising Gradle, bump AGP in settings.gradle.kts to match. Gradle and AGP move together, and Google publishes the pairing in the AGP and Gradle compatibility table. Flutter’s own flutter analyze --suggestions also reports Java, Gradle and AGP mismatches for the project in the current directory.

Real-World Scenario: A CI Runner That Passed Every Check

Consider a small team of two or three developers maintaining a mid-sized Flutter app on 3.38, with iOS builds on a self-hosted Mac mini running Xcode 16. Every pipeline is green. flutter doctor reports no errors, because 3.38 only errors below Xcode 14 and warns below 15. Then, in May 2026, the first release after Apple’s deadline fails at upload to App Store Connect, not at build. The build itself was fine. Only the SDK it was built against is no longer accepted.

The fix is not a Flutter change at all. Xcode 26 needs macOS Sequoia 15.6, and Xcode 26.4 needs Tahoe 26.2, so the Mac mini may need a macOS update before the Xcode upgrade can even start. Next, the team hits the 3.41.4 issue: simulator builds fail with Xcode 26 because one old CocoaPod has no arm64 slice. Then, debug sessions on physical devices crash until they reach 3.41.7. What looked like a one-line Xcode bump becomes a macOS upgrade, a Flutter minor upgrade and a plugin replacement over a couple of weeks.

The trade-off is plain in hindsight. The team trusted flutter doctor as a release gate, and it was never designed to be one. A pipeline step that checks the Xcode version against Apple’s current upload rule, separately from Flutter, would have flagged the problem months earlier. In addition, a calendar reminder for Apple’s announced deadlines costs nothing.

When to Follow the Flutter Version Matrix Exactly

  • You are creating a new project or a fresh CI image, where matching the template costs nothing
  • Your Android build prints a dependency warning, which is likely the next release’s hard floor
  • You are planning a jump of more than two Flutter minors, where several floors move at once
  • You ship to the App Store from CI and need the Xcode and macOS image pinned deliberately
  • You maintain a plugin and want your example app to match what new users get

When NOT to Follow the Flutter Version Matrix Exactly

  • A plugin you depend on has not published support for the newest AGP, so staying one version back keeps the build green
  • Your Android project is on AGP 8 with a manually applied Kotlin plugin, and AGP 9’s built-in Kotlin needs a separate migration
  • You must keep supporting iOS 13 or 14 users, which rules out 3.47 until you decide to drop them
  • You are mid-release and a toolchain bump would invalidate the QA pass, so the upgrade waits for the next cycle
  • The template version is newer than your Android Studio’s bundled AGP support, which forces an IDE upgrade first

Common Mistakes with Flutter Toolchain Versions

  • Treating a clean flutter doctor as proof that the build can ship to the App Store
  • Upgrading Flutter and expecting the project’s Gradle wrapper and AGP to follow, when nothing rewrites them
  • Leaving --android-skip-build-dependency-validation in a CI script after the one build that needed it
  • Bumping Gradle without bumping AGP, or the reverse, instead of using Google’s compatibility table
  • Ignoring the Java version that Android Studio bundles, so a local build and the CI build use different JDKs
  • Assuming every “Flutter 3.24” project has the same Android toolchain, when 3.24.2 changed the template
  • Raising IPHONEOS_DEPLOYMENT_TARGET in the Xcode project but not in the Podfile, so pods still build for the old target
  • Reading the dependency checker’s error value as the recommended version, when it is only the lowest that builds

Keeping This Flutter Version Matrix Current

This page is a tracker, not an article, and it is reviewed every three months and on each stable minor release. The next scheduled re-check is 2027-01-14. To check a release that appeared after the date above, run the script with the new tag. The output line drops straight into the tables. If a line comes back empty, a file moved again, and the script needs a new path. Related changes outside the toolchain, such as the move of Material and Cupertino into separate packages and Impeller becoming the default on Android, have their own migration guides.

Next Steps

The Flutter version matrix boils down to three rules. Apple sets the real Xcode floor, which is Xcode 26 today. The Flutter Gradle plugin sets the real Android floor, which is Gradle 8.14, AGP 8.11.1 and Java 17 on 3.47. Today’s warning thresholds are tomorrow’s floors. Start by running ./flutter-floors.sh with your current version and your target version, and fix every line that differs before you upgrade. If you maintain a plugin, the Flutter package publishing guide and the guide to native platform channels in Flutter plugins cover the files where these versions live. If an upgrade also changes what you ship, the Flutter app size measurements show where the megabytes go.