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
- Always derive the nested scope's env from the enclosing scope (JNIMethodScope.scope().env()) rather than an independent env reference.
- Never cache JNIEnv across threads or VMs; acquire it per-thread via GetEnv/AttachCurrentThread.
- 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
- Never cache JNIEnv in statics; obtain it per thread/per VM instance.
- In multi-VM setups, key all scope-entry helpers on the JNIEnv instance so different VMs cannot nest.
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
- Could not parse bytes for type annotations
- Label used by instructions at following offsets has not been
- Could not detach thread correctly
- expected %s, got %s
- %s is not an annotation interface
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/079b3c085051c5c5.
Report an issue: GitHub.