Tencent/tinker · error · DexException

unknown output extension: ${file}

Error message

unknown output extension: ${file}

What it means

Thrown by Dex(File) when the file name does not end with a recognized archive suffix (.zip/.jar/.apk via FileUtils.hasArchiveSuffix) nor with .dex. The constructor dispatches purely on the file-name extension, so any other spelling is rejected without reading the file.

Source

Thrown at third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/Dex.java:176

            }
        } else if (file.getName().endsWith(".dex")) {
            InputStream in = null;
            try {
                in = new BufferedInputStream(new FileInputStream(file));
                loadFrom(in, (int) file.length());
            } catch (Exception e) {
                throw new DexException(e);
            } finally {
                if (in != null) {
                    try {
                        in.close();
                    } catch (Exception e) {
                        // ignored.
                    }
                }
            }
        } else {
            throw new DexException("unknown output extension: " + file);
        }
    }

    private static void checkBounds(int index, int length) {
        if (index < 0 || index >= length) {
            throw new IndexOutOfBoundsException("index:" + index + ", length=" + length);
        }
    }

    private void loadFrom(InputStream in) throws IOException {
        loadFrom(in, 0);
    }

    private void loadFrom(InputStream in, int initSize) throws IOException {
        byte[] rawData = FileUtils.readStream(in, initSize);
        this.data = ByteBuffer.wrap(rawData);
        this.data.order(ByteOrder.LITTLE_ENDIAN);
        this.tableOfContents.readFrom(this);

View on GitHub (pinned to 1b7ea02c23)

Solutions

  1. Rename/copy the file to a literal .dex extension (or a proper archive suffix) before constructing Dex.
  2. Feed the bytes in via new Dex(InputStream/byte[]) to bypass name-based dispatch entirely.
  3. Check the exact filename string at runtime (case-sensitive) if the path is composed dynamically.

Example fix

// before
Dex dex = new Dex(stagedFile); // stagedFile = "patch.0352.tmp"

// after
File renamed = new File(stagedFile.getParentFile(), stagedFile.getName() + ".dex");
Files.copy(stagedFile.toPath(), renamed.toPath(), StandardCopyOption.REPLACE_EXISTING);
Dex dex = new Dex(renamed);
Defensive patterns

Strategy: validation

Validate before calling

String n = file.getName().toLowerCase(Locale.ROOT);
if (!(n.endsWith(".dex") || n.endsWith(".zip") || n.endsWith(".jar") || n.endsWith(".apk"))) {
    throw new IllegalArgumentException("unsupported dex input name: " + n);
}

Prevention

When it happens

Trigger: new Dex(new File("classes_dex.bak")), renamed artifacts, uppercase extensions (.DEX — hasArchiveSuffix/.endsWith are case-sensitive), or temp files whose suffix was changed mid-pipeline.

Common situations: Build pipelines that stage files under temporary names (foo.dex.tmp) and pass them before renaming; case-mismatched extensions on case-insensitive filesystems; files downloaded with the wrong suffix.

Related errors


AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14). Data as JSON: /api/errors/756d8c33abdcfec4. Report an issue: GitHub.