iBotPeaches/Apktool · error · AndrolibException

Could not smali folder

Error message

Could not smali folder: {smaliDir.getName()}

What it means

SmaliBuilder.build failed while assembling smali sources back into a classes.dex: the smali/dexlib writer threw an IOException, DirectoryException, or RuntimeException (e.g. assembly error from invalid smali). The message names the smali folder being built and keeps the cause.

Solutions

  1. Read the attached cause — for smali syntax errors it pinpoints file and line; fix the reported smali file
  2. If you edited smali, validate registers/labels (smali/baksmali tooling or an IDE plugin) before rebuilding
  3. Check the output directory is writable and has free space; ensure no stale locked classes.dex blocks the write
  4. Retry the decode→build cycle without manual edits to confirm the original smali assembles; if it does not, update apktool (bundled smali version may be too old for the dex)

Example fix

# before
# edited smali_classes2/com/example/MainActivity.smali and broke a register count
apktool b app
# -> Could not smali folder: smali_classes2

# after
# fix the reported line (cause exception names file:line), e.g. restore .registers
apktool b app
Defensive patterns

Strategy: try-catch

Validate before calling

// Smoke-assemble smali before a full build is not cheap; instead keep edits minimal and
// verify the decoded project builds BEFORE editing:
//   apktool d app.apk -o orig && apktool b orig   // baseline must pass
// Then diff your edits against orig when a build fails.

Try / catch

try {
    new SmaliBuilder(apkInfo, outDir, apiLevel).build();
} catch (AndrolibException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("Could not smali folder")) {
        Throwable cause = e.getCause();
        // cause is often a smali compile error naming file:line — surface it directly
        log.error("Smali assembly failed in {}: {}", e.getMessage(), cause);
    } else { throw e; }
}

Prevention

When it happens

Trigger: Calling ApkDecoder.build() / SmaliBuilder on a decoded project where smali files contain syntax or semantic errors, where the output dex path is not writable, or where a parent directory cannot be created. The catch also covers RuntimeExceptions from the dex writer on malformed input.

Common situations: Rebuilding after hand-editing smali and introducing register/label errors; rebuilding on a read-only or full disk; smali edited with an api-level mismatch causing writer failures; leftover classes.dex that cannot be replaced.

Related errors


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

Appendix: source

Thrown at brut.apktool/apktool-lib/src/main/java/brut/androlib/smali/SmaliBuilder.java:96

                    if (cause != null) {
                        ex.initCause(cause);
                    }
                    throw ex;
                }
            }

            if (dexFile.exists()) {
                OS.rmfile(dexFile);
            } else {
                File parentDir = dexFile.getParentFile();
                if (parentDir != null) {
                    OS.mkdir(parentDir);
                }
            }

            dexBuilder.writeTo(new FileDataStore(dexFile));
        } catch (DirectoryException | IOException | RuntimeException ex) {
            throw new AndrolibException("Could not smali folder: " + smaliDir.getName(), ex);
        }
    }

    private boolean buildFile(File smaliFile, DexBuilder dexBuilder) throws IOException, RecognitionException {
        try (InputStreamReader reader = new InputStreamReader(
                Files.newInputStream(smaliFile.toPath()), StandardCharsets.UTF_8)) {
            smaliFlexLexer lexer = new smaliFlexLexer(reader, mApiLevel);
            lexer.setSourceFile(smaliFile);

            CommonTokenStream tokens = new CommonTokenStream(lexer);

            if (PRINT_TOKENS) {
                for (Token token : tokens.getTokens()) {
                    if (token.getChannel() != smaliParser.HIDDEN) {
                        System.out.println(smaliParser.tokenNames[token.getType()] + ": " + token.getText());
                    }
                }
            }

View on GitHub (pinned to 79b63384d7)