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

  1. Check the caller method named in the message and the access flags of the referenced method; fix visibility (make it public) or the caller.
  2. If module encapsulation is the cause, add the required --add-opens/--add-exports opens.
  3. 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

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


AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14). Data as JSON: /api/errors/8668b830bce7adbe. Report an issue: GitHub.