oracle/graal · error · IncompatibleClassChangeError
cannot invokeinterface %s
Error message
cannot invokeinterface %s
What it means
When this custom resolver emulates invokeinterface linkage, it applies the JVM rule that the resolved method must be public and declared in an interface (or java.lang.Object); otherwise it throws IncompatibleClassChangeError('cannot invokeinterface ...'), reproducing the check HotSpot performs at link time.
Source
Thrown at compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/classfile/ClassfileConstant.java:148
ExecutableRef(byte tag, DataInputStream stream) throws IOException {
super(tag, stream);
}
ResolvedJavaMethod resolve(ClassfileConstantPool cp, int opcode) {
if (method == null) {
ResolvedJavaType cls = cp.get(ClassRef.class, classIndex).resolve(cp);
NameAndType nameAndType = cp.get(NameAndType.class, nameAndTypeIndex);
String name = nameAndType.getName(cp);
String type = nameAndType.getType(cp);
if (opcode == Bytecodes.INVOKEINTERFACE) {
method = resolveMethod(cp.context, cls, name, type, false);
if (method == null) {
throw new NoSuchMethodError(cls.toJavaName() + "." + name + type);
}
if (!method.isPublic() || !(method.getDeclaringClass().isInterface() || method.getDeclaringClass().isJavaLangObject())) {
throw new IncompatibleClassChangeError("cannot invokeinterface " + method.format("%H.%n(%P)%R"));
}
} else if (opcode == Bytecodes.INVOKEVIRTUAL || opcode == Bytecodes.INVOKESPECIAL) {
method = resolveMethod(cp.context, cls, name, type, false);
if (method == null) {
throw new NoSuchMethodError(cls.toJavaName() + "." + name + type);
}
} else {
assert opcode == Bytecodes.INVOKESTATIC : Assertions.errorMessage(opcode, cp);
method = resolveMethod(cp.context, cls, name, type, true);
if (method == null) {
throw new NoSuchMethodError(cls.toJavaName() + "." + name + type);
}
}
}
return method;
}
}
View on GitHub (pinned to a66e9ccd1d)
Solutions
- Recompile the caller against the interface version actually present at runtime.
- If you generate bytecode, emit the correct opcode: INVOKESTATIC for static, INVOKESPECIAL for private interface methods, INVOKEVIRTUAL/INVOKESPECIAL for default-method calls.
- Align dependency versions so the compiled hierarchy matches the runtime hierarchy.
Example fix
// ASM bytecode generation // before mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "Iface", "staticHelper", "()V", true); // static method -> error // after mv.visitMethodInsn(Opcodes.INVOKESTATIC, "Iface", "staticHelper", "()V", false);
Defensive patterns
Strategy: try-catch
Validate before calling
// Before linking, check the invokeinterface contract with reflection
static void checkInvokeInterface(Class<?> iface, String name, Class<?>... params) throws NoSuchMethodException {
java.lang.reflect.Method m = iface.getMethod(name, params); // getMethod => public only
if (!iface.isInterface() && !m.getDeclaringClass().equals(Object.class)) {
throw new IncompatibleClassChangeError("cannot invokeinterface " + m);
}
} Try / catch
try {
JavaMethod m = pool.lookupMethod(index, opcode);
} catch (IncompatibleClassChangeError e) {
// Recompile the caller against the current interface version, or fix the opcode; not retryable
throw new IllegalStateException("Interface/class hierarchy mismatch at link time", e);
} Prevention
- Keep compile-time and runtime versions of interface-bearing jars identical.
- When generating bytecode with ASM, choose the opcode from the resolved method's modifiers (static -> INVOKESTATIC, private -> INVOKESPECIAL).
When it happens
Trigger: invokeinterface resolves to a non-public method (e.g. a private interface method, which since JDK 9 must be invoked with invokespecial) or to a method whose declaring class is a class, not an interface (hierarchy changed between compilation and runtime).
Common situations: Stale jars on the classpath where a method moved between class and interface; bytecode generators (ASM) emitting INVOKEINTERFACE for static/private interface methods; compiling against one interface version and running against another.
Related errors
- cannot link call from %s
- Unsupported class file version: %s.%s
- Invalid constant pool tag: %s
- Class circularity detected
- unknown primitive or void type character:
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/4f56bd711013e659.
Report an issue: GitHub.