skylot/jadx · warning · CodegenException

Anonymous inner class unlimited recursion detected. Convert

Error message

Anonymous inner class unlimited recursion detected. Convert class to inner: 

What it means

Thrown by InsnGen.inlineAnonymousConstructor() when the anonymous class being inlined is the same class as the current method's parent class — meaning infinite recursion would occur if inlining proceeded. Rather than hanging, jadx converts the class from anonymous to a regular inner class (removes ANONYMOUS_CLASS and DONT_GENERATE flags), sets RESTART_CODEGEN to trigger a fresh codegen pass, and throws this CodegenException to unwind the current generation. This is a recovery mechanism, not a terminal error.

Source

Thrown at jadx-core/src/main/java/jadx/core/codegen/InsnGen.java:812

		if (instArg.isThis()) {
			return false;
		}
		// instance arg should be of an outer class type
		if (!instArg.getType().equals(ctrCls.getDeclaringClass().getType())) {
			return false;
		}
		addArgDot(code, instArg);
		// can't use another dot, force short name of class
		return true;
	}

	private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
		cls.ensureProcessed();
		if (this.mth.getParentClass() == cls) {
			cls.remove(AType.ANONYMOUS_CLASS);
			cls.remove(AFlag.DONT_GENERATE);
			mth.getParentClass().getTopParentClass().add(AFlag.RESTART_CODEGEN);
			throw new CodegenException("Anonymous inner class unlimited recursion detected."
					+ " Convert class to inner: " + cls.getClassInfo().getFullName());
		}
		ArgType parent = cls.get(AType.ANONYMOUS_CLASS).getBaseType();
		// hide empty anonymous constructors
		for (MethodNode ctor : cls.getMethods()) {
			if (ctor.contains(AFlag.ANONYMOUS_CONSTRUCTOR)
					&& RegionUtils.isEmpty(ctor.getRegion())) {
				ctor.add(AFlag.DONT_GENERATE);
			}
		}
		code.attachDefinition(cls);
		code.add("new ");
		useClass(code, parent);
		MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
		if (callMth != null) {
			// copy var names
			List<RegisterArg> mthArgs = callMth.getArgRegs();
			int argsCount = Math.min(insn.getArgsCount(), mthArgs.size());

View on GitHub (pinned to e738a26571)

Solutions

  1. This exception is expected recovery — it should be caught by wrapCodeGen which retries. If it surfaces as a user-visible error, the restart path itself also failed (see error 69).
  2. If the restart also fails, report the class to jadx and use fallback mode.
  3. No action needed if the decompilation succeeds after the restart — this is by design.
Defensive patterns

Strategy: try-catch

Try / catch

// This exception is caught internally by wrapCodeGen which triggers a restart.
// If you see it surface, the restart also failed:
try {
    codeGen.makeCode(cls);
} catch (JadxRuntimeException e) {
    if (e.getMessage().equals("Code generation error after restart")) {
        // The restart triggered by anonymous-class recursion also failed
        Throwable cause = e.getCause();
        logger.error("Anonymous class recursion recovery failed for: {}",
            cls.getClassInfo().getFullName(), cause);
        args.setDecompilationMode(DecompilationMode.FALLBACK);
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: An anonymous class whose constructor triggers inlining of itself, creating a cycle. This can happen with self-referential anonymous class patterns in obfuscated or generated code. The exception forces a restart where the class is treated as a normal inner class instead.

Common situations: Obfuscated code with circular anonymous class references. Kotlin lambda or inner class patterns that reference their own enclosing type during construction. The error message names the class being converted.

Related errors


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