Skip to content

[Bug]: cap build android fails with cryptic MinSdkVersionException / Missing AndroidManifest.xml when using apksigner with AAB (related to #6909) #8428

@stormbeforesunsetbee

Description

@stormbeforesunsetbee

Capacitor Version

💊 Capacitor Doctor 💊

Latest Dependencies:

@capacitor/cli: 8.3.0
@capacitor/core: 8.3.0
@capacitor/android: 8.3.0
@capacitor/ios: 8.3.0

Installed Dependencies:

@capacitor/ios: not installed
@capacitor/android: 8.1.0
@capacitor/cli: 8.1.0
@capacitor/core: 8.1.0

[success] Android looking great! 👌

Other API Details

- Capacitor CLI version: (any version with `signingType` support, i.e. ≥ 5.1.0)
- Platform: Android
- `releaseType`: `'AAB'` (default)
- `signingType`: `'apksigner'`

Platforms Affected

  • iOS
  • Android
  • Web

Current Behavior

Describe the bug

When signingType is set to 'apksigner' and releaseType is 'AAB', npx cap build android fails during the signing step with a confusing Java stack trace. The root cause is that apksigner does not support the AAB format — it is an APK-only tool — but the CLI passes the .aab file to it without any validation.

Error output

✖ Signing Release - failed!
[error] Exception in thread "main" com.android.apksig.apk.MinSdkVersionException: Failed to determine APK's minimum supported platform version. Use --min-sdk-version to override
        at com.android.apksigner.ApkSignerTool.sign(ApkSignerTool.java:431)
        at com.android.apksigner.ApkSignerTool.main(ApkSignerTool.java:94)
        Caused by: com.android.apksig.apk.MinSdkVersionException: Failed to determine APK's minimum supported Android platform version
        at com.android.apksig.ApkSigner.getMinSdkVersionFromApk(ApkSigner.java:1019)
        at com.android.apksig.ApkSigner.sign(ApkSigner.java:301)
        ...
        Caused by: com.android.apksig.apk.ApkFormatException: Missing AndroidManifest.xml
        at com.android.apksig.ApkSigner.getAndroidManifestFromApk(ApkSigner.java:975)
        ...

Steps to reproduce

  1. Set signingType: 'apksigner' in capacitor.config.ts (or pass --signing-type apksigner on the CLI), while releaseType is 'AAB' (the default, or explicitly set).
  2. Run npx cap build android --keystorepath ... --keystorepass ...
  3. The Gradle build succeeds, but the signing step fails with the above error.

Additional context

The inverse problem also exists. The default signingType is 'jarsigner':

// cli/src/tasks/build.ts, line 49
signingtype: buildOptions.signingtype || config.android.buildOptions.signingType || 'jarsigner',

jarsigner uses SHA1withRSA / SHA1 digest (hardcoded in signWithJarSigner):

// cli/src/android/build.ts, lines 121–125
const signingArgs = [
  '-sigalg', 'SHA1withRSA',
  '-digestalg', 'SHA1',
  ...

SHA-1 signatures are rejected by Android for APKs targeting API 30+. So when building an APK without specifying a signing type, the default jarsigner path produces a binary that modern Android versions will refuse to install. apksigner should be the default for APK builds.

Root cause

The signing dispatch in cli/src/android/build.ts is a simple binary branch with no awareness of releaseType:

// cli/src/android/build.ts, lines 58–62
if (buildOptions.signingtype == 'jarsigner') {
  await signWithJarSigner(config, buildOptions, releasePath, signedReleaseName, unsignedReleaseName);
} else {
  await signWithApkSigner(config, buildOptions, releasePath, signedReleaseName, unsignedReleaseName);
}

The else branch routes any value that is not 'jarsigner' — including 'apksigner' — to signWithApkSigner, regardless of whether the output file is an APK or an AAB. When releaseType is 'AAB' (the default), this calls:

apksigner sign --in app-release.aab --out app-release-signed.aab ...

apksigner expects an APK, which is a ZIP with AndroidManifest.xml at the root. An AAB stores its manifest at base/manifest/AndroidManifest.xml, so apksigner cannot find it and throws ApkFormatException: Missing AndroidManifest.xml, which surfaces as the misleading MinSdkVersionException.

The releaseType and signingType options are documented and treated as independent, but they are not — apksigner is fundamentally incompatible with AAB.

Expected Behavior

The CLI should either:

  • Throw a clear, actionable error before invoking apksigner, explaining that apksigner does not support AAB files and instructing the user to use jarsigner for AAB builds or switch releaseType to 'APK'.
  • Or automatically select the correct signing tool based on releaseType, with a warning if the user's explicit signingType is incompatible.

Proposed fix

Replace the binary signing dispatch with logic that is aware of releaseType:

if (releaseTypeIsAAB) {
  if (buildOptions.signingtype === 'apksigner') {
    throw `apksigner does not support AAB files. Set signingType to 'jarsigner' for AAB builds, or set androidReleaseType to 'APK'.`;
  }
  await signWithJarSigner(config, buildOptions, releasePath, signedReleaseName, unsignedReleaseName);
} else {
  // APK: prefer apksigner; only use jarsigner if explicitly requested
  if (buildOptions.signingtype === 'jarsigner') {
    await signWithJarSigner(config, buildOptions, releasePath, signedReleaseName, unsignedReleaseName);
  } else {
    await signWithApkSigner(config, buildOptions, releasePath, signedReleaseName, unsignedReleaseName);
  }
}

This also requires:

  • Removing the hardcoded || 'jarsigner' default from cli/src/tasks/build.ts line 49, so buildAndroid can choose the correct tool based on releaseType when the user has not explicitly set signingType.
  • Updating the @default "jarsigner" JSDoc comment in cli/src/declarations.ts line 278 to reflect that the default is now context-dependent ('jarsigner' for AAB, 'apksigner' for APK).

Additional Information

This issue is related to #6909

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions