skylot/jadx · error · JadxArgsValidateException

Found {} classes, single class output can't be used

Error message

Found {} classes, single class output can't be used

What it means

Thrown by SmaliDebugger.attach when an IOException occurs during the whole attach sequence: opening the TCP socket to host:port, running initJDWP (handshake + suspend + id-sizes), or constructing/starting the debugger. It is the top-level wrapper that says the JDWP connection to the debugged app could not be established, with the original IOException chained as the cause.

Source

Thrown at jadx-cli/src/main/java/jadx/cli/SingleClassMode.java:56

			}
			if (clsForProcess.contains(AFlag.DONT_GENERATE)) {
				throw new JadxArgsValidateException("Input class can't be saved by current jadx settings (marked as DONT_GENERATE)");
			}
			if (clsForProcess.isInner()) {
				clsForProcess = clsForProcess.getTopParentClass();
				LOG.warn("Input class is inner, parent class will be saved: {}", clsForProcess.getFullName());
			}
		} else {
			// singleClassOutput is set
			// expect only one class to be loaded
			List<ClassNode> classes = jadx.getRoot().getClasses().stream()
					.filter(c -> !c.isInner() && !c.contains(AFlag.DONT_GENERATE))
					.collect(Collectors.toList());
			int size = classes.size();
			if (size == 1) {
				clsForProcess = classes.get(0);
			} else {
				throw new JadxArgsValidateException("Found " + size + " classes, single class output can't be used");
			}
		}
		ICodeInfo codeInfo;
		try {
			codeInfo = clsForProcess.decompile();
		} catch (Exception e) {
			throw new JadxRuntimeException("Class decompilation failed", e);
		}
		String fileExt = SaveCode.getFileExtension(jadx.getRoot());
		File out;
		if (singleClassOutput == null) {
			out = new File(jadx.getArgs().getOutDirSrc(), clsForProcess.getClassInfo().getAliasFullPath() + fileExt);
		} else {
			if (singleClassOutput.endsWith(fileExt)) {
				// treat as file name
				out = new File(singleClassOutput);
			} else {
				// treat as directory

View on GitHub (pinned to e738a26571)

Solutions

  1. Re-run the ADB jdwp port forward for the target process id (`adb forward tcp:<port> jdwp:<pid>`) and retry attach.
  2. Confirm the app is installed as a debuggable build and is still running before attaching.
  3. Verify the device is online (`adb devices`) and only one debugger attaches at a time.
  4. Inspect the chained IOException cause to distinguish connection-refused vs timeout vs handshake failure, then address that specific layer.

Example fix

// before
SmaliDebugger dbg = SmaliDebugger.attach(host, port, listener);

// after
SmaliDebugger dbg;
try {
    dbg = SmaliDebugger.attach(host, port, listener);
} catch (SmaliDebuggerException e) {
    // cause reveals refused vs timeout vs handshake; re-establish the jdwp forward then retry once
    ensureJdwpForward(packageName, port);
    dbg = SmaliDebugger.attach(host, port, listener);
}
Defensive patterns

Strategy: retry

Validate before calling

// Verify the jdwp forward and target liveness before calling attach:
boolean ok = ensureJdwpForward(deviceSerial, packageName, port) && isProcessAlive(packageName);
if (!ok) {
    throw new IllegalStateException("jdwp forward/process not ready on port " + port);
}
return SmaliDebugger.attach(host, port, listener);

Try / catch

SmaliDebugger dbg = null;
for (int attempt = 0; attempt < 2 && dbg == null; attempt++) {
    try {
        dbg = SmaliDebugger.attach(host, port, listener);
    } catch (SmaliDebuggerException e) {
        Throwable cause = e.getCause();
        if (cause instanceof java.net.SocketTimeoutException || cause instanceof java.net.ConnectException) {
            ensureJdwpForward(packageName, port); // re-forward then retry once
            continue;
        }
        throw e; // handshake/protocol errors are not transient
    }
}
if (dbg == null) throw new SmaliDebuggerException("Attach failed after retry");

Prevention

When it happens

Trigger: Calling SmaliDebugger.attach(host, port, suspendListener) when the socket cannot connect or the JDWP setup I/O fails: connection refused (nothing listening on the ADB-forwarded port), connect timeout (device unreachable), stream read/write error, or initJDWP throwing. The 5-second socket timeout (setSoTimeout(5000)) makes slow/unresponsive targets surface here quickly.

Common situations: Wrong ADB jdwp port or forgot `adb forward tcp:port jdwp:pid`; the target app is not debuggable (android:debuggable=false) so no jdwp thread exists; device disconnected/offline; app already terminated before attach; another debugger already attached to the same process; firewall/USB transport issue.

Related errors


AI-assisted analysis of skylot/jadx@e738a26571 (2026-08-14). Data as JSON: /api/errors/7dde1563e3388fc2. Report an issue: GitHub.