When the time comes to bring a Flutter application to production on Android, one of the most important—and also error-prone—steps is correctly generating a signed APK or AAB. Google Play requires any app to be signed before it can be published, and if anything fails in this process, the compilation or upload to the Play Console simply won't work.
In this guide, I will explain step by step how to generate a signed APK and AAB in Flutter using Visual Studio Code or Android Studio, working on Windows (although the steps apply equally to macOS and Linux). Additionally, we will look at real-world errors that often appear during the process and how to resolve them.
We will cover several tips, steps, and considerations you need to keep in mind when generating the production release of your Flutter app using Visual Studio Code. The APK or AAB we will generate will be signed, which is Google's requirement for uploading your app to the Google Play Store.
Previously, we looked at how to generate the app launcher icon, which is essential before exporting your app to Google Play.
Prerequisites before signing your Flutter app
Before diving into commands and configurations, it is worth reviewing a few basic points to make sure your environment is properly prepared.
Having Flutter and Android correctly configured
Ensure your environment is set up properly by running:
$ flutter doctor -vHere you will be able to verify:
- Correct Java path (
JDK) Android SDKinstalled- Licenses accepted (
flutter doctor --android-licenses)
If flutter doctor reports any issues, resolve them before proceeding. An incomplete environment is the root cause of many build errors.
Difference between upload key and app signing key (Play App Signing)
Google Play manages two distinct keys, and it is important not to confuse them:
- Upload key: This is the key you use to upload the AAB or APK to the Play Console.
- App signing key: Managed internally by Google, this is the key that end users receive on their devices.
The standard and recommended approach is to enable Play App Signing, so Google secures the final signing key. That way, even if you lose your upload key, you can request a new one from Google without losing your app.
Where NOT to store your keystore
Never upload your .jks file to code repositories, whether public or private. This includes:
- GitHub
- GitLab
- Any public or private repository
Store it in a secure location outside your project and keep backups in at least two different places (for example, an external drive and an encrypted cloud storage service). If you lose your keystore without having Play App Signing configured, you could lose the ability to update your app forever.
Create the keystore (JKS) to sign your Flutter application
These steps also apply if you use Android Studio instead of Visual Studio Code. We will be working on Windows, but the process is equivalent on macOS and Linux. Here is the official documentation from the Flutter team.
To generate a signed Release APK for a Flutter application on Android using Visual Studio Code (VSC), follow these steps:
1. Generate the keystore with keytool
- Open a terminal in VSC.
Run the following command to generate a key (keystore) using
keytool. You can do this from any location in your system:keytool -genkey -v -keystore release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key-alias- Follow the wizard's instructions to set up the key. It is an interactive prompt requiring basic information. The parameters
release-key.jksandkey-aliasare customizable; you can replace them with your preferred names.
The console prompt looks like this:
C:\Users\andre\flutter
.\keytool.exe -genkey -v -keystore my-release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key-alias
Enter keystore password:
Re-enter new password:
What is your first and last name?
[Unknown]: Your Name
What is the name of your organizational unit?
[Unknown]: YourCompany
What is the name of your organization?
[Unknown]: YourCompany
What is the name of your City or Locality?
[Unknown]: YourCity
What is the name of your State or Province?
[Unknown]: YourState
What is the two-letter country code for this unit?
[Unknown]: (EG US)
Is CN=andres cruz, OU=desarrollolibre, O=desarrollolibre, L=caracas, ST=distrito capital, C=VE correct?
[no]: yesIf you run into issues running keytool, we cover them in detail in the following section.
During the process, you will be asked for:
- Keystore password (
storePassword) - Alias (
keyAlias) - Name, organization, city, state, and country code
On Windows, if you run into permission issues, specify an absolute path where you have write permissions:
keytool -genkey -v -keystore C:\Users\your_user\release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key-alias2. Create the key.properties file
- Inside the
androiddirectory of your Flutter project, create a file namedkey.properties. Add the following information to the file:
storePassword=your_keystore_password keyPassword=your_key_password keyAlias=my-key-alias storeFile=release-key.jks
- ⚠️ On Windows, if you specify an absolute path in
storeFile, remember to use double backslashes (\\), for example:storeFile=C:\\Users\\your_user\\release-key.jks.
Also make sure to add key.properties and *.jks to your .gitignore file to prevent exposed keystore passwords in your repository.
3. Modify the build.gradle file
- Open the
android/app/build.gradlefile. Add the following configuration. The lines marked with
+are the ones you need to add:+ def keystoreProperties = new Properties() + def keystorePropertiesFile = rootProject.file('key.properties') + if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) + } + android { ... } + signingConfigs { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null + storePassword keystoreProperties['storePassword'] + } + } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, // so `flutter run --release` works. - signingConfig signingConfigs.debug + signingConfig signingConfigs.release } }
In short, we are instructing the compiler to read the keystore parameters from
key.propertiesand use them to sign the app in Release mode, instead of using the default debug keys.
Common error: alias not found in keystore
Typical error:
No key with alias 'upload' found in keystoreThis means the keyAlias value in key.properties does not match the alias you used when generating the keystore. Check it carefully, as it is one of the most frequent and easy-to-miss mistakes.
4. Generate the signed APK or AAB
To generate the signed app in APK format, run:
flutter build apkTo generate in AAB format (recommended for Google Play):
flutter build appbundle- The signed
app-release.apkfile will be generated in thebuild/app/outputs/apk/release/directory of your Flutter project. The AAB file will be located inbuild/app/outputs/bundle/release/. Before uploading to Google Play, I recommend installing the APK on a physical device to verify that everything works properly: simply copy it to the Android device and follow the manual installation steps.
Do not share the keystore file (
release-key.jks) publicly. Keep it safe and never include it in the project repository.
All set! At this point, you have a signed Release APK or AAB ready for your Flutter application on Android.
Common issues with keytool and how to fix them
❌ keytool is not recognized as a command
This happens because keytool is not added to your system's PATH. The fastest solution is to locate the executable through Flutter:
Run:
flutter doctor -vLook for the line:
Java binary at: C:\Program Files\Android\Android Studio\jbr\bin\javaNavigate to that directory (excluding the java executable file):
cd "C:\Program Files\Android\Android Studio\jbr\bin"From there, you can execute keytool directly. Alternatively, you can add that path to your system's environment variables so you don't have to repeat this step in the future.
Issues generating the signed APK
Many things can go wrong when building your signed app. If you receive an access denied error like this when running keytool:
keytool error: java.io.FileNotFoundException: my-release-key.jks (Access is denied)
java.io.FileNotFoundException: my-release-key.jks (Access is denied)
at java.base/java.io.FileOutputStream.open0(Native Method)
at java.base/java.io.FileOutputStream.open(FileOutputStream.java:293)
at java.base/java.io.FileOutputStream.<init>(FileOutputStream.java:235)
at java.base/java.io.FileOutputStream.<init>(FileOutputStream.java:123)
at java.base/sun.security.tools.keytool.Main.doCommands(Main.java:1375)
at java.base/sun.security.tools.keytool.Main.run(Main.java:423)
at java.base/sun.security.tools.keytool.Main.main(Main.java:416)The issue is that you are trying to write the .jks file to a folder without write permissions (such as the Android Studio installation directory). The fix is to specify a path inside your user directory:
keytool -genkey -v -keystore C:\Users\andres\release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key-aliasWith that, the release-key.jks file will be generated properly in the specified location.
Before generating the signed APK, you should also update your app's namespace, since the default identifier cannot contain example as part of its name. To change it easily, you can use the package:
change_app_package_name on pub.dev
Another common build error is an incorrect alias. If you see something like this:
e: C:/Users/andre/.gradle/caches/transforms-3/c1e3cec58f97b65c118bb2f68fab94a8/transformed/jetified-core-ktx-1.10.1/jars/classes.jar!/META-INF/core-ktx_release.kotlin_module: Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.8.0, expected version is 1.6.0.
e: C:/Users/andre/.gradle/caches/transforms-3/a3842a17fe7307c5bcdac869078c73a0/transformed/core-1.10.1/jars/classes.jar!/META-INF/core_release.kotlin_module: Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.8.0, expected version is 1.6.0.
e: C:/Users/andre/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.8.22/636bf8b320e7627482771bbac9ed7246773c02bd/kotlin-stdlib-1.8.22.jar!/META-INF/kotlin-stdlib-jdk7.kotlin_module: Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.8.0, expected version is 1.6.0.
e: C:/Users/andre/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.8.22/636bf8b320e7627482771bbac9ed7246773c02bd/kotlin-stdlib-1.8.22.jar!/META-INF/kotlin-stdlib.kotlin_module: Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.8.0, expected version is 1.6.0.
e: C:/Users/andre/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.8.22/636bf8b320e7627482771bbac9ed7246773c02bd/kotlin-stdlib-1.8.22.jar!/META-INF/kotlin-stdlib-jdk8.kotlin_module: Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.8.0, expected version is 1.6.0.
e: C:/Users/andre/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.8.22/1a8e3601703ae14bb58757ea6b2d8e8e5935a586/kotlin-stdlib-common-1.8.22.jar!/META-INF/kotlin-stdlib-common.kotlin_module: Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.8.0, expected version is 1.6.0.
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:packageRelease'.
> A failure occurred while executing com.android.build.gradle.tasks.PackageAndroidArtifact$IncrementalSplitterRunnable
> com.android.ide.common.signing.KeytoolException: Failed to read key upload from store "C:\Users\andre\flutter\release-key-flutter.jks": No key with alias 'upload' found in keystore C:\Users\andre\flutter\release-key-flutter.jks
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org
BUILD FAILED in 11s
Running Gradle task 'assembleRelease'... 12,5sThe key to the error lies in: No key with alias 'upload' found in keystore. It means the keyAlias value in your key.properties:
storePassword=your_keystore_password
keyPassword=your_key_password
keyAlias=my-key-alias
storeFile=release-key.jks…does not match the alias you specified when creating the keystore with keytool. Make sure both are identical, including case sensitivity.
App opens but fails to load data
Another very common error occurs when the app requires an internet connection but does not have the corresponding permissions declared in AndroidManifest.xml. Make sure to include the following before the <application> block:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<applicationWithout these permissions, all data fetched from the internet simply won't load in Release mode, even though it works fine in debug mode. This happens because Release mode can enforce additional network restrictions based on the security configuration of the manifest.
Once successfully generated, the signed APK will be available at:
build\app\outputs\flutter-apk\app-release.apk
Versioning error in Google Play
If you see an error like this when attempting to upload your AAB or APK to Google Play:
You must use a different version code for your APK or your Android App Bundle because code 1 is already assigned to another APK or Android App Bundle.
It means a release with that versionCode already exists in the Play Console. You need to increment your app version in the pubspec.yaml file. The format is version: version_name+versionCode:
version: 1.0.4+4The number after the + is the versionCode that Google Play uses internally to identify each release. Every upload must have a higher code than the previous one.
APK vs AAB in Flutter: which to use and when
Why Google Play requires AAB
Since August 2021, Google Play has required the AAB (Android App Bundle) format for new applications, primarily because it:
- Generates optimized downloads tailored to each specific device
- Produces smaller file sizes compared to a universal APK
- Allows better distribution based on CPU architecture (
arm64-v8a,x86_64, etc.)
If you publish on Google Play: always use AAB (
flutter build appbundle).
When using an APK still makes sense
- Manual testing on physical devices
- Distribution outside the Play Store (direct installation or sideloading)
- Huawei AppGallery or other alternative stores
Import errors in build.gradle.kts (Kotlin DSL)
If your project uses the .kts format (Kotlin DSL) instead of traditional Groovy, you need to be more explicit with imports and types. It can trigger errors like these:
e: file:///C:/Users/andre/Desktop/proy/flutter/mios/aprendeingles/android/app/build.gradle.kts:9:26: Unresolved reference: Properties
e: file:///C:/Users/andre/Desktop/proy/flutter/mios/aprendeingles/android/app/build.gradle.kts:12:29: Unresolved reference: FileInputStream
e: file:///C:/Users/andre/Desktop/proy/flutter/mios/aprendeingles/android/app/build.gradle.kts:26:69: Unresolved reference: it
FAILURE: Build failed with an exception.
* Where:
Build file 'C:\Users\andre\Desktop\proy\flutter\mios\aprendeingles\android\app\build.gradle.kts' line: 9
* What went wrong:
Script compilation errors:
Line 09: val keystoreProperties = Properties()
^ Unresolved reference: Properties
Line 12: keystoreProperties.load(FileInputStream(keystorePropertiesFile))
^ Unresolved reference: FileInputStream
Line 24: keyAlias = keystoreProperties["keyAlias"] as String
^ No cast needed
Line 25: keyPassword = keystoreProperties["keyPassword"] as String
^ No cast needed
Line 26: storeFile = keystoreProperties["storeFile"]?.let { file(it) }
^ Unresolved reference: it
Line 27: storePassword = keystoreProperties["storePassword"] as String
^ No cast needed
Line 38: jvmTarget = JavaVersion.VERSION_17.toString()
^ 'jvmTarget: String' is deprecated. Please migrate to the compilerOptions DSL. More details are here: https://kotl.in/u1r8ln
7 errors
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.The fix is to add the required imports at the top of the android\app\build.gradle.kts file:
import java.util.Properties
import java.io.FileInputStream
With these two lines, Kotlin DSL will be able to resolve the references to Properties and FileInputStream properly.
Uploading your signed AAB to Google Play Console
- Log in to Google Play Console
- Go to the Production section or the corresponding test track (Alpha, Beta, etc.)
- Create a new release
- Upload the generated
.aabfile - Review any errors and warnings reported by the console
- Submit for review
Final recommendations before publishing
- Store the keystore (
.jks) in a secure place outside the repository - Back up the
.jksin at least two separate locations - Always test on a real physical device before releasing
- Manage versioning in
pubspec.yamland increment theversionCodefor every release - Use AAB (
flutter build appbundle) whenever possible for Google Play - Add
key.propertiesand*.jksto your.gitignoreright from the start of the project
Frequently asked questions about APK and AAB in Flutter
- Is signing a Flutter app mandatory for publishing?
- Yes. Google Play does not accept unsigned apps under any circumstances.
- Can I generate an AAB without Android Studio?
- Yes. Using the command line is sufficient:
flutter build appbundle.
- Yes. Using the command line is sufficient:
- What happens if I lose the keystore?
- If you do not have Play App Signing enabled, you could lose the ability to update the app on Google Play forever. That is why backing it up from day one is critical.
- Does Google Play accept APKs today?
- Only in very specific cases (legacy apps). For new releases, AAB has been the required standard since August 2021.
- Can I use FVM to generate the APK or AAB?
- Yes. If you use Flutter Version Manager (FVM), simply prepend
fvmto the command:fvm flutter build apkorfvm flutter build appbundle.
- Yes. If you use Flutter Version Manager (FVM), simply prepend
Often, when updating the SDK in Flutter, projects stop working. For this, the best thing you can do is follow these tips for updating a Flutter project.