iBotPeaches/Apktool · error · OutDirExistsException

Destination directory (" + path + ") already exists. Use -f

Error message

Destination directory (" + path + ") already exists. Use -f option if you want to overwrite it.

What it means

OutDirExistsException thrown by ApkDecoder.decode() when the output directory exists, is a non-empty directory, and the force/overwrite option (-f / config.isForced()) is not set. Apktool refuses to clobber an existing decode tree to avoid mixing stale files from a previous decode with new output. Note that an existing EMPTY directory is silently removed (OS.rmdir) and recreated.

Source

Thrown at brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java:71

    private SmaliDecoder mSmaliDecoder;
    private ResDecoder mResDecoder;
    private BackgroundWorker mWorker;

    public ApkDecoder(File apkFile, Config config) {
        mApkFile = new ExtFile(apkFile);
        mConfig = config;
        mFirstError = new AtomicReference<>();
    }

    public void decode(File outDir) throws AndrolibException {
        if (!mApkFile.isFile() || !mApkFile.canRead()) {
            throw new InFileNotFoundException(mApkFile.getPath());
        }
        // We don't follow symlinks here for safety reasons.
        if (Files.exists(outDir.toPath(), LinkOption.NOFOLLOW_LINKS)) {
            if (Files.isDirectory(outDir.toPath(), LinkOption.NOFOLLOW_LINKS)) {
                if (!mConfig.isForced() && BrutIO.isNonEmptyDirectory(outDir)) {
                    throw new OutDirExistsException(outDir.getPath());
                }
                OS.rmdir(outDir);
            } else {
                if (!mConfig.isForced()) {
                    throw new OutDirExistsException(outDir.getPath());
                }
                OS.rmfile(outDir);
            }
        }
        OS.mkdir(outDir);

        if (mConfig.getJobs() > 1) {
            mWorker = new BackgroundWorker(mConfig.getJobs() - 1);
        }
        try {
            mApkInfo = new ApkInfo();
            mApkInfo.setVersion(mConfig.getVersion());
            mApkInfo.setApkFile(mApkFile);

View on GitHub (pinned to 79b63384d7)

Solutions

  1. Add the -f flag: `apktool d -f app.apk` (or config.setForced(true)).
  2. Delete or move the existing output directory before re-running.
  3. Decode into a fresh directory with -o <newdir>.

Example fix

# before
apktool d app.apk
# after
apktool d -f app.apk -o app_out
Defensive patterns

Strategy: validation

Validate before calling

if (outDir.exists() && outDir.isDirectory() && BrutIO.isNonEmptyDirectory(outDir)
        && !config.isForced()) {
    org.apache.commons.io.FileUtils.deleteQuietly(outDir); // or set forced
}

Try / catch

catch (OutDirExistsException e) { config.setForced(true); /* retry decode once */ }

Prevention

When it happens

Trigger: Running `apktool d app.apk` twice into the same default directory (app/) without -f; calling decode() programmatically with a Config that has setForced(false) while outDir contains files.

Common situations: Re-decoding after a failed or interrupted run; CI jobs that re-run in an uncleaned workspace; scripted loops that always use the same output directory.

Related errors


AI-assisted analysis of iBotPeaches/Apktool@79b63384d7 (2026-08-14). Data as JSON: /api/errors/dd3cb44b9e9d1a0d. Report an issue: GitHub.