clojure/clojure · error · MethodTooLargeException
Method too large: .
Error message
Method too large: ${className}.${methodName} ${descriptor} What it means
The JVM's class file format stores the Code attribute length in a 2-byte unsigned short, so any single method's bytecode may not exceed 65535 bytes. During ClassWriter.toByteArray(), MethodWriter detects code.length > 65535 and throws MethodTooLargeException naming the class, method and descriptor; this means the generator produced too much bytecode for one method, not that a parameter is wrong.
Solutions
- Split the generated code across multiple private helper methods invoked from the main one.
- Move large constant tables/data into static fields, arrays or resources loaded at runtime instead of inlined instructions.
- Reduce instrumentation scope: wrap the original method body in a try or delegate to a generated helper instead of inlining logic.
- For javac inputs, refactor the source method (extract methods, replace long switch chains with map lookups) and recompile.
- Use invokedynamic/MethodHandles to dispatch dynamically instead of generating huge switch statements.
Example fix
// before: emit every case inline in one giant method
for (Rule r : rules) { generateCaseInline(mv, r); } // >65535 bytes -> MethodTooLargeException
// after
generateDispatch(mv, rules); // emits tableswitch calling generated helpers
for (Rule r : rules) {
MethodVisitor h = cw.visitMethod(ACC_PRIVATE | ACC_STATIC, "rule$" + r.id, "()V", null, null);
generateCaseInline(h, r);
} Defensive patterns
Strategy: try-catch
Validate before calling
// Track approximate generated size while emitting; bail out early
private int approxBytes = 0;
void emit(Consumer<MethodVisitor> op, int estimatedBytes) {
if (approxBytes + estimatedBytes > 60000) {
throw new IllegalStateException("Generated method nearing 64KB code limit; split it");
}
approxBytes += estimatedBytes;
op.accept(mv);
}
Try / catch
try {
byte[] classBytes = classWriter.toByteArray();
} catch (MethodTooLargeException e) {
// e gives className, methodName, descriptor, codeSize
throw new IllegalStateException(
"Split generated method " + e.getMethodName() + " (code size " + e.getCodeSize() + ") into helpers", e);
} Prevention
- Design generators to emit one helper method per logical block instead of one huge method.
- Store large constant pools/data as static fields or resources rather than inlined instructions.
- Estimate emitted instruction size for huge switches/loops and split before the 65535-byte limit.
- For javac-produced code, keep source methods small (extract methods, replace long switch chains).
When it happens
Trigger: Dynamic class generation or instrumentation that creates one huge method: giant switch/string dispatch, deeply inlined generated code, a huge static initializer (<clinit>) with many constants, or a bytecode weaver injecting a very large block into an already-large method.
Common situations: Compilers for DSLs (rule engines, query builders, protobuf-like serializers) generating one method per schema; test-code generators creating thousands of asserts in one method; annotation processors inlining large templates; instrumenting large legacy methods with substantial added prologue code.
Understand the failure class
Background: "File too large" / "file size exceeds limit" errors: why libraries cap file sizes and how to fix them — this error's family across 46 libraries.
Related errors
- Bytecode not available, can't check class version
- Class versions V1_5 or less must use F_NEW frames.
- I/O error, can't check class version
- Invalid descriptor
- Invalid descriptor
AI-assisted analysis of clojure/clojure@f3b143341d (2026-09-09).
Data as JSON: /api/errors/fb127f6a03c19e4b.
Report an issue: GitHub.
Appendix: source
Thrown at src/jvm/clojure/asm/MethodWriter.java:2087
/**
* Returns the size of the method_info JVMS structure generated by this MethodWriter. Also add the
* names of the attributes of this method in the constant pool.
*
* @return the size in bytes of the method_info JVMS structure.
*/
int computeMethodInfoSize() {
// If this method_info must be copied from an existing one, the size computation is trivial.
if (sourceOffset != 0) {
// sourceLength excludes the first 6 bytes for access_flags, name_index and descriptor_index.
return 6 + sourceLength;
}
// 2 bytes each for access_flags, name_index, descriptor_index and attributes_count.
int size = 8;
// For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
if (code.length > 0) {
if (code.length > 65535) {
throw new MethodTooLargeException(
symbolTable.getClassName(), name, descriptor, code.length);
}
symbolTable.addConstantUtf8(Constants.CODE);
// The Code attribute has 6 header bytes, plus 2, 2, 4 and 2 bytes respectively for max_stack,
// max_locals, code_length and attributes_count, plus the bytecode and the exception table.
size += 16 + code.length + Handler.getExceptionTableSize(firstHandler);
if (stackMapTableEntries != null) {
boolean useStackMapTable = symbolTable.getMajorVersion() >= Opcodes.V1_6;
symbolTable.addConstantUtf8(useStackMapTable ? Constants.STACK_MAP_TABLE : "StackMap");
// 6 header bytes and 2 bytes for number_of_entries.
size += 8 + stackMapTableEntries.length;
}
if (lineNumberTable != null) {
symbolTable.addConstantUtf8(Constants.LINE_NUMBER_TABLE);
// 6 header bytes and 2 bytes for line_number_table_length.
size += 8 + lineNumberTable.length;
}
if (localVariableTable != null) {View on GitHub (pinned to f3b143341d)