APK build failed: what each error means and how to fix it

Quick answer: Most failed APK builds come from five inputs, not from Gradle: the icon file, the package ID, the ZIP layout, the manifest or URL the builder fetches, and the version and signing metadata. Read the first error line, see which of those it points at, change that one thing and rebuild. A failure naming the toolchain, a killed daemon or a missing SDK is on the builder's side, and one retry is reasonable.

Each failure below is the symptom you see, why it happens and the fix. Gradle prints the cause first and a summary last, so read logs from the top.

Missing manifest, or no index.html in the ZIP

Symptom: a URL build fails with Build failed: Unexpected token '<', "<!DOCTYPE "... is not valid JSON, while a ZIP build is refused with a short 400 naming index.html.

Why: the builder fetched an HTML page where it expected a manifest, usually because the manifest sits at a non-standard path, or the host answers server requests with an anti-bot page.

Fix: link the manifest in your HTML head so /manifest.json is discoverable, or build from a ZIP with index.html at the root, inside dist, build, out or public, or at app/src/main/assets/public, www or build/web for Capacitor, Cordova and Flutter.

Package ID must be com.example.app

Symptom: the build fails while writing the manifest or Java package, often naming the bad segment: Namespace 'com.private.app' is not a valid Java package name as 'private' is a Java keyword.

Why: the package ID becomes the namespace, applicationId and Java package directory, so it must be a legal identifier: at least two segments, lowercase, each starting with a letter, no hyphens, no Java keyword in any segment.

Fix: use com.yourbrand.appname and decide it before publishing, because a Play listing's package ID is permanent.

Keystore mismatch on update

Symptom: the device refuses the install with App not installed as package conflicts with an existing package, or adb reports INSTALL_FAILED_UPDATE_INCOMPATIBLE: signatures do not match previously installed version. Play refuses a bundle signed with a key different from the app's registered key.

Why: Android installs an update only when package name and signing certificate match. A debug-signed build still on the phone, a keystore regenerated between builds, or a fresh key on a new machine all cause it.

Fix: on your own device uninstall the old copy; for a release, sign with the original keystore or request an upload key reset in Play Console.

Version code not higher than the live one

Symptom: Play rejects a correct upload with You need to use a different version code for your APK or Android App Bundle because you already have one with version code 1; on a device, an older build over a newer one is refused as a downgrade.

Why: versionCode may only increase and the store keeps the highest it has seen, while generated projects often hard-code 1, so the second upload collides.

Fix: raise versionCode on every build sent to a store; versionName, such as 1.4.2, is a label and may repeat.

HTTPS and cleartext URL problems

Symptom: the app opens to a blank white page, or shows net::ERR_CLEARTEXT_NOT_PERMITTED, and an http:// address is refused before the build.

Why: apps targeting Android 9 (API 28) and above block cleartext HTTP by default and a WebView obeys that. Causes: a hardcoded http:// API endpoint, an http image on an https page, or a redirect landing on http.

Fix: put a certificate on the origin, which can be free, and fix mixed content on the server. Where a fixed endpoint cannot be upgraded, permit cleartext for that one domain only.

Icon missing, or the wrong file type

Symptom: the resource compiler fails on the launcher icon with res/drawable/ic_launcher.png: AAPT: error: file failed to compile, usually followed by Android resource linking failed. Some builders substitute a placeholder, so the build succeeds with the wrong icon.

Why: the uploaded image is copied into the APK as the launcher icon, and a JPEG or WebP renamed to .png is not a compilable Android PNG. Density buckets expect 48 to 192 pixel icons, and the Play listing needs a separate 512x512 icon.

Fix: export a real square 512x512 PNG from the design file, never a renamed photo.

AAB uploaded where an APK is needed

Symptom: a tester taps the file and gets There was a problem parsing the package, or the file manager will not open it; Play Console refuses an .apk where it expects a bundle.

Why: an Android App Bundle is a publishing format, not an installable file. Play splits it into optimised APKs per device, so no phone installs an .aab from a file manager.

Fix: AAB for Play, APK for sideload links, testers, MDM and QA, both from the same project so signing and version match.

