Skip to main content

Solving Android Gradle Plugin 9.0 & Java 17 Compatibility in Flutter 2025

 You have just upgraded your Flutter SDK or dependencies, attempted a build, and hit a wall of red text. The logs are screaming about Incompatible Gradle version, asking you to specify a namespace, or throwing Unsupported class file major version 61 (or 65).

In the transition to 2025, the Android ecosystem has aggressively deprecated legacy build configurations. Android Gradle Plugin (AGP) 9.0 and the enforcement of Java 17+ (and increasingly Java 21) are no longer optional warnings—they are breaking changes.

This post dissects why your build is failing and provides the exact code changes required to modernize your Flutter project’s Android layer.

The Root Cause: Bytecode and Separation of Concerns

Two primary architectural shifts are causing these failures:

  1. The Manifest/Gradle Split (AGP 8.0 -> 9.0): Historically, the AndroidManifest.xml handled both the application ID (for the Play Store) and the package name (for R.java class generation). AGP now strictly decouples these. The package attribute in the Manifest is deprecated for builds and ignored by AGP 9.0. You must define the namespace in your Gradle build files for code generation, while applicationId remains the unique identifier for the app store.
  2. JDK Bytecode Mismatch: AGP 9.0 requires the Gradle Daemon to run on JDK 17 or 21. If your JAVA_HOME points to JDK 11 (common in older setups) or JDK 1.8, Gradle cannot read the class files generated by the Android compiler. Conversely, if your Gradle version is too old, it cannot run on JDK 21.

The Solution: A Four-Step Migration

We will upgrade the Gradle Wrapper, enforce the correct JVM toolchain, and migrate the Manifest configurations.

Step 1: Upgrade the Gradle Wrapper

AGP 9.0 requires Gradle 8.10 or newer. Open android/gradle/wrapper/gradle-wrapper.properties and update the distribution URL.

# android/gradle/wrapper/gradle-wrapper.properties

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
# Ensure you are using at least 8.10.2 (or the latest stable 9.x if available)
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

Step 2: Update Project-Level Build Configuration

You must align your Kotlin Gradle Plugin and AGP versions. In 2025, we use the declarative plugins block inside settings.gradle (or the legacy root build.gradle if you haven't migrated to the new plugin management system).

Here is the modern configuration for android/settings.gradle:

// android/settings.gradle

pluginManagement {
    def flutterSdkPath = {
        def properties = new Properties()
        file("local.properties").withInputStream { properties.load(it) }
        def flutterSdkPath = properties.getProperty("flutter.sdk")
        assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
        return flutterSdkPath
    }
    settings.ext.flutterSdkPath = flutterSdkPath()

    includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")

    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
    }
    
    plugins {
        // Match the AGP version to your requirements (9.0.0-alpha or stable 8.7+)
        id "com.android.application" version "8.7.0" apply false
        // Kotlin version must be compatible with AGP
        id "org.jetbrains.kotlin.android" version "2.0.20" apply false
    }
}

include ":app"

If your project still uses the legacy root build.gradle for classpath dependencies, ensure com.android.tools.build:gradle is updated to the corresponding version defined above.

Step 3: Implement Namespace and Enforce Java 17

This is the most critical step. We will modify android/app/build.gradle. We need to remove the reliance on the manifest package attribute and enforce the Java 17 toolchain.

// android/app/build.gradle

plugins {
    id "com.android.application"
    // The Flutter plugin loader handles the integration automatically
    id "kotlin-android"
    id "dev.flutter.flutter-gradle-plugin"
}

android {
    // 1. DEFINING THE NAMESPACE (Critical for AGP 8+)
    // This replaces the 'package' attribute in AndroidManifest.xml for R.java generation
    namespace "com.example.your_project_name"
    
    // 2. COMPILE SDK
    // Must be 34 or 35 (Android 14/15) to use modern AGP
    compileSdk 35

    defaultConfig {
        // This remains your unique ID on the Play Store
        applicationId "com.example.your_project_name"
        minSdk 23 // Flutter default minimum is rising
        targetSdk 35
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
    }

    // 3. JAVA COMPATIBILITY
    // Enforce Java 17 (or 21) for compilation
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_17
        targetCompatibility JavaVersion.VERSION_17
    }

    // 4. KOTLIN OPTIONS
    // Ensure Kotlin compiles to the same JVM target
    kotlinOptions {
        jvmTarget = "17"
    }

    buildTypes {
        release {
            signingConfig signingConfigs.debug
        }
    }
}

flutter {
    source "../.."
}

Step 4: Clean the Manifest

Open android/app/src/main/AndroidManifest.xml. You must remove the package attribute from the <manifest> tag.

Before:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.your_project_name">
    <!-- ... -->
</manifest>

After:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- The 'package' attribute is GONE. 
         It is now handled by 'namespace' in build.gradle. -->
    
    <application
        android:label="your_project_name"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">
        <!-- ... -->
    </application>
</manifest>

Why This Works

Decoupling Identity

By removing package from the Manifest and adding namespace to Gradle, you resolve the ambiguity that AGP 9.0 forbids. Now, applicationId strictly controls the app's identity on the device and store, while namespace strictly controls the package name for the generated R class and BuildConfig class.

JVM Toolchains

Setting sourceCompatibility and jvmTarget to 17 ensures that the bytecode generated by the Kotlin compiler is compatible with the class file format expected by the Android runtime and the Gradle daemon. If you leave these at 1.8 (the old default), the build fails when it encounters modern library dependencies (like OkHttp 5 or the latest AndroidX libraries) which now ship with Java 17 bytecode.

Conclusion

The "Build Failed" errors accompanying the shift to AGP 9.0 and Java 17 are not bugs; they are the result of stricter build boundaries and modernization. By explicitly defining your namespace in Gradle and aligning your Java toolchains, you make your Flutter project robust, faster to build, and ready for the next generation of Android development.

Run flutter clean and flutter pub get immediately after applying these changes to flush the build cache.