shwenzhang/AndResGuard · error · OptionsException

( ) must be a decimal number

Error message

<valueDescription> (<mLastOptionOriginalForm>) must be a decimal number: <value>

What it means

An option whose value must be an integer (parsed via Integer.parseInt) received a non-decimal value. The parser first fetched the required value successfully, then failed converting it, reporting the offending option, its original form, and the raw value. Typical of options like --min-sdk-version or --v1-signing-scheme variants taking numeric input.

Solutions

  1. Replace the value with a plain decimal integer (Android API level), e.g. `--min-sdk-version 24` instead of `--min-sdk-version 7.0`.
  2. Trim whitespace and hidden characters from the value, especially in shell scripts: `"${VAR//[[:space:]]/}"`.
  3. Convert hex or version strings to decimal API levels before passing them.
  4. Check which option the message names — it points at the exact flag receiving the bad value.

Example fix

// before
apksigner sign --min-sdk-version 7.0 --out app.apk in.apk
// after
apksigner sign --min-sdk-version 24 --out app.apk in.apk
Defensive patterns

Strategy: validation

Validate before calling

static String requireDecimal(String option, String value) {
  if (value == null || !value.matches("-?\\d+")) {
    throw new IllegalArgumentException(option + " must be a decimal integer (got: " + value + ")");
  }
  return value.trim();
}
// usage before building the command line:
String minSdk = requireDecimal("--min-sdk-version", System.getenv("MIN_SDK"));

Try / catch

try {
  apksignerSign(args);
} catch (OptionsException e) {
  if (e.getMessage().contains("must be a decimal number")) {
    System.err.println("Bad numeric flag: " + e.getMessage()
        + " — pass Android API levels as plain integers (e.g. 24, not 7.0).");
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling `apksigner sign --min-sdk-version 4.2 ...` or passing values with spaces, plus signs in unexpected places, hex notation (`0x18`), or an empty/non-numeric value where an int option is expected.

Common situations: Copying Android API level as a version string ("7.0") instead of the numeric API level (24); typos like `24 ` with trailing characters; localization or shell quoting introducing invisible characters; scripts interpolating non-numeric variables into numeric flags.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of shwenzhang/AndResGuard@e4df245d82 (2026-09-12). Data as JSON: /api/errors/4b07c0d6385d624c. Report an issue: GitHub.

Appendix: source

Thrown at AndResGuard-core/src/main/java/apksigner/OptionsParser.java:128

    String param = mParams[mIndex];
    if ("--".equals(param)) {
      // End of options marker
      throw new OptionsException(valueDescription + " missing after " + mLastOptionOriginalForm);
    }
    mIndex++;
    return param;
  }

  /**
   * Returns the value of the current numeric option, throwing an exception if the value is
   * missing or is not numeric.
   */
  public int getRequiredIntValue(String valueDescription) throws OptionsException {
    String value = getRequiredValue(valueDescription);
    try {
      return Integer.parseInt(value);
    } catch (NumberFormatException e) {
      throw new OptionsException(valueDescription
                                 + " ("
                                 + mLastOptionOriginalForm
                                 + ") must be a decimal number: "
                                 + value);
    }
  }

  /**
   * Gets the value of the current boolean option. Boolean options are not required to have
   * explicitly specified values.
   */
  public boolean getOptionalBooleanValue(boolean defaultValue) throws OptionsException {
    if (mLastOptionValue != null) {
      // --option=value form
      String stringValue = mLastOptionValue;
      mLastOptionValue = null;
      if ("true".equals(stringValue)) {
        return true;

View on GitHub (pinned to e4df245d82)