oracle/graal · error · PermanentBailoutException
cannot link call from %s
Error message
cannot link call from %s
What it means
Thrown by BytecodeParser.lookupMethodInPool when ConstantPool.lookupMethod fails with IllegalAccessError while resolving a method reference (invokevirtual/invokestatic/invokespecial/invokeinterface) from the compiled method. The underlying linkage failure (e.g. package-private access across a split or renamed package) is wrapped into a permanent bailout naming the caller method.
Source
Thrown at compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/BytecodeParser.java:4805
protected JavaType lookupType(int cpi, int bytecode) {
maybeEagerlyResolve(cpi, bytecode);
JavaType result = constantPool.lookupType(cpi, bytecode);
assert !graphBuilderConfig.unresolvedIsError() || result instanceof ResolvedJavaType : Assertions.errorMessage(result);
return result;
}
private JavaMethod lookupMethod(int cpi, int opcode) {
maybeEagerlyResolve(cpi, opcode);
JavaMethod result = lookupMethodInPool(cpi, opcode);
assert !graphBuilderConfig.unresolvedIsError() || result instanceof ResolvedJavaMethod : result.format("%H.%n(%P)%R");
return result;
}
protected JavaMethod lookupMethodInPool(int cpi, int opcode) {
try {
return constantPool.lookupMethod(cpi, opcode, method);
} catch (IllegalAccessError e) {
throw new PermanentBailoutException(e, "cannot link call from %s", method.format("%H.%n(%p)"));
}
}
protected JavaField lookupField(int cpi, int opcode) {
maybeEagerlyResolve(cpi, opcode);
JavaField result = constantPool.lookupField(cpi, method, opcode);
return lookupField(result);
}
protected JavaField lookupField(JavaField result) {
assert !graphBuilderConfig.unresolvedIsError() || result instanceof ResolvedJavaField : "Not resolved: " + result;
if (parsingIntrinsic() || eagerInitializing) {
if (result instanceof ResolvedJavaField) {
ResolvedJavaType declaringClass = ((ResolvedJavaField) result).getDeclaringClass();
if (!declaringClass.isInitialized()) {
// Even with eager initialization, superinterfaces are not always initialized.
// See StaticInterfaceFieldTest
assert !eagerInitializing || declaringClass.isInterface() : "Declaring class not initialized but not an interface? " + declaringClass;View on GitHub (pinned to a66e9ccd1d)
Solutions
- Check the caller method named in the message and the access flags of the referenced method; fix visibility (make it public) or the caller.
- If module encapsulation is the cause, add the required --add-opens/--add-exports opens.
- Verify the correct version of the referenced class is on the classpath/modulepath (no stale duplicate with stricter access).
Defensive patterns
Strategy: try-catch
Try / catch
// PermanentBailoutException wrapping IllegalAccessError: fix access, do not catch. // The message names the caller method; inspect that method's constant pool refs.
Prevention
- Keep access modifiers consistent across packages split by builds.
- Add required --add-opens/--add-exports when running under JPMS with Graal.
When it happens
Trigger: constantPool.lookupMethod(cpi, opcode, method) throwing IllegalAccessError during graph building — the constant-pool entry for a call cannot be linked because the referenced method is not accessible to the caller (visibility, module boundaries, or package splits).
Common situations: Compile-time linking of a method that references a package-private member of another package (broken encapsulation that only fails under Graal's eager resolution); JPMS module boundaries not opened; bytecode transformations that rewrite access flags; multi-release JARs where a different version of a class restricts visibility.
Related errors
- Can not duplicate block with JSR data
- Too many loops in method
- Irreducible
- Non-reducible loop requires too much duplication. Setting %s
- Block that is reached by a fall through end of code is reach
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/8668b830bce7adbe.
Report an issue: GitHub.