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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions