Fixing Flutter App Bundle Rejection:
16 KB Memory Page Size Error on Google Play Store
A complete, step-by-step troubleshooting guide — from diagnosing the failing native library to force-resolving the exact Gradle dependency causing Play Store rejection.
What Is the 16 KB Page Size Requirement?
Starting with Android 15, Google introduced support for devices that use 16 KB memory page sizes instead of the traditional 4 KB. This change primarily targets high-performance ARM64 devices where larger page sizes improve memory efficiency and app launch speed.
Google Play Store now requires all apps with native code to support 16 KB page alignment in their compiled .so (shared object) libraries. If even a single native library in your app bundle is aligned to 4 KB instead of 16 KB, your release will be blocked.
| Alignment | Hex Value | Play Store | Meaning |
|---|---|---|---|
| 4 KB | 0x1000 | ❌ REJECTED | Old default — fails new requirement |
| 16 KB | 0x4000 | ✅ PASS | Required for Play Store compliance |
| 64 KB | 0x10000 | ✅ PASS | Flutter engine default — fine |
This affects not just your own Dart/native code, but every pre-built .so bundled by your Flutter plugins and Android dependencies. A single unaligned library from a third-party SDK is enough to trigger rejection.
Understanding the Play Store Error
Your app does not support 16 KB memory page sizes.
This App Bundle contains native code that is not aligned to 16 KB page boundaries. Learn More →
This error appears in the Play Console under App Bundle Explorer → Messages after uploading your release AAB. It is a hard blocker — the release cannot be published until resolved.
You may also see two accompanying warnings:
This release no longer supports N devices that were supported in your previous release. This is caused by restricting abiFilters to 64-bit architectures only, dropping 32-bit ARM devices.
This App Bundle contains native code, and you’ve not uploaded debug symbols. Symbols help analyze crashes and ANRs. This is a warning, not a blocker, but should be resolved.
Upgrade NDK to Version 28
The most common root cause is using an NDK version older than 28. NDK 27 and below do not fully support 16 KB page alignment when compiling native code.
| NDK Version | 16 KB Support | Recommendation |
|---|---|---|
| < 27.x | ❌ None | Do not use |
| 27.x | ⚠️ Partial | Warnings become errors on Play Store |
| 28.x | ✅ Full | Required minimum |
Install NDK 28 in Android Studio
- Open SDK Manager
Go to Android Studio → Settings → Appearance & Behavior → System Settings → Android SDK
- Select SDK Tools tab
Check the “Show Package Details” checkbox in the bottom right corner.
- Install NDK 28.2.13676358
Expand “NDK (Side by side)” and check version
28.2.13676358, then click Apply.
Set NDK version in build.gradle.kts
// android/app/build.gradle.kts
android {
namespace = "com.example.app"
compileSdk = 35
ndkVersion = "28.2.13676358" // ✅ NDK 28 — 16 KB aligned
// ndkVersion = "27.0.12077973" // ❌ NDK 27 — fails Play Store
}
Update build.gradle.kts Completely
Beyond the NDK version, several packaging and build settings must be configured correctly. Here is a complete, production-ready build.gradle.kts for a Flutter app targeting Play Store compliance:
import java.util.Properties
plugins {
id("com.android.application")
id("com.google.gms.google-services")
id("kotlin-android")
id("dev.flutter.flutter-gradle-plugin")
}
android {
val keystoreProperties = Properties().apply {
load(File(rootDir, "key.properties").inputStream())
}
namespace = "com.example.app"
compileSdk = 35
ndkVersion = "28.2.13676358" // ✅ Critical
compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
defaultConfig {
applicationId = "com.example.app"
minSdk = 23
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
ndk {
// 64-bit only — drops 32-bit devices but enables 16KB compliance
abiFilters += listOf("arm64-v8a", "x86_64")
}
}
signingConfigs {
create("release") {
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
storeFile = file(keystoreProperties["storeFile"] as String)
storePassword = keystoreProperties["storePassword"] as String
}
}
buildTypes {
getByName("release") {
isMinifyEnabled = true
isShrinkResources = true
isCrunchPngs = false
signingConfig = signingConfigs.getByName("release")
ndk {
debugSymbolLevel = "FULL" // Required for debug symbols upload
}
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
packaging {
jniLibs {
useLegacyPackaging = false // ✅ Required for 16KB compliance
}
dex {
useLegacyPackaging = false
}
}
}
flutter {
source = "../.."
}
dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
implementation(enforcedPlatform("com.google.firebase:firebase-bom:33.13.0"))
implementation("com.google.firebase:firebase-analytics")
implementation("com.google.firebase:firebase-auth")
implementation("com.google.firebase:firebase-firestore")
implementation("com.google.android.gms:play-services-ads:23.6.0")
implementation("com.android.billingclient:billing:7.1.1")
}
kotlin {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
}
}
Diagnose Which .so File Is Failing
Updating the NDK alone often doesn’t fix the issue, because pre-built .so libraries bundled inside Flutter plugins and Android dependencies retain their original alignment. You need to identify exactly which library is failing.
Step 1 — Extract the AAB
cd build/app/outputs/bundle/release/
unzip -o app-release.aab -d aab_extracted 2>/dev/null
# List all native libraries
find aab_extracted -name "*.so" | sort
Step 2 — Run the alignment checker script
Save the following as check_alignment.py and run it from the same directory:
import struct, os
def check_alignment(filepath):
with open(filepath, 'rb') as f:
data = f.read()
if data[:4] != b'\x7fELF':
return "NOT_ELF"
bits = data[4] # 1=32bit, 2=64bit
if bits == 2: # 64-bit ELF
e_phoff = struct.unpack_from('<Q', data, 32)[0]
e_phentsize = struct.unpack_from('<H', data, 54)[0]
e_phnum = struct.unpack_from('<H', data, 56)[0]
for i in range(e_phnum):
off = e_phoff + i * e_phentsize
p_type = struct.unpack_from('<I', data, off)[0]
if p_type == 1: # PT_LOAD segment
return struct.unpack_from('<Q', data, off + 48)[0]
return None
root = "aab_extracted"
bad, ok = [], []
for dirpath, _, files in os.walk(root):
for fname in files:
if not fname.endswith(".so"): continue
fpath = os.path.join(dirpath, fname)
align = check_alignment(fpath)
entry = (os.path.basename(fpath), hex(align) if align else "N/A", fpath)
(bad if align == 0x1000 else ok).append(entry)
print(f"\n{'FILE':<45} {'ALIGN':<10} STATUS")
print("-" * 70)
for name, a, _ in sorted(ok + bad, key=lambda x: x[0]):
status = "✅ OK" if a == "0x4000" or a == "0x10000" else "❌ 4KB FAILING"
print(f"{name:<45} {a:<10} {status}")
print(f"\n❌ {len(bad)} FAILING ✅ {len(ok)} OK")
python3 check_alignment.py
Reading the output
The script will print a table like this. Any ❌ line is a Play Store blocker:
FILE ALIGN STATUS
----------------------------------------------------------------------
libapp.so 0x10000 ✅ OK
libbarhopper_v3.so 0x1000 ❌ 4KB FAILING
libdatastore_shared_counter.so 0x4000 ✅ OK
libface_detector_v2_jni.so 0x4000 ✅ OK
libflutter.so 0x10000 ✅ OK
libimage_processing_util_jni.so 0x1000 ❌ 4KB FAILING
❌ 2 FAILING ✅ 4 OK
Pinpoint the Exact Gradle Dependency
Once you know which .so files are failing, trace them back to their source Gradle artifact. This is the most important step — without it, you may update the wrong package.
# Search the Gradle cache for the failing .so files
find ~/.gradle -name "libbarhopper_v3.so" 2>/dev/null
find ~/.gradle -name "libimage_processing_util_jni.so" 2>/dev/null
# Also search pub-cache for Flutter plugin sources
find ~/.pub-cache -name "libbarhopper_v3.so" 2>/dev/null
The output will reveal the exact artifact name and version. For example:
# libbarhopper_v3.so comes from:
~/.gradle/caches/.../jetified-barcode-scanning-17.2.0/jni/arm64-v8a/libbarhopper_v3.so
# libimage_processing_util_jni.so comes from:
~/.gradle/caches/.../jetified-camera-core-1.3.1/jni/arm64-v8a/libimage_processing_util_jni.so
In the example above, the culprits are ML Kit Barcode Scanning 17.2.0 (ships libbarhopper_v3.so at 4 KB alignment) and CameraX camera-core 1.3.x (ships libimage_processing_util_jni.so at 4 KB alignment). These are pulled in transitively by Flutter plugins such as mobile_scanner and camera.
Force-Resolve the Failing Dependencies
The transitive dependencies pulling in the old .so files can be force-overridden in Gradle without changing your Flutter plugin versions. Add a resolution strategy to both your root and app-level Gradle files.
android/build.gradle.kts (root level)
allprojects {
configurations.all {
resolutionStrategy {
// ✅ ML Kit Barcode — 17.3.0+ is 16KB aligned
force("com.google.mlkit:barcode-scanning:17.3.0")
// ✅ CameraX — 1.4.1+ is 16KB aligned
force("androidx.camera:camera-core:1.4.1")
force("androidx.camera:camera-camera2:1.4.1")
force("androidx.camera:camera-lifecycle:1.4.1")
force("androidx.camera:camera-view:1.4.1")
}
}
}
android/app/build.gradle.kts (app level)
Add inside the android { } block, after the packaging { } section:
configurations.all {
resolutionStrategy {
force("com.google.mlkit:barcode-scanning:17.3.0")
force("androidx.camera:camera-core:1.4.1")
force("androidx.camera:camera-camera2:1.4.1")
force("androidx.camera:camera-lifecycle:1.4.1")
force("androidx.camera:camera-view:1.4.1")
}
}
Clear Gradle cache and rebuild
# Clear the specific transform caches for the failing artifacts
rm -rf ~/.gradle/caches/modules-2/files-2.1/com.google.mlkit
rm -rf ~/.gradle/caches/modules-2/files-2.1/androidx.camera
# Full clean rebuild
flutter clean
cd android && ./gradlew clean && cd ..
flutter pub get
flutter build appbundle --release \
--obfuscate \
--split-debug-info=./debug-symbols/
# Verify the forced versions are being used
cd android && ./gradlew app:dependencies | grep -E "barcode-scanning|camera-core"
The dependencies output should show the forced upgrade:
com.google.mlkit:barcode-scanning:17.2.0 -> 17.3.0 ✅
androidx.camera:camera-core:1.3.1 -> 1.4.1 ✅
Update Flutter Plugins to 16 KB-Safe Versions
Some Flutter plugins bundle their own native .so files directly in the plugin package (not via Gradle), which means Gradle force-resolution won’t help. These must be updated at the Flutter level.
| Plugin | Risk | Action |
|---|---|---|
| mobile_scanner | 🔴 High | Upgrade from v3 → v5.2.3+ (breaking changes) |
| google_mlkit_face_detection | 🔴 High | Upgrade to ^0.12.0 |
| camera | 🔴 High | Upgrade to ^0.11.0+ |
| speech_to_text | 🟡 Medium | Upgrade to latest ^7.x |
| flutter_tts | 🟡 Medium | Upgrade to latest ^4.x |
| google_maps_flutter | 🟡 Medium | Upgrade to ^2.10.0+ |
| razorpay_flutter | 🔴 High | Contact vendor or use web checkout |
Version 5 changes the onDetect callback signature. Update your code from onDetect: (barcode, args) to onDetect: (capture) { final barcodes = capture.barcodes; }.
If a third-party payment or analytics SDK ships a closed-source .so that is not 16 KB aligned, contact the vendor directly. As an interim, use their web-based checkout flow which avoids the native library entirely.
Upload Debug Symbols
The debug symbols warning does not block your release, but Google strongly recommends uploading them for crash analysis in Play Console and Firebase Crashlytics.
Generate symbols during build
Ensure debugSymbolLevel = "FULL" is set in your release build type (already shown in Fix 2). Then build:
flutter build appbundle --release \
--obfuscate \
--split-debug-info=./debug-symbols/
Upload to Play Console
- Go to App Bundle Explorer
Play Console → Your App → Release → App Bundle Explorer → select your version
- Click Downloads tab
Find the “Native Debug Symbols” option and click the upload button.
- Upload the symbols ZIP
The file is located at:
build/app/intermediates/merged_native_libs/release/mergeReleaseNativeLibs/out/lib/
The Device Count Warning Explained
When you restrict abiFilters to 64-bit architectures only (arm64-v8a, x86_64), your app drops support for 32-bit ARM devices (armeabi-v7a). This triggers the “N devices no longer supported” warning in Play Console.
| ABI Filter Config | Devices Supported | 16 KB Compliance |
|---|---|---|
| arm64-v8a, x86_64 | 64-bit only | ✅ Easier |
| arm64-v8a, armeabi-v7a, x86_64 | 32-bit + 64-bit | ⚠️ Harder |
32-bit ARM devices (armeabi-v7a) are mostly Android 8–10 era hardware. Google’s own data shows less than 1% of active Play Store devices are exclusively 32-bit as of 2025. Dropping them simplifies 16 KB compliance significantly and is the recommended path for new releases.
Complete Resolution Checklist
| # | Check | How to verify |
|---|---|---|
| 1 | NDK version is 28.2.13676358 | ndkVersion in build.gradle.kts |
| 2 | useLegacyPackaging = false for jniLibs and dex | packaging block in build.gradle.kts |
| 3 | abiFilters set to 64-bit only | defaultConfig ndk block |
| 4 | No .so files with 0x1000 alignment in AAB | Run check_alignment.py script |
| 5 | Transitive ML Kit / CameraX versions force-resolved | ./gradlew app:dependencies |
| 6 | Flutter plugins updated to 16 KB-safe versions | pubspec.lock versions |
| 7 | Debug symbols generated and uploaded | Play Console → App Bundle Explorer |
| 8 | Full clean rebuild performed | flutter clean + gradlew clean |
Final build command
flutter clean
cd android && ./gradlew clean && cd ..
flutter pub get
flutter build appbundle --release \
--obfuscate \
--split-debug-info=./debug-symbols/
# Re-check alignment after build
unzip -o build/app/outputs/bundle/release/app-release.aab \
-d build/app/outputs/bundle/release/aab_extracted 2>/dev/null
python3 check_alignment.py
# Should show: ❌ 0 FAILING ✅ N OK
Still Facing the 16 KB Error?
If you’ve followed all the steps above and the Play Store rejection persists, the issue may be in a closed-source SDK, a deeply nested transitive dependency, or a platform-specific build configuration. Our experts have resolved this issue across dozens of production Flutter apps.
Contact Our Experts →We’ll diagnose your specific AAB and resolve it — researchthinker.com