Gradle and JDK version drift

Symptom: the build dies before your code compiles, with a toolchain message instead of a resource error: Unsupported class file major version 61 for Java 17 classes on an older Gradle, or Gradle build daemon has been stopped: stop command received.

Why: plugin, Gradle, JDK and compileSdk move as a set, so a machine whose default JAVA_HOME changed fails a project that built last month. A stopped daemon is different: the process was killed mid-build, often memory pressure or a concurrent build.

Fix: pin the JDK your plugin expects, AGP 8.x wants JDK 17, build through ./gradlew, and retry a daemon-killed build once before debugging it.

Large asset bundles timing out

Symptom: the upload stalls at the end, the server answers 413 Request Entity Too Large, or no build appears in your history.

Why: several ceilings stack up, the request limit, the archive size accepted and per-file limits, while extraction writes those bytes to disk again. A ZIP carrying node_modules, .git or video breaks more than one.

Fix: upload the built output rather than the source tree, keep large media on a CDN or use a URL build that streams from your server, and read the status: 413 is size, 400 is content.

How to read a build log, and what to send when you ask for help

Read the first error, not the last. Gradle ends with a summary such as Execution failed for task ':app:processReleaseResources'; the cause is the first line above it naming a file, resource or symbol.

Classify before retrying: input problems are fixed by changing the input, site problems on the server or with a ZIP build, toolchain problems by one retry. Store rejections are not build failures at all, since Play can refuse a build that compiled fine.

When asking for help, send the build ID, URL or ZIP mode, package ID, format, the first error line and the log, plus what changed since it last worked. For install problems add the Android version, the on-screen wording and where the previous copy came from.

Frequently asked questions

Why does my build fail with Unexpected token '<', "<!DOCTYPE " is not valid JSON?

The builder asked for a web app manifest and received an HTML page, so the JSON parser failed on the first character. Either the manifest is not at a discoverable path, or the host answers server-side requests with an HTML page, which free hosting providers do routinely. Add a manifest link to your HTML head and check the response with curl, or build from a ZIP of your compiled files, which skips the host entirely.

Can I install an .aab file on a phone?

No. An Android App Bundle is a publishing format that Play Console splits into per-device APKs. A tester who receives a bundle gets a parse error or a file manager that will not open it. Build an APK for anything that installs directly on a device and keep the AAB for store uploads.

Why does my app say App not installed on Android?

On Android 11 and later, App not installed as package conflicts with an existing package almost always means a copy of the same package is already on the device, signed with a different key. Uninstall the old copy and install again, or sign the new build with the keystore the installed version used. It is a signature conflict, not a corrupted download.

Do I need my own keystore to publish an app?

You need a keystore that stays the same for the life of the app. With Play App Signing, Google holds the app signing key and you sign uploads with an upload key, so losing that key means requesting a reset rather than republishing. For sideload-only apps the key is entirely yours, and losing it means existing users have to uninstall before your next build will install.

What does 413 Request Entity Too Large mean on an upload?

The archive was refused for its size before anything was extracted, which is a different problem from a content refusal. A 400 with a short explanation means the builder read the file and did not accept what was inside it, for example no index.html. Strip node_modules, .git and media from the ZIP, or use a URL build, then upload again.

Why did my build work yesterday and fail today?

Check what changed outside your code first: a toolchain version bump, a different machine or JAVA_HOME, an expired signing configuration, or a Gradle daemon killed in a shared builder. Rerun once before editing anything, then compare what the log says now with the last successful build.

My app opens to a blank white screen after install. Where do I start?

Start with the URL and the network policy. An http origin, mixed content or a redirect to an insecure address produces a blank view and often the net::ERR_CLEARTEXT_NOT_PERMITTED message. If the site loads in mobile Chrome but not in the app, check the manifest and the entry point, and whether a ZIP build shipped an index.html from a folder the builder did not expect.

How do I make an app update install over the old version?

Keep the package ID identical, sign with the same keystore and raise the version code. Android requires all three for an in-place update. Change any one of them and the install either fails with a package conflict or is treated as a different app, so the user has to uninstall first and loses local data.