oracle/graal · error · JVMCIError

The VM does not expose the required Graal capability %s.

Error message

The VM does not expose the required Graal capability %s.

What it means

JNI scopes form a per-thread stack and every nested scope must use the same JNIEnv as its parent. The constructor throws IllegalStateException when a nested JNIMethodScope is opened with an env that != the env of the current top scope: mixing environments would make handle ownership and exception state incoherent, so the API rejects it immediately.

Source

Thrown at compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/api/runtime/GraalRuntime.java:38

 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package jdk.graal.compiler.api.runtime;

import jdk.vm.ci.common.JVMCIError;

public interface GraalRuntime {

    String getName();

    <T> T getCapability(Class<T> clazz);

    default <T> T getRequiredCapability(Class<T> clazz) {
        T ret = getCapability(clazz);
        if (ret == null) {
            throw new JVMCIError("The VM does not expose the required Graal capability %s.", clazz.getName());
        }
        return ret;
    }
}

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Always derive the nested scope's env from the enclosing scope (JNIMethodScope.scope().env()) rather than an independent env reference.
  2. Never cache JNIEnv across threads or VMs; acquire it per-thread via GetEnv/AttachCurrentThread.
  3. Separate per-VM call paths so scopes for different JNIEnv values never nest on the same thread.

Example fix

// before
try (JNIMethodScope outer = new JNIMethodScope("outer", envA)) {
    try (JNIMethodScope inner = new JNIMethodScope("inner", envB)) { // throws: envB != envA
        ...
    }
}

// after
try (JNIMethodScope outer = new JNIMethodScope("outer", envA)) {
    try (JNIMethodScope inner = new JNIMethodScope("inner", JNIMethodScope.scope().env())) { // same env
        ...
    }
}
Defensive patterns

Strategy: validation

Validate before calling

// Always open nested scopes with the enclosing scope's env:
JNIMethodScope top = JNIMethodScope.scope();
try (JNIMethodScope inner = new JNIMethodScope("inner", top.env())) { // guaranteed same env
    ...
}

Prevention

When it happens

Trigger: Opening a nested JNIMethodScope with a JNIEnv obtained from a different embedded VM or a different thread's env while a scope is already active; caching an env pointer and reusing it after the thread attached to another JNIEnv; reentrant code that receives env as a parameter but ignores the enclosing scope's env.

Common situations: Multi-VM setups (more than one embedded JVM/Espresso instance) sharing code paths; JNI code that stashes JNIEnv globally instead of per-thread; callbacks crossing VM boundaries inside one thread.

Related errors


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