2

I'm using GraalVM to execute JavaScript code in Java. I included the following Maven dependencies:

<dependency>
  <groupId>org.graalvm.js</groupId>
  <artifactId>js</artifactId>
  <version>24.1.2</version>
  <type>pom</type>
</dependency>
<dependency>
  <groupId>org.graalvm.polyglot</groupId>
  <artifactId>polyglot</artifactId>
  <version>24.1.2</version>
</dependency>
<dependency>
  <groupId>org.graalvm.js</groupId>
  <artifactId>js-scriptengine</artifactId>
  <version>24.1.2</version>
</dependency>

I'm trying to get the JavaScript engine like this:

ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");

But for some users or environments, this returns null, while for others it works as expected. Why is this happening, and how can I ensure the JavaScript engine is available consistently?

1 Answer 1

4

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Your description of the SPI mechanism as “unreliable” is misleading. In any normal Java runtime, it is completely reliable. Shaded jars (a.k.a “fat jars”) are a hack that was never supported, for exactly the reason you mention: they often don’t properly incorporate non-class data like META-INF/services from various other .jars. (It gets even more complicated with modular jars, which don’t use META-INF/services at all.)
Thanks for the clarification - you're absolutely right that SPI works reliably in standard Java runtimes when used correctly. My point was more about the practical issues that often arise in modern setups (fat JARs, modules, GraalVM native image), where SPI can silently fail. I'll reword the answer to make that clearer and avoid giving the wrong impression. Appreciate the input!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.