{"record":{"id":"93df96bc4f8e7528","repo":"skylot/jadx","slug":"class-decompilation-failed","errorCode":null,"errorMessage":"Class decompilation failed","messagePattern":"Class decompilation failed","errorType":"exception","errorClass":"JadxRuntimeException","httpStatus":null,"severity":"error","filePath":"jadx-cli/src/main/java/jadx/cli/SingleClassMode.java","lineNumber":63,"sourceCode":"\t\t\t}\n\t\t} else {\n\t\t\t// singleClassOutput is set\n\t\t\t// expect only one class to be loaded\n\t\t\tList<ClassNode> classes = jadx.getRoot().getClasses().stream()\n\t\t\t\t\t.filter(c -> !c.isInner() && !c.contains(AFlag.DONT_GENERATE))\n\t\t\t\t\t.collect(Collectors.toList());\n\t\t\tint size = classes.size();\n\t\t\tif (size == 1) {\n\t\t\t\tclsForProcess = classes.get(0);\n\t\t\t} else {\n\t\t\t\tthrow new JadxArgsValidateException(\"Found \" + size + \" classes, single class output can't be used\");\n\t\t\t}\n\t\t}\n\t\tICodeInfo codeInfo;\n\t\ttry {\n\t\t\tcodeInfo = clsForProcess.decompile();\n\t\t} catch (Exception e) {\n\t\t\tthrow new JadxRuntimeException(\"Class decompilation failed\", e);\n\t\t}\n\t\tString fileExt = SaveCode.getFileExtension(jadx.getRoot());\n\t\tFile out;\n\t\tif (singleClassOutput == null) {\n\t\t\tout = new File(jadx.getArgs().getOutDirSrc(), clsForProcess.getClassInfo().getAliasFullPath() + fileExt);\n\t\t} else {\n\t\t\tif (singleClassOutput.endsWith(fileExt)) {\n\t\t\t\t// treat as file name\n\t\t\t\tout = new File(singleClassOutput);\n\t\t\t} else {\n\t\t\t\t// treat as directory\n\t\t\t\tout = new File(singleClassOutput, clsForProcess.getShortName() + fileExt);\n\t\t\t}\n\t\t}\n\t\tFile resultOut = FileUtils.prepareFile(out);\n\t\tif (clsForProcess.getClassInfo().hasAlias()) {\n\t\t\tLOG.info(\"Saving class '{}' (alias: '{}') to file '{}'\",\n\t\t\t\t\tclsForProcess.getClassInfo().getFullName(), clsForProcess.getFullName(), resultOut.getAbsolutePath());","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/skylot/jadx/blob/e738a26571d02919f01df40de93bc9a44dee4e18/jadx-cli/src/main/java/jadx/cli/SingleClassMode.java#L45-L81","documentation":"Thrown by SmaliDebugger.initJDWP after the handshake and command exchange completed without IOException, but the reply packets did not satisfy the expected JDWP contract: either the Suspend reply or the IDSizes reply was not a reply packet or did not carry packet id 1. Reaching the trailing throw means the peer answered but its answers were not the expected replies, so the JDWP layer could not be initialized and field sizes remain unknown.","triggerScenarios":"initJDWP runs handshake, sends Suspend (packet id 1) then IDSizes (packet id 1), checking res.isReplyPacket() && res.getID() == 1 at each step. If either check fails the code falls through past the nested ifs to the final throw. Happens when the connected endpoint speaks a non-JDWP or non-conformant protocol, or when packets arrive out of order / with unexpected ids.","commonSituations":"The ADB forward target is not actually a jdwp endpoint (e.g. forwarded to a plain app port); a non-Android JVM or a custom agent that doesn't honor the JDWP suspend/IDSizes command/reply pairing; packet id mismatch from a half-initiated session; race with another client consuming the replies.","solutions":["Recreate the ADB jdwp forward against the correct process id and ensure nothing else is attached.","Confirm the target is an Android debuggable process exposing jdwp (not an arbitrary TCP service).","Restart the target app so its jdwp thread is fresh, then re-attach before any other client does.","Upgrade jadx/jdwp library; if reproducible on a stock device, report the unexpected reply as a protocol bug with the packet dump."],"exampleFix":"// before\nif (res.isReplyPacket() && res.getID() == 1) {\n    JDWP.IDSizes.IDSizesReplyData sizes = JDWP.IDSizes.decode(res.getBuf(), JDWP.PACKET_HEADER_SIZE);\n    return new JDWP(sizes);\n}\n// falls through to:\nthrow new SmaliDebuggerException(\"Failed to init JDWP.\");\n\n// after (diagnose instead of opaque failure)\nif (!res.isReplyPacket() || res.getID() != 1) {\n    throw new SmaliDebuggerException(\"JDWP init: expected reply id 1, got id=\" + res.getID()\n        + \" reply=\" + res.isReplyPacket() + \" (peer is not a conformant jdwp agent?)\");\n}","handlingStrategy":"retry","validationCode":"// Confirm the forward target is a jdwp endpoint (responds to handshake) before full init:\nif (!probeJdwpHandshake(host, port)) {\n    throw new IllegalStateException(\"Port \" + port + \" is not a conformant jdwp endpoint\");\n}\n// only then call SmaliDebugger.attach which runs initJDWP","typeGuard":null,"tryCatchPattern":"// initJDWP is private and called from attach; guard at the attach boundary\ntry {\n    return SmaliDebugger.attach(host, port, listener);\n} catch (SmaliDebuggerException e) {\n    if (\"Failed to init JDWP.\".equals(e.getMessage())) {\n        // non-conformant or stale endpoint: refresh forward + restart target, then retry once\n        restartDebuggableApp(packageName);\n        ensureJdwpForward(packageName, port);\n        return SmaliDebugger.attach(host, port, listener);\n    }\n    throw e;\n}","preventionTips":["Forward to the real jdwp pid, not an arbitrary port.","Restart the debuggable app so its jdwp thread is fresh before attaching.","Ensure only one client attaches; multiple consumers desync the reply stream.","Upgrade jadx/jdwp so init failures carry a diagnostic cause rather than an opaque message."],"tags":["debugger","jdwp","protocol","attach"],"backgroundTag":null,"analyzedSha":"e738a26571d02919f01df40de93bc9a44dee4e18","analyzedAt":"2026-08-14T00:10:24.238Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}