ScriptEngine in GraalVM is part of the legacy Java Scripting API (JSR-223), which relies on the standard SPI (Service Provider Interface) mechanism to discover available scripting engines. In well-configured environments, this mechanism works reliably. However, in some modern setups - such as shaded
("fat") JARs, modular applications, or when using GraalVM native image - service discovery can silently fail due to how metadata like
META-INF/services/javax.script.ScriptEngineFactory is handled or omitted.
This file must be present in the correct location inside the JAR for the engine to be discovered. If it's missing or not correctly merged during packaging, getEngineByName("JavaScript") can return null.
As per the official GraalVM documentation:
GraalJS provides a JSR-223 compliant javax.script.ScriptEngine implementation for running JavaScript. Note that this feature is provided for legacy reasons to allow easier migration for implementations currently based on a ScriptEngine. We strongly encourage users to use the org.graalvm.polyglot.Context interface to control many of the settings directly and benefit from finer-grained security settings in GraalVM.
So, for a more stable and portable setup (especially when using native image or modular runtimes), it's recommended to use the org.graalvm.polyglot.Context API directly, which doesn't rely on SPI and is fully supported.
Example:
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
public class JsExample {
public static void main(String[] args) {
try (Context context = Context.newBuilder("js").allowAllAccess(true).build()) {
Value result = context.eval("js", "const name = 'JS'; `Hello, ${name}!`");
System.out.println(result.asString()); // Output: Hello, JS!
}
}
}
This approach is reliable across all environments where GraalJS is properly included.