GraalJS

GraalJS is a fast JavaScript language implementation built on top of GraalVM. It is ECMAScript-compliant, provides interoperability with Java and other Graal languages, common tooling, and, if run on the GraalVM JDK, provides the best performance with the Graal JIT compiler by default. You can also use GraalJS with Oracle JDK or OpenJDK.

GraalJS is a suitable replacement for projects wanting to migrate from Nashorn or Rhino to a JavaScript engine that supports new ECMAScript standards and features. You can easily add GraalJS to your Java application as shown below.

Getting Started with GraalJS on the JVM

To embed JavaScript in a Java host application, enable GraalJS by adding it as a project dependency. All necessary artifacts can be downloaded directly from Maven Central. All artifacts relevant to embedders can be found in the Maven dependency group org.graalvm.polyglot.

Below is the Maven configuration for a JavaScript embedding:

<dependency>
    <groupId>org.graalvm.polyglot</groupId>
    <artifactId>polyglot</artifactId>
    <version>${graaljs.version}</version>
</dependency>
<dependency>
    <groupId>org.graalvm.polyglot</groupId>
    <artifactId>js</artifactId>
    <version>${graaljs.version}</version>
    <type>pom</type>
</dependency>

This enables GraalJS which is built on top of Oracle GraalVM and licensed under the GraalVM Free Terms and Conditions (GFTC). Use artifactId js-community instead of js if you want to use GraalJS built on GraalVM Community Edition.

Go step-by-step to create a Maven project, embedding JavaScript in Java, and run it. This example application was tested with GraalVM for JDK 25 and the GraalVM Polyglot API version 25.1.3. See how to install GraalVM on the Downloads page.

  1. Create a new Maven Java project named “helloworld” in your favorite IDE or from your terminal with the following structure:
     ├── pom.xml
     └── src
         ├── main
         │   └── java
         │       └── com
         │           └── example
         │               └── App.java
    

    For example, you can run this command to create a new Maven project using the quickstart archetype:

     mvn archetype:generate -DgroupId=com.example -DartifactId=helloworld -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.5 -DinteractiveMode=false
    
  2. Replace the contents of App.java with the following code:
     package com.example;
    
     import org.graalvm.polyglot.*;
     import org.graalvm.polyglot.proxy.*;
    
     public class App {
    
         static String JS_CODE = "(function myFun(param){console.log('Hello ' + param + ' from JS');})";
    
         public static void main(String[] args) {
             String who = args.length == 0 ? "World" : args[0];
             System.out.println("Hello " + who + " from Java");
             try (Context context = Context.create()) {
                 Value value = context.eval("js", JS_CODE);
                 value.execute(who);
             }
         }
     }
    

    This example application uses the Polyglot API and returns a JavaScript function as a Java value.

  3. Add the following dependencies to pom.xml to include the JavaScript engine (GraalJS):
     <dependencies>
         <dependency>
             <groupId>org.graalvm.polyglot</groupId>
             <artifactId>polyglot</artifactId>
             <version>${graaljs.version}</version>
         </dependency>
         <dependency>
             <groupId>org.graalvm.polyglot</groupId>
             <artifactId>js</artifactId>
             <version>${graaljs.version}</version>
             <type>pom</type>
         </dependency>
     </dependencies>
    

    Set the GraalJS and GraalVM Polyglot API versions by adding a graaljs.version property to the <properties> section. Alternatively, you can replace ${graaljs.version} with the version string directly. For this example, use 25.1.3:

     <properties>
         <graaljs.version>25.1.3</graaljs.version>
     </properties>
    
  4. Add the Maven plugins for compiling the project into a JAR file and copying all runtime dependencies into a directory to your pom.xml file:
     <build>
         <plugins>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <version>3.13.0</version>
                 <configuration>
                     <fork>true</fork>
                 </configuration>
             </plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-jar-plugin</artifactId>
                 <version>3.4.2</version>
                 <configuration>
                     <archive>
                         <manifest>
                             <mainClass>com.example.App</mainClass>
                         </manifest>
                     </archive>
                 </configuration>
             </plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-dependency-plugin</artifactId>
                 <version>3.8.0</version>
                 <executions>
                     <execution>
                         <id>copy-dependencies</id>
                         <phase>package</phase>
                         <goals>
                             <goal>copy-dependencies</goal>
                         </goals>
                         <configuration>
                             <outputDirectory>${project.build.directory}/modules</outputDirectory>
                             <includeScope>runtime</includeScope>
                             <includeTypes>jar</includeTypes>
                         </configuration>
                     </execution>
                 </executions>
             </plugin>
         </plugins>
     </build>
    
  5. (Optional.) Add module-info.java to your application. If you would like to run your application on the module path, create a module-info.java file in src/main/java with the following contents:
     module com.example {
         requires org.graalvm.polyglot;
     }
    
  6. Compile and package the project:
     mvn clean package
    
  7. Run the application using GraalVM or another compatible JDK. If you’ve included module-info.java in your project (step 5), you can now run the application on the module path, using one of the following commands:
    java --module-path target/modules:target/helloworld-1.0-SNAPSHOT.jar --module com.example/com.example.App "GraalVM"
    java -p target/modules:target/helloworld-1.0-SNAPSHOT.jar -m com.example/com.example.App "GraalVM"
    

    Otherwise, you can run with the dependencies on the module path and the application on the class path:

    java --module-path target/modules --add-modules=org.graalvm.polyglot -cp target/helloworld-1.0-SNAPSHOT.jar com.example.App "GraalVM"
    java --module-path target/modules --add-modules=org.graalvm.polyglot -jar target/helloworld-1.0-SNAPSHOT.jar "GraalVM"
    

    Alternatively, you can run with everything on the class path as well (in this case you need to use * or specify all JAR files):

    java -cp "target/modules/*:target/helloworld-1.0-SNAPSHOT.jar" com.example.App "GraalVM"
    # or using shell expansion:
    java -cp "$(find target/modules -name '*.jar' | tr '\n' :)target/helloworld-1.0-SNAPSHOT.jar" com.example.App "GraalVM"
    java -cp "$(printf %s: target/modules/*.jar)target/helloworld-1.0-SNAPSHOT.jar" com.example.App "GraalVM"
    

    Note: We discourage bundling all dependencies into a single “fat” JAR (for example, using the Maven Assembly plugin) as it can cause issues and prevent ahead-of-time compilation with GraalVM Native Image. Instead, we recommend using the original, separate JAR files for all org.graalvm.* dependencies, preferably on the module path. Learn more in the Guide to Embedding Languages.

The source code unit can be represented with a String, as shown in the example, a file, read from URL, and other means. By wrapping the function definition (()), you return the function immediately:

Value f = context.eval("js", "(function f(x, y) { return x + y; })");
Value result = f.execute(19, 23);

You can also lookup Java types from JavaScript and instantiate them, as demonstrated below:

try (Context context = Context.newBuilder()
                           .allowHostAccess(HostAccess.newBuilder(HostAccess.ALL).build())
                           .allowHostClassLookup(className -> true)
                       .build()) {
    java.math.BigDecimal v = context.eval("js",
            "var BigDecimal = Java.type('java.math.BigDecimal');" +
            "BigDecimal.valueOf(10).pow(20)")
        .asHostObject();
    assert v.toString().equals("100000000000000000000");
}

The Polyglot API offers many other ways to access a guest language code from Java, for example, by directly accessing JavaScript objects, numbers, strings, and arrays. Learn more about JavaScript to Java interoperability and find more examples in the Java Interoperability guide.

GraalJS is also available as a standalone distribution that you can download from GitHub. Learn more here.

We provide the following documentation for GraalJS users:

Migration Guides

Learn more about migration from legacy environments:

Java Interoperability

This documentation shows you how to enable interoperability with Java and possible JavaScript-to-Java embedding scenarios.

Enabling Java Interoperability

As of GraalVM for JDK 21, all necessary artifacts can be downloaded directly from Maven Central. All artifacts relevant to embedders can be found in the Maven dependency group org.graalvm.polyglot. Learn more about the dependency setup in the Getting Started guide.

Polyglot Context

The preferred method of embedding JavaScript in Java is via Context. For that, a new org.graalvm.polyglot.Context is built with the hostAccess option allowing access and a hostClassLookup predicate defining the Java classes you allow access to:

Context context = Context.newBuilder("js")
    .allowHostAccess(HostAccess.ALL)
    //allows access to all Java classes
    .allowHostClassLookup(className -> true)
    .build();
context.eval("js", jsSourceCode);

See the Guide to Embedding Languages on how to interact with a guest language such as JavaScript from a Java host application.

ScriptEngine (JSR 223)

JavaScript running on a GraalVM JDK is fully compatible with JSR 223 and supports the ScriptEngine API. Internally, the GraalVM’s JavaScript ScriptEngine wraps a polyglot Context instance:

ScriptEngine eng = new ScriptEngineManager()
    .getEngineByName("graal.js");
Object fn = eng.eval("(function() { return this; })");
Invocable inv = (Invocable) eng;
Object result = inv.invokeMethod(fn, "call", fn);

See the ScriptEngine guide for more details on how to use it from GraalJS.

Access Java from JavaScript

GraalVM provides a set of features to allow interoperability from JavaScript to Java. While Rhino, Nashorn, and GraalJS have a mostly comparable overall feature set, they differ in exact syntax, and, partly, semantics.

Class Access

To access a Java class, GraalJS supports the Java.type(typeName) function:

var FileClass = Java.type('java.io.File');

If the host class lookup is allowed (allowHostClassLookup), the java global property is available by default. Existing code accessing, for example, java.io.File, should be rewritten to use the Java.type(name) function:

// GraalJS (and Nashorn) compliant syntax
var FileClass = Java.type("java.io.File");
// Backwards-compatible syntax
var FileClass = java.io.File;

GraalJS provides Packages, java, and similar global properties for compatibility. However, explicitly accessing the required class with Java.type is preferred whenever possible for two reasons:

  1. It resolves the class in one step instead of trying to resolve each property as a class.
  2. Java.type immediately throws a TypeError if the class cannot be found or is not accessible, rather than silently treating an unresolved name as a package.

The js.java-package-globals flag can be used to deactivate the global fields of Java packages (set to false to avoid creation of the fields; default is true).

Constructing Java Objects

A Java object can be constructed with JavaScript’s new keyword:

var FileClass = Java.type('java.io.File');
var file = new FileClass("myFile.md");

Field and Method Access

The static fields of a Java class, or the fields of a Java object, can be accessed like JavaScript properties:

var JavaPI = Java.type('java.lang.Math').PI;

Java methods can be called like JavaScript functions:

var file = new (Java.type('java.io.File'))("test.md");
var fileName = file.getName();

Conversion of Method Arguments

JavaScript is defined to operate on the double number type. GraalJS might internally use additional Java data types for performance reasons (for example, the int type).

When calling Java methods, a value conversion might be required. This happens when the Java method expects a long parameter, and an int is provided from GraalJS (type widening). If this conversion causes a lossy conversion, a TypeError is thrown:

//Java
void longArg   (long arg1);
void doubleArg (double arg2);
void intArg    (int arg3);
//JavaScript
javaObject.longArg(1);     //widening, OK
javaObject.doubleArg(1);   //widening, OK
javaObject.intArg(1);      //match, OK

javaObject.longArg(1.1);   //lossy conversion, TypeError!
javaObject.doubleArg(1.1); //match, OK
javaObject.intArg(1.1);    //lossy conversion, TypeError!

Note how the argument values have to fit into the parameter types. You can override this behavior using custom target type mappings.

Method Selection

Java allows overloading of methods by argument types. When calling from JavaScript to Java, the method with the narrowest available type that the actual argument can be converted to without loss is selected:

//Java
void foo(int arg);
void foo(short arg);
void foo(double arg);
void foo(long arg);
//JavaScript
javaObject.foo(1);              // will call foo(short);
javaObject.foo(Math.pow(2,16)); // will call foo(int);
javaObject.foo(1.1);            // will call foo(double);
javaObject.foo(Math.pow(2,32)); // will call foo(long);

To override this behavior, an explicit method overload can be selected using the javaObject['methodName(paramTypes)'] syntax. Parameter types need to be comma-separated without spaces, and object types need to be fully qualified (for example, 'get(java.lang.String,java.lang.String[])'). Note that this is different from Nashorn which allows extra spaces and simple names. In the example above, one might always want to call, for example, foo(long), even when foo(short) can be reached with lossless conversion (foo(1)):

javaObject['foo(int)'](1);
javaObject['foo(long)'](1);
javaObject['foo(double)'](1);

Note that the argument values still have to fit into the parameter types. You can override this behavior using custom target type mappings.

An explicit method selection can also be useful when the method overloads are ambiguous and cannot be automatically resolved as well as when you want to override the default choice:

//Java
void sort(List<Object> array, Comparator<Object> callback);
void sort(List<Integer> array, IntBinaryOperator callback);
void consumeArray(List<Object> array);
void consumeArray(Object[] array);
//JavaScript
var array = [3, 13, 3, 7];
var compare = (x, y) => (x < y) ? -1 : ((x == y) ? 0 : 1);

// throws TypeError: Multiple applicable overloads found
javaObject.sort(array, compare);
// explicitly select sort(List, Comparator)
javaObject['sort(java.util.List,java.util.Comparator)'](array, compare);

// will call consumeArray(List)
javaObject.consumeArray(array);
// explicitly select consumeArray(Object[])
javaObject['consumeArray(java.lang.Object[])'](array);

Note that there is currently no way to explicitly select constructor overloads. Future versions of GraalJS might lift that restriction.

Package Access

GraalJS provides a Packages global property:

> Packages.java.io.File
JavaClass[java.io.File]

Array Access

GraalJS supports the creation of Java arrays from JavaScript code. Both the patterns suggested by Rhino and Nashorn are supported:

//Rhino pattern
var JArray = Java.type('java.lang.reflect.Array');
var JString = Java.type('java.lang.String');
var sarr = JArray.newInstance(JString, 5);
// Nashorn pattern
var IntArray = Java.type("int[]");
var iarr = new IntArray(5);

The arrays created are Java types, but can be used in JavaScript code:

iarr[0] = iarr[iarr.length] * 2;

Map Access

In GraalJS you can create and access Java Maps, for example, java.util.HashMap:

var HashMap = Java.type('java.util.HashMap');
var map = new HashMap();
map.put('someKey', 'someValue');
map.get('someKey');

GraalJS supports iterating over such maps:

for (var [key, value] of map.entries()) {
    print(key);
    print(value);
}

List Access

In GraalJS you can create and access Java Lists, for example, java.util.ArrayList:

var ArrayList = Java.type('java.util.ArrayList');
var list = new ArrayList();
list.add(42);
list.add('foo');
list.add({});

for (var element of list) {
    print(element);
}

String Access

GraalJS can create Java strings with Java interoperability. The length of the string can be queried with the length property (note that length is a value property and cannot be called as a function):

var javaString = new (Java.type('java.lang.String'))("Java");
javaString.length === 4;

Note that GraalJS uses Java strings internally to represent JavaScript strings, so the above code and the JavaScript string literal "Java" are actually not distinguishable.

Iterating Properties

Properties (fields and methods) of Java classes and Java objects can be listed with a JavaScript Object.keys() function:

var m = Java.type('java.lang.Math');
for (var key of Object.keys(m)) { print(key); }
> E
> PI
> TAU
> abs
> sin
> ...

Access to JavaScript Objects from Java

JavaScript objects are exposed to Java code as instances of com.oracle.truffle.api.interop.java.TruffleMap. This class implements Java’s Map interface.

JavaImporter

The JavaImporter feature is available only in Nashorn compatibility mode (with the js.nashorn-compat option).

Console Output of Java Classes and Java Objects

GraalJS provides both print and console.log.

GraalJS provides a print built-in function compatible with Nashorn.

The console.log is provided by Node.js directly. It does not provide special treatment of interop objects. Note that the default implementation of console.log on GraalJS is just an alias for print, and Node’s implementation is only available when running on Node.js.

Exceptions

Exceptions thrown in Java code can be caught in JavaScript code. They are represented as Java objects:

try {
    Java.type('java.lang.Class')
    .forName("nonexistent");
} catch (e) {
    print(e.getMessage());
}

Promises

GraalJS provides support for interoperability between JavaScript Promise objects and Java. Java objects can be exposed to JavaScript code as thenable objects, allowing JavaScript code to await Java objects. Moreover, JavaScript Promise objects are regular JavaScript objects, and can be accessed from Java using the mechanisms described in this document. This allows Java code to be called back from JavaScript when a JavaScript promise is resolved or rejected.

Creating JavaScript Promise Objects That Can Be Resolved from Java

JavaScript applications can create Promise objects delegating to Java the resolution of the Promise instance. This can be achieved from JavaScript by using a Java object as the “executor” function of the JavaScript Promise. For example, Java objects implementing the following functional interface can be used to create new Promise objects:

@FunctionalInterface
public interface PromiseExecutor {
    void onPromiseCreation(Value onResolve, Value onReject);
}

Any Java object implementing PromiseExecutor can be used to create a JavaScript Promise:

// `javaExecutable` is a Java object implementing the `PromiseExecutor` interface
var myPromise = new Promise(javaExecutable).then(...);

JavaScript Promise objects can be created not only using functional interfaces, but also using any other Java object that can be executed by GraalJS (for example, any Java object implementing the Polyglot ProxyExecutable interface). More detailed example usages are available in the GraalJS unit tests.

Using await with Java Objects

JavaScript applications can use the await expression with Java objects. This can be useful when Java and JavaScript have to interact with asynchronous events. To expose a Java object to GraalJS as a thenable object, the Java object should implement a method called then() having the following signature:

void then(Value onResolve, Value onReject);

When await is used with a Java object implementing then(), GraalJS will treat the object as a JavaScript Promise. The onResolve and onReject arguments are executable Value objects that should be used by the Java code to resume or abort the JavaScript await expression associated with the corresponding Java object. More detailed example usages are available in the GraalJS unit tests.

Using JavaScript Promises from Java

Promise objects created in JavaScript can be exposed to Java code like any other JavaScript object. Java code can access such objects like normal Value objects, with the possibility to register new promise resolution functions using the Promise’s default then() and catch() functions. As an example, the following Java code registers a Java callback to be executed when a JavaScript promise resolves:

Value jsPromise = context.eval(ID, "Promise.resolve(42);");
Consumer<Object> javaThen = (value)
    -> System.out.println("Resolved from JavaScript: " + value);
jsPromise.invokeMember("then", javaThen);

More detailed example usages are available in the GraalJS unit tests.

Multithreading

GraalJS supports multithreading when used in combination with Java. More details about the GraalJS multithreading model can be found in the Multithreading documentation.

Extending Java classes

GraalJS provides support for extending Java classes and interfaces using the Java.extend function. Note that host access has to be enabled in a polyglot context for this feature to be available.

Java.extend

Java.extend(types...) returns a generated adapter Java class object that extends the specified Java class and/or interfaces. For example:

var Ext = Java.extend(Java.type("some.AbstractClass"),
                      Java.type("some.Interface1"),
                      Java.type("some.Interface2"));
var impl = new Ext({
  superclassMethod: function() {/*...*/},
  interface1Method: function() {/*...*/},
  interface2Method: function() {/*...*/},
  toString() {return "MyClass";}
});
impl.superclassMethod();

Super methods can be called via Java.super(adapterInstance). See a combined example:

var sw = new (Java.type("java.io.StringWriter"));
var FilterWriterAdapter = Java.extend(Java.type("java.io.FilterWriter"));
var fw = new FilterWriterAdapter(sw, {
    write: function(s, off, len) {
        s = s.toUpperCase();
        if (off === undefined) {
            fw_super.write(s, 0, s.length)
        } else {
            fw_super.write(s, off, len)
        }
    }
});
var fw_super = Java.super(fw);
fw.write("abcdefg");
fw.write("h".charAt(0));
fw.write("**ijk**", 2, 3);
fw.write("***lmno**", 3, 4);
print(sw); // ABCDEFGHIJKLMNO

Note that in the nashorn-compat mode, you can also extend interfaces and abstract classes using a new operator on a type object of an interface or an abstract class:

// --experimental-options --js.nashorn-compat
var JFunction = Java.type('java.util.function.Function');
 var sqFn = new JFunction({
   apply: function(x) { return x * x; }
});
sqFn.apply(6); // 36

GraalJS Compatibility

GraalJS is an ECMAScript-compliant JavaScript language runtime. This document explains the public API it presents for user applications written in JavaScript.

ECMAScript Language Compliance

GraalJS implements the ECMAScript (ECMA-262) specification and is fully compatible with the ECMAScript 2026 specification. New features are frequently added to GraalVM when they are confirmed to be part of ECMAScript 2026, see the CHANGELOG.md for details. Older versions starting from ECMAScript 5 can be enabled with a configuration option (by edition: --js.ecmascript-version=5 or by year: --js.ecmascript-version=2015). In a production environment, you might consider specifying a fixed ECMAScript version to be used, as future versions of GraalJS will use newer versions of the specification once available.

GraalJS provides the following function objects in the global scope as specified by ECMAScript, representing the JavaScript core library: Array, ArrayBuffer, Boolean, DataView, Date, Error, Function, JSON, Map, Math, Number, Object, Promise, Proxy, Reflect, RegExp, Set, SharedArrayBuffer, String, Symbol, TypedArray, WeakMap, and WeakSet.

Additional objects are available under options, for example, --js.temporal. Run js --help for the list of available options.

Several of these function objects and some of their members are only available when a certain version of the specification is selected for execution. For a list of methods provided, inspect the ECMAScript specification. Extensions to the specification are specified below.

Internationalization API (ECMA-402)

GraalJS comes with an implementation of the ECMA-402 Internationalization API, enabled by default (can be disabled using the following option: --js.intl-402=false). This includes the following extensions:

  • Intl.Collator
  • Intl.DateTimeFormat
  • Intl.DisplayNames
  • Intl.ListFormat
  • Intl.Locale
  • Intl.NumberFormat
  • Intl.PluralRules
  • Intl.RelativeTimeFormat
  • Intl.Segmenter

The functionality of a few other built-ins, such as toLocaleString, is also updated according to the ECMA-402 specification.

JavaScript Modules

GraalJS supports modules as defined by ECMAScript 6 and later. Be aware that the support for this feature continues to increase. Be sure to use the latest ECMAScript version for the all the latest features.

When loading modules via a polyglot Source, you can use the unofficial application/javascript+module MIME type to specify that you are loading a module. When loading with JavaScript code from a file, make sure the module is loaded from a file with the .mjs extension. Loading with the import keyword is not limited by that, and can import from a file of any extension.

Compatibility Extensions

The following objects and methods are available in GraalJS for compatibility with other JavaScript engines. Note that the behavior of such methods might not strictly match the semantics of those methods in all existing engines.

Language Features

Conditional Catch Clauses

GraalJS supports conditional catch clauses if the js.syntax-extensions option is enabled:

try {
    myMethod(); // can throw
} catch (e if e instanceof TypeError) {
    print("TypeError caught");
} catch (e) {
    print("another Error caught");
}

Global Properties

crypto

The optional crypto global property provides the Web Crypto API methods getRandomValues() and randomUUID(). Enable it with the experimental js.crypto option:

js --experimental-options --js.crypto
performance

The optional performance global property provides now(), timeOrigin, and toJSON() from the Web High Resolution Time API. Enable it with the stable js.performance option. The js launcher enables it by default.

load(source)
  • loads (parses and executes) the specified JavaScript source code

Source can be of type:

  • a String: the path of the source file or a URL to execute.
  • java.lang.URL: the URL is queried for the source code to execute if the js.load-from-url option is set to true.
  • java.io.File: the file is read for the source code to execute.
  • a JavaScript object: the object is queried for a name and a script property, which represent the source name and code, respectively.
  • all other types: the source is converted to a String.

load is available by default and can be deactivated by setting the js.load option to false.

print(...arg) and printErr(...arg)
  • prints the arguments on the console (stdout and stderr, respectively)
  • provides a best-effort human readable output

print and printErr are available by default and can be deactivated by setting the js.print option to false.

Methods of the console Global Object

A global console object is provided that offers several methods for debugging purposes. These methods strive to provide similar functionality as provided in other engines, but do not guarantee identical results.

Note that those methods behave differently when GraalJS is executed in Node.js mode (for example, the node executable is started instead of js). Node.js provides its own implementation that is used instead.

  • console.log, console.info, and console.debug: an alias for print(...arg)
  • console.error, and console.warn: similar to print, but using the error IO stream
  • console.assert(check, message): prints message when check is falsy
  • console.clear: clears the console window if possible
  • console.count(), and console.countReset(): counts and prints how many times it has been called, or resets this counter
  • console.group, and console.groupEnd: increases or decreases the indentation for succeeding outputs to the console
  • console.time(), console.timeLog(), and console.timeEnd(): starts a timer, prints the duration the timer has been active, or prints the duration and stops the timer, respectively

The console object is available by default and can be deactivated by setting the option js.console to false.

Additional Global Functions in the js Shell

quit(status)
  • exits the engine and returns the specified status code
read(file)
  • reads the content of file

The result is returned as a String.

The argument file can be of type:

  • java.io.File: the file is used directly.
  • all other types: file is converted to a String and interpreted as a file name.
readbuffer(file)
  • reads the content of file similar to the read function

The result is returned as a JavaScript ArrayBuffer object.

readline()
  • reads one line of input from the input stream

The result is returned as a String.

Object

Object.prototype.__defineGetter__(prop, func)
  • defines the prop property of this to be the getter function func

This functionality is deprecated in most JavaScript engines. In recent ECMAScript versions, getters and setters are natively supported by the language.

Object.prototype.__defineSetter__(prop, func)
  • defines the prop property of this to be the setter function func

This functionality is deprecated in most JavaScript engines. In recent ECMAScript versions, getters and setters are natively supported by the language.

Object.prototype.__lookupGetter__(prop)
  • returns the getter function for property prop of the object as set by __defineGetter__

This functionality is deprecated in most JavaScript engines. In recent ECMAScript versions, getters and setters are natively supported by the language.

Object.prototype.__lookupSetter__(prop)
  • returns the setter function for property prop of the object as set by __defineSetter__

This functionality is deprecated in most JavaScript engines. In recent ECMAScript versions, getters and setters are natively supported by the language.

Nashorn Scripting Mode

GraalJS provides a scripting mode compatible with the one provided by the Nashorn engine. It is enabled with the js.scripting option. Make sure to have --experimental-options set:

js --experimental-options --js.scripting=true

In scripting mode, several properties and functions are added to the global object, including readFully, readLine, $ARG, $ENV, and $EXEC.

There are migration guides available for code previously targeted to the Nashorn or Rhino engines.

GraalJS Extensions

Graal Object

The Graal object is provided as a property of the global object. It provides Graal-specific information. The existence of the property can be used to identify whether GraalJS is the current language engine:

if (typeof Graal != 'undefined') {
    print(Graal.versionECMAScript);
    print(Graal.versionGraalVM);
    print(Graal.isGraalRuntime());
}

The Graal object is available in GraalJS by default, unless deactivated by an option (js.graal-builtin=false).

Graal.versionECMAScript
  • provides the version number (year value) of the GraalJS ECMAScript compatibility mode
Graal.versionGraalVM
  • provides the version of GraalVM, if the current engine is executed on GraalVM
Graal.isGraalRuntime()
  • indicates if GraalJS is executed on a GraalVM-enabled runtime
  • If true, hot code is compiled by the Graal compiler, resulting in high peak performance.
  • If false, GraalJS will not be optimized by the Graal Compiler, typically resulting in lower performance.

Graal.setUnhandledPromiseRejectionHandler(handler)

  • provides the unhandled promise rejection handler when using option (js.unhandled-rejections=handler).
  • the handler is called with two arguments: (rejectionReason, unhandledPromise).
  • Graal.setUnhandledPromiseRejectionHandler can be called with null, undefined, or empty arguments to clear the handler.

Java

The Java object is only available when host class lookup is allowed. To access Java host classes and its members, they first need to be allowed by the host access policy, and when running from a native executable, be registered for runtime reflection.

Note that some functions require the Nashorn compatibility mode to be set (--js.nashorn-compat=true).

Java.type(className)

Java.type loads the specified Java class and returns a constructible object that has the static members (for example, methods and fields) of the class and can be used with the new keyword to construct new instances:

var BigDecimal = Java.type('java.math.BigDecimal');
var point1 = new BigDecimal("0.1");
var two = BigDecimal.TWO;
console.log(point1.multiply(two).toString());

Note that when used directly with the new operator, Java.type(...) needs to be enclosed in parentheses:

console.log(new (Java.type('java.math.BigDecimal'))("1.1").pow(15));
Java.from(javaData)

Java.from creates a shallow copy of the Java data structure (Array, List) as a JavaScript array.

In many cases, this is not necessary; you can typically use the Java data structure directly from JavaScript.

Java.to(jsData, javaType)

Java.to converts the argument to the Java type.

The source object jsData is expected to be a JavaScript array, or an array-like object with a length property. The target javaType can either be a String (for example, an "int[]") or a type object (such as Java.type("int[]")). Valid target types are Java arrays. When the target type is omitted, it defaults to Object[].

var jsArray = ["a", "b", "c"];
var stringArrayType = Java.type("java.lang.String[]");
var javaArray = Java.to(jsArray, stringArrayType);
assertEquals('class java.lang.String[]', String(javaArray.getClass()));
var javaArray = Java.to(jsArray);
assertEquals('class java.lang.Object[]', String(javaArray.getClass()));

The conversion methods as defined by ECMAScript (for example, ToString and ToDouble) are executed when a JavaScript value has to be converted to a Java type. Lossy conversion is disallowed and results in a TypeError.

Java.isJavaObject(obj)
  • returns true if obj is a Java host object
  • returns false for native JavaScript objects, as well as for objects of other polyglot languages
Java.isType(obj)
  • returns true if obj is an object representing the constructor and static members of a Java class, as obtained by Java.type() or package objects.
  • returns false for all other arguments
Java.typeName(obj)
  • returns the Java Class name of obj when obj represents a Java type (isType(obj) === true) or Java Class instance
  • returns undefined otherwise
Java.isJavaFunction(fn)
  • returns whether fn is an object of the Java language that represents a Java function
  • returns false for all other types, including native JavaScript function, and functions of other polyglot languages

This function is only available in Nashorn compatibility mode (--js.nashorn-compat=true).

Java.isScriptObject(obj)
  • returns whether obj is an object of the JavaScript language
  • returns false for all other types, including objects of Java and other polyglot languages

This function is only available in Nashorn compatibility mode (--js.nashorn-compat=true).

Java.isScriptFunction(fn)
  • returns whether fn is a JavaScript function
  • returns false for all other types, including Java function, and functions of other polyglot languages

This function is only available in Nashorn compatibility mode (--js.nashorn-compat=true).

Java.addToClasspath(location)
  • adds the specified location (a .jar file or directory path string) to Java’s classpath

Polyglot

The functions of the Polyglot object allow to interact with values from other polyglot languages.

The Polyglot object is available by default, unless deactivated by setting the js.polyglot-builtin option to false.

Polyglot.export(key, value)
  • exports the JavaScript value under the name key (a string) to the polyglot bindings:
    function helloWorld() { print("Hello, JavaScript world"); }
    Polyglot.export("helloJSWorld", helloWorld);
    

If the polyglot bindings already had a value identified by key, it is overwritten with the new value. The value may be any valid Polyglot value.

  • throws a TypeError if key is not a String or is missing
Polyglot.import(key)
  • imports the value identified by key (a string) from the polyglot bindings and returns it:
    var rubyHelloWorld = Polyglot.import("helloRubyWorld");
    rubyHelloWorld();
    

If no language has exported a value identified by key, undefined is returned.

  • throws a TypeError if key is not a string or missing
Polyglot.eval(languageId, sourceCode)
  • parses and evaluates the sourceCode with the interpreter identified by languageId

The value of sourceCode is expected to be a String (or convertible to one).

  • returns the evaluation result, depending on the sourceCode and/or the semantics of the language evaluated:
    var pyArray = Polyglot.eval('python', 'import random; [random.uniform(0.0, 1.0) for _ in range(1000)]');
    

Exceptions can occur when an invalid languageId is passed, when the sourceCode cannot be evaluated by the language, or when the executed program throws one.

Polyglot.evalFile(languageId, sourceFileName)
  • parses the file sourceFileName with the interpreter identified by languageId

The value of sourceFileName is expected to be a String (or convertible to one), representing a file reachable by the current path.

  • returns an executable object, typically a function:
    var rFunc = Polyglot.evalFile('R', 'myExample.r');
    var result = rFunc();
    

Exceptions can occur when an invalid languageId is passed, when the file identified by sourceFileName cannot be found, or when the language throws an exception during parsing (parse time errors, for example, syntax errors). Exceptions thrown by the evaluated program are only thrown once the resulting function is evaluated.

The Polyglot.evalFile function is available by default when the Polyglot builtin is available, unless deactivated by setting the js.polyglot-evalfile option to false. It is also available when js.debug-builtin is activated.

Debug

  • requires starting the engine with the js.debug-builtin option

Debug is a GraalJS specific function object that provides functionality for debugging JavaScript code and the JavaScript engine. This API might change without notice. Do not use for production purposes.

Global Functions

printErr(...arg)
  • behaves identically to print

The only difference is that the error stream is used to print to, instead of the default output stream.

loadWithNewGlobal(source, arguments)
  • behaves similarly to load function

The relevant difference is that the code is evaluated in a new global scope (Realm, as defined by ECMAScript).

Source can be of type:

  • java.lang.URL: the URL is queried for the source code to execute.
  • a JavaScript object: the object is queried for a name and a script property.
  • all other types: the source is converted to a String.

The value of arguments is provided to the loaded code upon execution.

Using JavaScript Modules and Packages in GraalJS

GraalJS is compatible with the latest ECMAScript standard, and can be run in a variety of Java-based embedding scenarios. Depending on the embedding, JavaScript packages and modules may be used in different ways.

Java Embedding via Context API

When embedded in a Java application (using the Context API), GraalJS can execute JavaScript applications and modules that do not depend on Node.js’ built-in modules such as 'fs', 'events', or 'http' or Node.js-specific functions such as setTimeout() or setInterval(). On the other hand, modules that depend on such Node.js builtins cannot be loaded in a GraalVM polyglot Context.

Supported NPM packages can be used in a JavaScript Context using one of the following approaches:

  1. Using a package bundler. For example, to combine multiple NPM packages in a single JavaScript Source file.
  2. Using ECMAScript (ES) modules on the local FileSystem. Optionally, a custom Truffle FileSystem can be used to configure how files are resolved.

By default, a Java Context does not load modules using the CommonJS require() function. This is because require() is a Node.js built-in function, and is not part of the ECMAScript specification. Experimental support for CommonJS modules can be enabled through the js.commonjs-require option as described below.

ECMAScript Modules (ESM)

GraalJS supports the full ES modules specification, including import statements, dynamic modules import using import(), and advanced features such as top-level await.

GraalJS supports import attributes using the with syntax:

import config from "./config.json" with { type: "json" };

Legacy import assertions using assert, and the js.import-assertions option, are no longer supported. Use import attributes and the js.import-attributes option instead.

Importing Source Text and Bytes

GraalJS can import the contents of a module without evaluating it. Enable import attributes together with the corresponding experimental feature:

js --experimental-options \
   --js.import-attributes \
   --js.import-text \
   --module application.mjs

Use the text import attribute to receive the source as a string:

import source from "./document.txt" with { type: "text" };

Enable --js.import-bytes and use the bytes import attribute to receive the source as a Uint8Array:

import data from "./data.bin" with { type: "bytes" };

ECMAScript modules can be loaded in a Context simply by evaluating the module sources. GraalJS loads ECMAScript modules based on their file extension. Therefore, any ECMAScript module should have file name extension .mjs. Alternatively, the module Source should have MIME type "application/javascript+module".

As an example, let’s assume that you have a file named foo.mjs containing the following simple ES module:

export class Foo {

    square(x) {
        return x * x;
    }
}

This ES module can be loaded in a polyglot Context in the following way:

public static void main(String[] args) throws IOException {

    String src = "import {Foo} from '/path/to/foo.mjs';" +
                 "const foo = new Foo();" +
                 "console.log(foo.square(42));";

    Context cx = Context.newBuilder("js")
                .allowIO(true)
                .build();

	cx.eval(Source.newBuilder("js", src, "test.mjs").build());
}

Note that the ES module file has .mjs extension. Also note that the allowIO() option is provided to enable IO access. More examples of ES modules usage are available here.

Module namespace exports

The --js.esm-eval-returns-exports option (false by default) can be used to expose the ES module namespace exported object to a Polyglot Context. This can be handy when an ES module is used directly from Java:

public static void main(String[] args) throws IOException {

    String code = "export const foo = 42;";

    Context cx = Context.newBuilder("js")
                .allowIO(true)
                .option("js.esm-eval-returns-exports", "true")
                .build();

    Source source = Source.newBuilder("js", code)
                .mimeType("application/javascript+module")
                .build();

    Value exports = cx.eval(source);
    // now the `exports` object contains the ES module exported symbols.
    System.out.println(exports.getMember("foo").toString()); // prints `42`
}

Truffle FileSystem

By default, GraalJS uses the built-in FileSystem of the polyglot Context to load and resolve ES modules. A FileSystem can be used to customize the ES modules loading process. For example, a custom FileSystem can be used to resolve ES modules using URLs:

Context cx = Context.newBuilder("js").fileSystem(new FileSystem() {

	private final Path TMP = Paths.get("/some/tmp/path");

    @Override
    public Path parsePath(URI uri) {
    	// If the URL matches, return a custom (internal) Path
    	if ("http://localhost/foo".equals(uri.toString())) {
        	return TMP;
		} else {
        	return Paths.get(uri);
        }
    }

	@Override
    public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
    	if (TMP.equals(path)) {
        	String moduleBody = "export class Foo {" +
                            "        square(x) {" +
                            "            return x * x;" +
                            "        }" +
                            "    }";
            // Return a dynamically-generated file for the ES module.
            return createByteChannelFrom(moduleBody);
        }
    }

    /* Other FileSystem methods not shown */

}).allowIO(true).build();

String src = "import {Foo} from 'http://localhost/foo';" +
             "const foo = new Foo();" +
             "console.log(foo.square(42));";

cx.eval(Source.newBuilder("js", src, "test.mjs").build());

In this simple example, a custom FileSystem is used to load a dynamically-generated ES module when an application attempts to import the http://localhost/foo URL.

A complete example of a custom Truffle FileSystem to load ES modules can be found here.

CommonJS Modules

By default, the Context API does not support CommonJS modules, and has no built-in require() function. In order to be loaded and used from a Context in Java, a CommonJS module needs to be bundled into a self-contained JavaScript source file. This can be achieved using one of the many popular open-source bundling tools such as Parcel, Browserify, and Webpack. Experimental support for CommonJS modules can be enabled through the js.commonjs-require option as described below.

Experimental support for CommonJS NPM modules in the Context API

The js.commonjs-require option provides a built-in require() function that can be used to load NPM-compatible CommonJS modules in a JavaScript Context. Currently, this is an experimental feature and not for production usage.

To enable CommonJS support, a JavaScript context can be created in the following way:

Map<String, String> options = new HashMap<>();
// Enable CommonJS experimental support.
options.put("js.commonjs-require", "true");
// (optional) directory where the NPM modules to be loaded are located.
options.put("js.commonjs-require-cwd", "/path/to/root/directory");
// (optional) Node.js built-in replacements as a comma separated list.
options.put("js.commonjs-core-modules-replacements",
            "buffer:buffer/," +
            "path:path-browserify");
// Create context with IO support and experimental options.
Context cx = Context.newBuilder("js")
                            .allowExperimentalOptions(true)
                            .allowIO(true)
                            .options(options)
                            .build();
// Require a module
Value module = cx.eval("js", "require('some-module');");

The "js.commonjs-require-cwd" option can be used to specify the main folder where NPM packages have been installed. As an example, this can be the directory where the npm install command was executed, or the directory containing your main node_modules/ directory. Any NPM module will be resolved relative to that directory, including any built-in replacement specified using "js.commonjs-core-modules-replacements".

Differences with Node.js built-in require() function

The Context built-in require() function can load regular NPM modules implemented in JavaScript, but cannot load native NPM modules. The built-in require() relies on the FileSystem, therefore I/O access needs to be enabled at context creation time using the allowIO option. The built-in require() aims to be largely compatible with Node.js, and we expect it to work with any NPM module that would work in a browser (for example, created using a package bundler).

Installing an NPM module to be used via the Context API

To be used from a JavaScript Context, an NPM module needs to be installed to a local directory, for example, by running the npm install command. At runtime, the option js.commonjs-require-cwd can be used to specify the main installation directory for NPM packages. The require() built-in function resolves packages according to the default Node.js’ package resolution protocol starting from the directory specified via js.commonjs-require-cwd. When no directory is provided with the option, the current working directory of the application will be used.

Node.js core modules mockups

Some JavaScript applications or NPM modules might need functionalities that are available in Node.js’ built-in modules (for example, 'fs' and 'buffer'). Such modules are not available in the Context API. Thankfully, the Node.js community has developed high-quality JavaScript implementations for many Node.js core modules (for example, the ‘buffer’ module for the browser). Such alternative module implementations can be exposed to a JavaScript Context using the js.commonjs-core-modules-replacements option, in the following way:

options.put("js.commonjs-core-modules-replacements", "buffer:my-buffer-implementation");

As the code suggests, the option instructs GraalJS to load a module called my-buffer-implementation when an application attempts to load the Node.js buffer built-in module using require('buffer').

Global symbols pre-initialization

An NPM module or a JavaScript application might expect certain global properties to be defined in the global scope. For example, applications or modules might expect the Buffer global symbol to be defined in the JavaScript global object. To this end, the application user code can use globalThis to patch the application’s global scope:

// define an empty object called 'process'
globalThis.process = {};
// define the 'Buffer' global symbol
globalThis.Buffer = require('some-buffer-implementation').Buffer;
// import another module that might use 'Buffer'
require('another-module');

Multithreading

Running JavaScript on GraalVM supports multithreading. Depending on the usage scenario, threads can be used to execute parallel JavaScript code using multiple Context objects, or multiple Worker threads.

Multithreading with Java and JavaScript

Multithreading is supported when running JavaScript in the context of Java interoperability. The basic model of multithreaded execution supported by GraalVM is a “share-nothing” model that should be familiar to any JavaScript developer:

  1. An arbitrary number of JavaScript Contexts can be created, but they should be used by one thread at a time.
  2. Concurrent access to JavaScript objects is not allowed: any JavaScript object cannot be accessed by more than one thread at a time.
  3. Concurrent access to Java objects is allowed: any Java object can be accessed by any Java or JavaScript thread, concurrently.

A JavaScript Context cannot be accessed by two or more threads, concurrently, but it is possible to access the same Context from multiple threads using proper syncronization, to ensure that concurrent access never happens.

Examples

The GraalJS unit tests contain several examples of multithreaded Java/JavaScript interactions. The most notable ones describe how:

  1. Multiple Context objects can be executed in multiple threads.
  2. JavaScript values created by one thread can be used from another thread when proper synchronization is used.
  3. A Context can be accessed from multiple threads when proper synchronization is used.
  4. Java concurrency can be used from JavaScript.
  5. Java objects can be accessed by multiple JavaScript threads, concurrently.

Migration Guide from Nashorn to GraalJS

This guide serves as a migration guide for code previously targeted to the Nashorn engine. See the Java Interoperability guide for an overview of supported Java interoperability features.

The Nashorn engine has been deprecated in JDK 11 as part of JEP 335 and has been removed from JDK15 as part of JEP 372.

GraalJS can step in as a replacement for JavaScript code previously executed on the Nashorn engine. GraalJS provides all the features for JavaScript previously provided by Nashorn. Many are available by default, some are behind options, and others require minor modifications to your source code.

Both Nashorn and GraalJS support a similar set of syntax and semantics for Java interoperability. One notable difference is that GraalJS takes a secure by default approach, meaning some features need to be explicitly enabled that were available by default on Nashorn. The most important differences relevant for migration are listed here.

Nashorn features available by default (dependent on security settings):

  • Java.type, Java.typeName
  • Java.from, Java.to
  • Java.extend, Java.super
  • Java package globals: Packages, java, javafx, javax, com, org, edu

Nashorn Compatibility Mode

GraalJS provides a Nashorn compatibility mode. Some of the functionality necessary for Nashorn compatibility is only available when the js.nashorn-compat option is enabled. This is the case for Nashorn-specific extensions that GraalJS does not want to expose by default.

Note that you have to unlock experimental features to use this option. Further note that setting this option defeats the secure by default approach of GraalJS in some cases, for example, when operating on a legacy ScriptEngine.

When you use the Nashorn compatibility mode, by default, ECMAScript 5 is set as compatibility level. You can specify a different ECMAScript version using the js.ecmascript-version option. Note that this might conflict with full Nashorn compatibility. A code example how to set the option is given near the end of this section.

The js.nashorn-compat option can be set:

  • By using a command line option:
      js --experimental-options --js.nashorn-compat=true
    
  • By using the Polyglot API:
      import org.graalvm.polyglot.Context;
    
      try (Context context = Context.newBuilder().allowExperimentalOptions(true).option("js.nashorn-compat", "true").build()) {
          context.eval("js", "print(__LINE__)");
      }
    
  • By using a system property when starting a Java application (remember to enable allowExperimentalOptions on the Context.Builder in your application as well):
      java -Dpolyglot.js.nashorn-compat=true MyApplication
    

Functionality only available under the nashorn-compat option includes:

  • Java.isJavaFunction, Java.isJavaMethod, Java.isScriptObject, Java.isScriptFunction
  • new Interface|AbstractClass(fn|obj)
  • JavaImporter
  • JSAdapter
  • java.lang.String methods on string values
  • load("nashorn:parser.js"), load("nashorn:mozilla_compat.js")
  • exit, quit

The js.ecmascript-version option can be set in similar fashion. As this is a supported option, there is no need to provide the experimental-options option just for setting the ecmascript-version:

js --js.ecmascript-version=2020

Nashorn Syntax Extensions

Nashorn syntax extensions can be enabled using the js.syntax-extensions experimental option. They are also enabled by default in the Nashorn compatibility mode (js.nashorn-compat).

GraalJS vs Nashorn

GraalJS differs from Nashorn in some aspects that were intentional design decisions.

Secure by Default

GraalJS takes a secure by default approach. Unless explicitly permitted by the embedder, JavaScript code cannot access Java classes or access the file system, among other restrictions. Several features of GraalJS, including Nashorn compatibility features, are only available when the relevant security settings are permissive enough.

Make sure you understand the security implications of any change that lifts the secure default limits to your application and the host system.

For a full list of available settings, see Context.Builder. Those options can be defined when building the context with the Polyglot API.

Options frequently required to enable features of GraalJS are:

  • allowHostAccess(): configure which public constructors, methods or fields of public classes are accessible by a guest application. Use HostAccess.EXPLICIT or a custom HostAccess policy to selectively enable access. Set to HostAccess.ALL to allow unrestricted access.
  • allowHostClassLookup(): set a filter that specifies the Java host classes that can be looked up by a guest application. Set to the Predicate className -> true to allow lookup of all classes.
  • allowIO(): allow a guest language to perform unrestricted IO operations on the host system, required, for example, to load() from the file system. Set to true to enable IO.

If you run code on the legacy ScriptEngine, see Setting Options via Bindings regarding how to set them there.

Finally, note that the nashorn-compat mode enables the relevant options when executing code on the ScriptEngine (but not on Context), to provide better compatibility with Nashorn in that setup.

Launcher Name js

GraalJS comes with a binary launcher named js. Note that, depending on the build environment, GraalJS might still ship Nashorn and its jjs launcher.

ScriptEngine Name graal.js

GraalJS is shipped with support for ScriptEngine. It registers under several names, including “graal.js”, “JavaScript”, and “js”. Be sure to activate the Nashorn compatibility mode as described above if you need full Nashorn compatibility. Depending on the build setup, GraalJS might still ship Nashorn and provide it via ScriptEngine. For more details, see ScriptEngine Implementation.

ClassFilter

GraalJS provides a class filter when starting with a polyglot Context. See Context.Builder.hostClassFilter.

Fully Qualified Names

GraalJS requires the use of Java.type(typename). It does not support accessing classes just by their fully qualified class name by default. Java.type brings more clarity and avoids the accidental use of Java classes in JavaScript code. For example, look at this pattern:

var bd = new java.math.BigDecimal('10');

It should be expressed as:

var BigDecimal = Java.type('java.math.BigDecimal');
var bd = new BigDecimal('10');

Lossy Conversion

GraalJS does not allow lossy conversions of arguments when calling Java methods. This could lead to bugs with numeric values that are hard to detect.

GraalJS always selects the overloaded method with the narrowest possible argument types that can be converted to without loss. If no such overloaded method is available, GraalJS throws a TypeError instead of lossy conversion. In general, this affects which overloaded method is executed.

Custom targetTypeMappings can be used to customize behavior. See HostAccess.Builder#targetTypeMapping.

ScriptObjectMirror Objects

GraalJS does not provide objects of the class ScriptObjectMirror. Instead, JavaScript objects are exposed to Java code as objects implementing Java’s Map interface.

Code referencing ScriptObjectMirror instances can be rewritten by changing the type to either an interface (Map or List) or the polyglot Value class which provides similar capabilities.

Multithreading

Running JavaScript on GraalVM supports multithreading by creating several Context objects from Java code. Contexts can be shared between threads, but each context must be accessed by a single thread at a time. Multiple JavaScript engines can be created from a Java application, and can be safely executed in parallel on multiple threads:

Context polyglot = Context.create();
Value array = polyglot.eval("js", "[1,2,42,4]");

GraalJS does not allow the creation of threads from JavaScript with access to the current Context. Moreover, GraalJS does not allow concurrent threads to access the same Context at the same time. This could lead to unmanageable synchronization problems like data races in a language that is not prepared for multithreading. For example:

new Thread(function() {
    print('printed from another thread'); // throws Exception due to potential synchronization problems
}).start();

JavaScript code can create and start threads with Runnables implemented in Java. The child thread may not access the Context of the parent thread or of any other polyglot thread. In case of violations, an IllegalStateException will be thrown. A child thread may create a new Context instance, though.

new Thread(aJavaRunnable).start(); // allowed on GraalJS

With proper synchronization in place, multiple contexts can be shared between different threads. The example Java applications using JavaScript Contexts from multiple threads can be found here.

Extensions Only Available in Nashorn Compatibility Mode

The following JavaScript extensions available in Nashorn are deactivated in GraalJS by default. They are provided in the Nashorn compatibility mode. It is highly recommended not to implement new applications based on those features, but only to use it as a means to migrate existing applications to GraalVM.

String length Property

GraalJS does not treat the length property of a String specially. The canonical way of accessing the String length is reading the length property:

myJavaString.length;

Nashorn enables users to access length as both a property and a function. Existing function calls length() should be expressed as property access. Nashorn behavior is mimicked in the Nashorn compatibility mode.

Java Packages in the JavaScript Global Object

GraalJS requires the use of Java.type instead of fully qualified names. In the Nashorn compatibility mode, the following Java packages are added to the JavaScript global object: java, javafx, javax, com, org, and edu.

JavaImporter

The JavaImporter feature is available only in the Nashorn compatibility mode.

JSAdapter

The use of the non-standard JSAdapter feature is discouraged and should be replaced with the equivalent standard Proxy feature. For compatibility, JSAdapter is still available in the Nashorn compatibility mode.

Java.* Methods

Several methods provided by Nashorn on the Java global object are available only in the Nashorn compatibility mode, or currently not supported by GraalJS. Available in the Nashorn compatibility mode are: Java.isJavaFunction, Java.isJavaMethod, Java.isScriptObject, and Java.isScriptFunction. Java.asJSONCompatible is currently not supported.

Accessors

In the Nashorn compatibility mode, GraalJS allows users to access getters and setters just by using the names as properties, while omitting get, set, or is:

var Date = Java.type('java.util.Date');
var date = new Date();

var myYear = date.year; // calls date.getYear()
date.year = myYear + 1; // calls date.setYear(myYear + 1);

GraalJS mimics the behavior of Nashorn regarding the ordering of the access:

  • In case of a read operation, GraalJS will first try to call a getter with the name get and the property name in camel case. If that is not available, a getter with the name is and the property name in camel case is called. In the second case, unlike Nashorn, the resulting value is returned even if it is not of type boolean. Only if both methods are not available, the property itself will be read.
  • In case of a write operation, GraalJS will try to call a setter with the name set and the property name in camel case, providing the value as argument to that function. If the setter is not available, the property itself will be written.

Note that Nashorn (and thus, GraalJS) makes a clear distinction between property read/writes and function calls. When the Java class has both a field and a method of the same name publicly available, obj.property will always read the field (or the getter as discussed above), while obj.property() will always call the respective method.

Additional Aspects to Consider

Features of GraalJS

GraalJS supports features of the newest ECMAScript specification and some extensions to it. See JavaScript Compatibility. Note that this example adds objects to the global scope that might interfere with existing source code unaware of those extensions.

Console Output

GraalJS provides a print builtin function compatible with Nashorn.

Note that GraalJS also provides a console.log function. This is an alias for print in pure JavaScript mode, but uses an implementation provided by Node.js when running in Node mode. The behavior around Java objects differs for console.log in Node mode as Node.js does not implement special treatment for such objects.

Node.js Runtime

GraalVM can run unmodified Node.js applications. GraalVM’s Node.js runtime is based on a recent version of Node.js, and runs the GraalVM JavaScript engine (GraalJS) instead of Google V8. Some internal features (for example, VM-internal statistics, configuration, profiling, debugging, and so on) are unsupported, or supported with potentially different behavior.

Applications can freely import and use NPM packages, including native ones.

Getting Started with Node.js

As of GraalVM for JDK 21, the GraalVM Node.js runtime is available as a separate distribution. Two standalone runtime options are available for both Oracle GraalVM and GraalVM Community Edition: a Native Image compiled launcher or a JVM-based runtime. To distinguish between them, the GraalVM Community Edition version has the suffix -community in the name: graaljs-community-<version>-<os>-<arch>.tar.gz, graalnode24-community-<version>-<os>-<arch>.tar.gz. A GraalVM Node.js standalone archive includes the Node.js major version in its name, for example graalnode24; a standalone that comes with a JVM has a -jvm suffix in its name.

To enable the GraalVM Node.js runtime, install the Node.js distribution based on Oracle GraalVM or GraalVM Community Edition for your operating system.

  1. Navigate to GitHub releases and select a desired standalone for your operating system.

  2. Unzip the archive:
     tar -xzf <archive>.tar.gz
    

    Alternatively, open the file in the Finder.

  3. Check the version to see if the runtime is active:
     ./path/to/bin/node --version
    

Running Node.js Applications

The Node.js installation provides node and npm launchers:

node [options] [filename] [args]

The npm command is equivalent to the default Node.js command, and features additional GraalVM-specific functionalities (for example, interoperability with Java). A list of available options can be obtained with node --help.

Use the node launcher to execute a Node.js application. For example:

  1. Install the colors and ansispan packages using npm install as follows:
     npm install colors ansispan
    

    After the packages are installed, you can use them from your application.

  2. Add the following code snippet to a file named app.js and save it in the same directory where you installed the Node.js packages:
     const http = require("http");
     const span = require("ansispan");
     require("colors");
    
     http.createServer(function (request, response) {
         response.writeHead(200, {"Content-Type": "text/html"});
         response.end(span("Hello Node.js!".green));
     }).listen(8000, function() { console.log("Node.js server running at http://127.0.0.1:8000/".red); });
    
     setTimeout(function() { console.log("DONE!"); process.exit(); }, 2000);
    
  3. Execute it on the GraalVM Node.js runtime using the node command as follows:
     node app.js
    

The Node.js functionality is available when an application is started from the node binary launcher. Certain limits apply when launching a Node.js application or accessing NPM packages from a Java context, see Node.js vs. Java Script Context.

Installing Packages Using npm

To install a Node.js package, use the npm launcher. The npm command is equivalent to the default NPM command, and supports most of its options.

An NPM package can be installed with:

npm install [package]

As the npm command of GraalVM Node.js is largely compatible with NPM, packages are installed in the node_modules/ directory, as expected.

Installing npm Packages Globally

Node packages can be installed globally using npm and the -g option. By default, npm installs global packages (links to their executables) in the path where the node executable is installed, typically node/bin/. That directory is where global packages are installed. You might want to add that directory to your $PATH if you regularly use globally installed packages, especially their command line interfaces.

Another option is to specify the global installation directory of npm by setting the $PREFIX environment variable, or by specifying the --prefix option when running npm install. For example, the following command will install global packages in the /foo/bar/ directory:

npm install --prefix /foo/bar -g <package>

More details about prefix can be found in the official NPM documentation.

Interoperability with Java

The Node.js runtime cannot be embedded into a JVM but has to be started as a separate process.

  1. Save the following code in a file named HelloPolyglot.java and compile:
     import org.graalvm.polyglot.*;
     import org.graalvm.polyglot.proxy.*;
    
     public class HelloPolyglot {
    
         static String JS_CODE = "(function myFun(param){console.log('hello '+param);})";
    
         public static void main(String[] args) {
             System.out.println("Hello Java!");
             try (Context context = Context.create()) {
                 Value value = context.eval("js", JS_CODE);
                 value.execute(args[0]);
             }
         }
     }
    
  2. Then save this code a file named app.js:
     var HelloPolyglot = Java.type("HelloPolyglot");
    
     HelloPolyglot.main(["from node.js"]);
    
     console.log("done");
    
  3. Run it with node:
     node --vm.cp=. app.js
    

    You should see the following output:

     Hello Java!
     hello from node.js
     done
    

Both Node.js and JVM then run in the same process and the interoperability works using the same Value classes as above.

For the differences between running the node launcher and accessing Node.js NPM modules or ECMAScript modules from a Java Context, see NodeJSVSJavaScriptContext.

Multithreading with Node.js

The basic multithreading model of GraalJS applies to Node.js applications as well. In Node.js, a Worker thread can be created to execute JavaScript code in parallel, but JavaScript objects cannot be shared between Workers. On the contrary, a Java object created with GraalVM Java interoperability (for example, using Java.type()) can be shared between Node.js Workers. This allows multithreaded Node.js applications to share Java objects.

The GraalVM Node.js unit tests contain several examples of multithreaded Node.js applications. The most notable examples show how:

  1. Node.js worker threads can execute Java code.
  2. Java objects can be shared between Node.js worker threads.
  3. JavaScript Promise objects can be used to await on messages from workers, using Java objects to bind promises to worker messages.

Node.js FAQ

Is GraalVM’s Node.js runtime compatible with the original Node implementation?

GraalVM’s Node.js runtime is largely compatible with the original Node.js (based on the V8 engine). This leads to a high number of npm-based modules being compatible. In fact, out of the 100k npm modules we test, more than 94% of them pass all tests. Still, several sources of differences have to be considered:

  • Setup: GraalVM’s Node.js mostly mimicks the original setup of Node, including the node executable, npm, and similar. However, not all command-line options are supported (or behave exactly identically). Modules might require that native modules are (re)compiled against the v8.h file.

    As of GraalVM for JDK 21, the GraalVM Node.js runtime is available as a separate distribution. See Getting Started with Node.js.

  • Internals: GraalVM’s Node.js is implemented on top of a JVM, and thus has a different internal architecture than Node.js based on V8. This implies that some internal mechanisms behave differently and cannot exactly replicate V8 behavior. This will hardly ever affect user code, but might affect modules implemented natively, depending on V8 internals.

  • Performance: Due to GraalVM’s Node.js being implemented on top of a JVM, performance characteristics vary from the original native implementation. While GraalVM’s peak performance can match V8 on many benchmarks, it will typically take longer to reach the peak (known as warmup). Be sure to give the Graal compiler some extra time when measuring (peak) performance.

  • Compatibility: GraalVM’s Node.js runtime uses the following approaches to check and retain compatibility with Node.js code:

    • node-compat-table: GraalVM’s Node.js is compared against other engines using the node-compat-table module, highlighting incompatibilities that might break Node.js code.
    • automated mass-testing of modules using mocha: in order to test a large set of modules, GraalVM’s Node.js runtime is tested against 95k modules that use the mocha test framework. Using mocha allows automating the process of executing the test and comprehending the test result.
    • manual testing of popular modules: a select list of npm modules is tested in a manual test setup. These highly-relevant modules are tested in a more sophisticated manner.

Can NPM packages be installed globally?

Node packages can be installed globally using npm and the -g option, both with the GraalVM’s Node.js implementation.

While the original Node.js implementation has one main directory (node/bin/) to put binaries and globally installed packages and their command-line tools, GraalVM’s Node.js puts binaries in the /path/to/graaljs/bin/ directory. When installing NPM packages globally on the GraalVM Node.js runtime, links to the executables, for example, for command line interface tools are put to the JavaScript-specific directory. In order for globally installed packages to function properly, you might need to add /path/to/graaljs/bin to your $PATH.

Another option is to specify the global installation directory of npm by setting the $PREFIX environment variable, or by specifying the --prefix option when running npm install.

For more details, see Installing npm Packages Globally.

Differences Between Node.js and Java Embeddings

GraalVM provides a fully-compliant ECMAScript 2024 JavaScript runtime. As such, it can run JavaScript code in a variety of embedding scenarios, including Oracle Database, any Java-based application, and Node.js.

Depending on the embedding scenario, applications have access to different built-in capabilities. For example, Node.js applications executed using GraalVM’s bin/node executable have access to all of Node.js’ APIs, including built-in Node.js modules such as fs, http, and so on. Conversely, JavaScript code embedded in a Java application has access to limited capabilities, as specified through the Context API, and do not have access to Node.js built-in modules.

This guide describes the main differences between a Node.js application and JavaScript embedded in a Java application.

Context Creation

JavaScript code in GraalVM can be executed using an execution context.

In a Java application, a new context can be created using the Context API. New contexts can be configured in multiple ways, and configuration options include exposing access to Java classes, allowing access to IO, and so on. A list of context creation options can be found in the API documentation. In this scenario, Java classes can be exposed to JavaScript by using GraalVM’s Polyglot Bindings.

In a Node.js application, the GraalVM Context executing the application is pre-initialized by the Node.js runtime, and cannot be configured by the user application. In this scenario, Java classes can be exposed to the Node.js application by using the --vm.cp= command line option of the bin/node command, as described below.

Java Interoperability

JavaScript applications can interact with Java classes using the Java built-in object. This object is available by default in the js and node launchers, but accessing Java classes is only possible in the JVM standalone (that have -jvm in the name).

When embedding JavaScript using the Polyglot API, you have to explicitly enable host access in the Context.Builder (allowHostAccess, allowHostClassLookup). More details on the JavaScript-Java interoperability are available in the Java Interoperability guide.

Multithreading

A polyglot Context running JavaScript enforces a “share-nothing” model of parallelism: no JavaScript values can be accessed by two concurrent Java threads at the same time. In order to leverage parallel execution, multiple contexts have to be created and executed from multiple threads:

  1. In Node.js mode, multiple contexts can be created using Node.js’ Worker threads API. The Worker threads API ensures that no sharing can happen between two parallel contexts.
  2. In Java, multiple contexts can be executed from multiple threads. As long as a context is not accessed by two threads at the same time, parallel execution happens safely.

More details on parallel execution in GraalJS are available in this blog post.

Java Libraries

Java libraries can be accessed from GraalJS through the Java built-in object. In order for a Java library to be accessible from a Context, its JAR files need to be added to the class path. This can be done in the following way:

  1. In Node.js mode, the class path can be modified using the --vm.cp option.
  2. In Java, the default Java’s -cp option can be used.

Read more in Command-line Options.

JavaScript Packages and Modules

Many popular JavaScript modules such as those available on the npm package registry can be used from Node.js as well as from Java:

  1. In Node.js mode, JavaScript modules are handled by the Node.js runtime. Therefore, GraalJS supports all modules supported by Node.js (including ES modules, CommonJS modules, and native modules).
  2. In Java mode, GraalJS can execute any JavaScript module or package that does not depend on native Node.js built-in modules (such as fs, http, and so on). Modules can be loaded using a package bundler, or using the available built-in mechanisms for ES modules. CommonJS modules are supported in Java mode under an experimental option.

More details on JavaScript modules are available in Modules.

Operator Overloading

GraalJS provides an early implementation of the ECMAScript operator overloading proposal. This lets you overload the behavior of JavaScript’s operators on your JavaScript classes.

If you want to experiment with this feature, enable it. Since both the proposal and the GraalJS implementation of it are in early stages, you need to pass the --experimental-options option:

js --experimental-options --js.operator-overloading

After setting the option, you will see a new builtin in the global namespace, the Operators function. You can call this function, passing it a JavaScript object as an argument. The object should have a property for every operator you wish to overload, with the key being the name of the operator and the value being a function that implements it. The return value of the Operators function is a constructor that you can then subclass when defining your type. By subclassing this constructor, you get a class whose objects inherit the overloaded operator behavior that you defined in your argument to the Operators function.

Basic Example

Look at an example from the original proposal featuring vectors:

const VectorOps = Operators({
  "+"(a, b) {
    return new Vector(a.contents.map((elt, i) => elt + b.contents[i]));
  },
  "=="(a, b) {
    return a.contents.length === b.contents.length &&
           a.contents.every((elt, i) => elt == b.contents[i]);
  },
});

class Vector extends VectorOps {
  contents;
  constructor(contents) {
    super();
    this.contents = contents;
  }
}

Here two operators, + and ==, are overloaded. Calling the Operators function with the table of overloaded operators yields the VectorOps class. Then the Vector class is defined as a subclass of VectorOps.

If you create instances of Vector, you can observe that they follow the overloaded operator definitions:

> new Vector([1, 2, 3]) + new Vector([4, 5, 6]) == new Vector([5, 7, 9])
true

Example with Mixed Types

It is also possible to overload operators between values of different types, allowing, for example, multiplication of vectors by numbers:

const VectorOps = Operators({
    "+"(a, b) {
        return new Vector(a.contents.map((elt, i) => elt + b.contents[i]));
    },
    "=="(a, b) {
        return a.contents.length === b.contents.length &&
            a.contents.every((elt, i) => elt == b.contents[i]);
    },
}, {
    left: Number,
    "*"(a, b) {
        return new Vector(b.contents.map(elt => elt * a));
    }
});

class Vector extends VectorOps {
    contents;
    constructor(contents) {
        super();
        this.contents = contents;
    }
}

To define mixed-type operators, pass additional objects to the Operators function. These extra tables should each have either a left property or a right property, depending on whether you overload the behavior of operators with some other type on the left or on the right side of the operator. In the example, the * operator is overloaded for cases when there is a Number on the left and the type, Vector, on the right. Each extra table can have either a left property or a right property and then any number of operator overloads that will apply to that particular case.

Running this example you see:

> 2 * new Vector([1, 2, 3]) == new Vector([2, 4, 6])
true

Usage Documentation

The function Operators(table, extraTables...) returns a class with overloaded operators. Users should define their own class which extends that class.

The table argument must be an object with one property for every overloaded operator. The property key must be the name of the operator. These are the names of operators which can be overloaded:

  • binary operators: "+", "-", "*", "/", "%", "**", "&", "^", "|", "<<", ">>", ">>>", "==", "<"
  • unary operators: "pos", "neg", "++", "--", "~"

The "pos" and "neg" operator names correspond to unary + and unary -, respectively. Overloading "++" works both for pre-increments ++x and post-increments x++, the same goes for "--". The overload for "==" is used both for equality x == y and inequality x != y tests. Similarly, the overload for "<" is used for all comparison operators (x < y, x <= y, x > y, x >= y) by swapping the arguments and/or negating the result.

The value assigned to an operator name must be a function of two arguments in the case of binary operators or a function of one argument in the case of unary operators.

The table argument can also have an open property. If so, the value of that property must be an array of operator names. These are the operators that future classes will be able to overload on this type (for example, a Vector type might declare "*" to be open so that later a Matrix type might overload the operations Vector * Matrix and Matrix * Vector). If the open property is missing, all operators are considered to be open for future overloading with other types.

Following the first argument table are optional arguments extraTables. Each of these must also be an object. Each extra table must have either a left property or a right property, not both. The value of that property must be one of the following JavaScript constructors:

  • Number
  • BigInt
  • String
  • any class with overloaded operators (i.e. extended from a constructor returned by Operators)

The other properties of the extra table should be operator overloads as in the first table argument (operator name as key, function implementing the operator as value).

These extra tables define the behavior of operators when one of the operand types is of a type other than the one being defined. If the extra table has a left property, its operator definitions will apply to cases when the left operand is of the type named by the left property and the right operand is of the type whose operators are being defined. Similarly for the right property, if the extra table has a right property, the table’s operator definitions will apply when the right operand has the named type and the left operand has the type whose operators are being defined.

Note that you are free to overload any of the binary operators between your custom type and the JavaScript numeric types Number and BigInt. However, the only operators you are allowed to overload between your custom type and the String type are "==" and "<".

The Operators function returns a constructor that you will usually want to extend in your own class. Instances of that class will respect your overloaded operator definitions. Whenever you use an operator on an object with overloaded operators, the following happens: 1) Every operand that does not have overloaded operators is coerced to a primitive. 2) If there is an applicable overload for this pairing of operands, it is called. Otherwise, a TypeError is thrown.

Notably, your objects with overloaded operators will not be coerced to primitives when applying operators and you can get TypeErrors when applying undefined operators to them. There are two exceptions to this: 1) If you are using the + operator and one of the arguments is a String (or an object without overloaded operators that coerces to a String via ToPrimitive), then the result will be a concatenation of the ToString values of the two operands. 2) If you are using the == operator and there is no applicable overload found, the two operands are assumed to be different (x == y will return false and x != y will return true).

Differences from the Proposal

There a few differences between the proposal (as defined by its specification and prototype implementation) and GraalJS implementation:

  • You do not have to use the with operators from construction to enable the use of overloaded operators. When you overload operators for a class, those operators can then be used anywhere without using with operators from. Furthermore, the parser will not accept the with operators from clause as valid JavaScript.
  • You cannot use decorators to define overloaded operators. At the time of implementing this proposal, GraalJS does not support decorators (these are still an in-progress proposal).
  • You cannot overload the "[]" and "[]=" operators for reading and writing integer-indexed elements. These two operators require more complex treatment and are not currently supported.

Options

Running JavaScript on GraalVM can be configured with several options.

These options are to control the behavior of the js launcher:

  • -e, --eval <code>: evaluate the JavaScript source code, then exit the engine.
     js -e 'print(1+2);'
    
  • -f, --file <arg>: load and execute the provided script file. Note that the -f option is optional and can be omitted in most cases, as any additional argument to js will be interpreted as a file anyway.
     js -f myfile.js
    
  • --module <arg>: load and execute the provided module file. Note that .mjs files are treated as modules by default.
     js --module myfile.mjs
    
  • --version: print the version information of GraalJS, then exit.
  • --strict: execute the engine in JavaScript’s strict mode.

GraalJS Engine Options

There are several options to configure the behavior of GraalJS. Depending on how the engine is started, the options can be passed either to the launcher or programmatically.

For a full list of options of the JavaScript engine, pass the --help:js flag to the js launcher (available from GraalVM 22.1, for older releases use --help:languages). To include internal options, use --help:js:internal. Note that those lists both include stable, supported, and experimental options.

Pass Options on the Command Line

To pass the options to the js launcher, use the --js.<option-name>=<value> syntax. For example:

js --js.ecmascript-version=2015

Pass Options Programmatically Using the Context API

When embedded in Java using GraalVM’s Polyglot API, the options can be passed programmatically to the Context object:

Context context = Context.newBuilder("js")
                         .option("js.ecmascript-version", "2015")
                         .build();
context.eval("js", "42");

See the Polyglot Programming reference for information on how to set options programmatically.

Stable and Experimental Options

The available options are distinguished as stable and experimental options. If an experimental option is used, an extra option has to be provided upfront.

Using the js launcher, --experimental-options has to be passed before all experimental options. When using a Context, the option allowExperimentalOptions(true) has to be called on a Context.Builder. See ScriptEngine Implementation on how to use experimental options with a ScriptEngine.

Frequently Used Stable Options

The following stable options are frequently relevant:

  • --js.ecmascript-version: emulate a specific ECMAScript version. Integer value (5, 6, etc., 2015-2027), "latest" (latest supported version of the spec, including finished proposals), or "staging" (latest version including supported unfinished proposals). Default is "latest".
  • --js.foreign-object-prototype: provide JavaScript’s default prototype to foreign objects that mimic JavaScript’s own types (foreign Arrays, Objects, and Functions). Boolean value, default is true.
  • --js.intl-402: enable ECMAScript Internationalization API. Boolean value, default is true.
  • --js.regexp-static-result: provide static RegExp properties containing the results of the last successful match, for example, RegExp.$1 (legacy). Boolean value, default is true.
  • --js.strict: enable strict mode for all scripts. Boolean value, default is false.
  • --js.console: enable the console global property. Boolean value, default is true.
  • --js.allow-eval: allow the code generation from strings, for example, using eval() or the Function constructor. Boolean value, default is true.
  • --js.timer-resolution: sets the resolution of timing functions, such as Date.now() and performance.now(), in nanoseconds. Default: 1000000 (i.e. 1 ms).
  • --js.unhandled-rejections: configure unhandled promise rejection tracking. Accepted values are none (default, no tracking), warn (print a warning to stderr), throw (throw an exception), and handler (invoke a custom handler).
  • --js.esm-eval-returns-exports: context.eval of an ES module Source returns its exported symbols.

For a complete list, use js --help:js:internal

ECMAScript Version

The --js.ecmascript-version option provides compatibility with a specific version of the ECMAScript specification. It expects an integer value, where both the edition numbers (5, 6, …) and the publication years (starting from 2015) are supported. As of GraalVM 21.2, latest, staging are also supported. The default is the latest supported finalized specification, currently ECMAScript 2026. GraalJS implements some features of the future draft specification and of open proposals, if you explicitly select that version and/or enable specific experimental options. For production settings, it is recommended to set the ecmascript-version to a released, finalized version of the specification (for example, 2022).

Available versions are:

  • 5 for ECMAScript 5.1
  • 2015 (or 6) for ECMAScript 2015
  • 2016 (or 7) for ECMAScript 2016
  • 2017 (or 8) for ECMAScript 2017
  • 2018 (or 9) for ECMAScript 2018
  • 2019 (or 10) for ECMAScript 2019
  • 2020 (or 11) for ECMAScript 2020
  • 2021 (or 12) for ECMAScript 2021 (default in 21.3)
  • 2022 (or 13) for ECMAScript 2022 (default in 22.0+)
  • 2023 (or 14) for ECMAScript 2023 (default in 23.1)
  • 2024 (or 15) for ECMAScript 2024 (default in 24.1)
  • 2025 (or 16) for ECMAScript 2025 (default in 25.0)
  • 2026 (or 17) for ECMAScript 2026 (default in 25.1)
  • 2027 (or 18) for the ECMAScript 2027 draft
  • latest for the latest supported language version (the default version)
  • staging for the latest supported language features including experimental unstable, unfinished proposals (do not use in production!)
intl-402

The --js.intl-402 option enables ECMAScript’s Internationalization API. It expects a Boolean value and the default is true.

Strict Mode

The --js.strict option enables JavaScript’s strict mode for all scripts. It expects a Boolean value and the default is false.

Frequently Used Experimental Options

Note that these options are experimental and are not guaranteed to be maintained or available in the future. To use them, the --experimental-options option is required upfront.

These are the frequently used experimental options:

  • --js.nashorn-compat: provide compatibility mode with the Nashorn engine. Sets ECMAScript version to 5 by default. Might conflict with newer ECMAScript versions. Boolean value, default is false.
  • --js.timezone: set the local time zone. String value, default is the system default.
  • --js.v8-compat: provide better compatibility with Google’s V8 engine. Boolean value, default is false.
  • --js.temporal: explicitly enable or disable the Temporal API. It is enabled by default in ECMAScript 2027 and later.
  • --js.webassembly: enable WebAssembly API.

Migration Guide from Rhino to GraalJS

This document serves as a migration guide for code previously targeted to the Rhino engine. See the Java Interoperability guide for an overview of supported features.

Both Rhino and GraalJS support a similar set of syntax and semantics for Java interoperability. The most important differences relevant for migrations are listed here.

Java.type(typename) instead of java.a.b.c.typename

GraalJS does not put available Java classes in the JavaScript scope. You have to explicitly load the classes using Java.type(typename).

GraalJS supports the Packages global object, but loading the classes explicitly is still encouraged. The following Java package globals are available in the Nashorn compatibility mode (js.nashorn-compat option): java, javafx, javax, com, org, edu.

Console Output of Java Classes and Java Objects

GraalJS provides the print builtin function. It tries to special-case its behavior on Java classes and Java objects to provide the most useful output.

Note that GraalJS also provides a console.log function. This is an alias for print in pure JavaScript mode, but uses an implementation provided by Node.js when in Node mode. The behavior around interop objects differs for console.log in Node mode as it does not implement special treatment for such objects.

JavaScript vs Java Strings

GraalJS uses Java strings internally to represent JavaScript strings. This makes it impossible to differentiate whether a specific string was created by JavaScript or by Java code. In GraalJS, the JavaScript properties take precedence over Java fields or methods. For instance, you can query the length property (of JavaScript) but you cannot call the length function (of Java) on JavaScript strings - length behaves like a data property, not like a function.

JavaImporter

The JavaImporter feature is available only in the Nashorn compatibility mode (js.nashorn-compat).

Run GraalJS on a Stock JDK

GraalJS is optimized for execution as part of GraalVM, primarily recommended for use in a Java application. This guarantees the best possible performance by using the Graal compiler as the optimizing compiler, and potentially Native Image to compile the engine ahead of time into a native binary.

It is, however, possible to execute GraalJS on a standard Java VM such as Oracle JDK or OpenJDK. When executed without the Graal Compiler, JavaScript performance will be significantly worse. While the JIT compiler available on a standard JVM can execute and JIT-compile the GraalJS codebase, it cannot optimize GraalJS to its full performance potential. This document describes how to run GraalJS on a standard Java VM, and shows how you can use the Graal compiler as a JIT compiler to guarantee the best possible performance.

GraalJS on Maven Central

GraalJS is open source and regularly pushed to Maven Central Repository by the community. You can find it under org.graalvm.polyglot:js.

We provide example projects running GraalJS embedded in Java on JDK 21 (or later) and using the Graal compiler:

  • Polyglot Embedding Demo. Maven and Gradle projects for a simple JavaScript “Hello World” application.
  • JS Maven Demo. This example contains a Maven project for a JavaScript benchmark (a prime number generator). It enables a user to compare the performance of GraalJS running with or without the Graal compiler as the optimizing compiler. Running with the Graal compiler significantly improves the execution performance of any relatively large JavaScript codebase. In essence, the example pom.xml file activates the JVM Compiler Interface (JVMCI) and configures the JIT compiler to be the Graal compiler by providing it on --module-path and --upgrade-module-path.

ScriptEngine JSR 223

GraalJS can be started via ScriptEngine when js-scriptengine.jar is included on the module path. The engine registers under several different names, including Graal.js, js, JavaScript, and javascript. Note that the Nashorn engine might be available under its names as well, if on the module path.

To start GraalJS from ScriptEngine, the following code can be used:

new ScriptEngineManager().getEngineByName("Graal.js");

To list all available engines:

List<ScriptEngineFactory> engines = new ScriptEngineManager().getEngineFactories();
for (ScriptEngineFactory f : engines) {
    System.out.println(f.getLanguageName() + " " + f.getEngineName() + " " + f.getNames());
}

Inspecting the Setup - Is the GraalVM Compiler Used as a JIT Compiler?

The --engine.TraceCompilation option enables a debug output whenever a JavaScript method is compiled by the Graal compiler. JavaScript source code with a long-enough run time will trigger the compilation and print a log output:

> function add(a,b) { return a+b; }; for (var i=0;i<1000*1000;i++) { add(i,i); }
[truffle] opt done         add <opt> <split-c0875dd>                                   |ASTSize       7/    7 |Time    99(  90+9   )ms |DirectCallNodes I    0/D    0 |GraalNodes    22/   71 |CodeSize          274 |CodeAddress 0x7f76e4c1fe10 |Source    <shell>:1:1

ScriptEngine Implementation

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.

Note: As of GraalVM for JDK 21, GraalVM no longer includes ScriptEngine by default. If you relied on that, you will have to migrate your setup to explicitly depend on the script engine module and add it to the module path.

To enable the js-scriptengine module, add it as the Maven dependency, as follows:

<dependency>
    <groupId>org.graalvm.js</groupId>
    <artifactId>js-scriptengine</artifactId>
    <version>${graaljs.version}</version>
</dependency>
<dependency>
    <groupId>org.graalvm.polyglot</groupId>
    <artifactId>js</artifactId>
    <version>${graaljs.version}</version>
    <type>pom</type>
</dependency>

If you are not using Maven, you will need to add the js-scriptengine.jar file to the module path manually, for example, --module-path=languages/js/graaljs-scriptengine.jar. In some cases you may also need to add --add-modules org.graalvm.js.scriptengine to the command line, to ensure that the ScriptEngine will be found. An explicit dependency on the org.graalvm.js.scriptengine module is only required if you want to use GraalJSScriptEngine directly (see below). Finally, it is also possible to use jlink to generate a custom Java runtime image that contains the GraalJS’s ScriptEngine.

An example pom.xml file can be found in the GraalJS repository on GitHub.

Recommendation for Use

To avoid unnecessary recompilation of JavaScript sources, it is recommended to use CompiledScript.eval instead of ScriptEngine.eval. This prevents JIT-compiled code from being garbage-collected as long as the corresponding CompiledScript object is alive.

Single-threaded example:

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("js");
CompiledScript script = ((Compilable) engine).compile("console.log('hello world');");
script.eval();

Multi-threaded example:

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("js");
CompiledScript script = ((Compilable) engine).compile("console.log('start');var start = Date.now(); while (Date.now()-start < 2000);console.log('end');");
new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            // Create ScriptEngine for this thread (with a shared polyglot Engine)
            ScriptEngine engine = manager.getEngineByName("js");
            script.eval(engine.getContext());
        } catch (ScriptException scriptException) {
            scriptException.printStackTrace();
        }
    }
}).start();
script.eval();

Setting Options via Bindings

The ScriptEngine interface does not provide a default way to set options. As a workaround, GraalJSScriptEngine supports setting some Context options through Bindings. These options are:

  • polyglot.js.allowHostAccess <boolean>
  • polyglot.js.allowNativeAccess <boolean>
  • polyglot.js.allowCreateThread <boolean>
  • polyglot.js.allowIO <boolean>
  • polyglot.js.allowHostClassLookup <boolean or Predicate<String>>
  • polyglot.js.allowHostClassLoading <boolean>
  • polyglot.js.allowAllAccess <boolean>
  • polyglot.js.nashorn-compat <boolean>
  • polyglot.js.ecmascript-version <String>

These options control the sandboxing rules applied to evaluated JavaScript code and are set to false by default, unless the application was started in the Nashorn compatibility mode (--js.nashorn-compat=true).

Note that using ScriptEngine implies allowing experimental options. This is an exhaustive list of allowed options to be passed via Bindings; in case you need to pass additional options to GraalJS, you need to manually create a Context as shown below.

To set an option via Bindings, use Bindings.put(<option name>, true) before the engine’s script context is initialized. Note that even a call to Bindings#get(String) may lead to a context initialization. The following code shows how to enable polyglot.js.allowHostAccess via Bindings:

ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
bindings.put("polyglot.js.allowHostAccess", true);
bindings.put("polyglot.js.allowHostClassLookup", (Predicate<String>) s -> true);
bindings.put("javaObj", new Object());
engine.eval("(javaObj instanceof Java.type('java.lang.Object'));"); // it will not work without allowHostAccess and allowHostClassLookup

This example will not work if the user calls, for example, engine.eval("var x = 1;"), before calling bindings.put("polyglot.js.allowHostAccess", true);, since any call to eval forces a context initialization.

Setting Options via System Properties

Options to the JavaScript engine can be set via system properties before starting the JVM by prepending polyglot.:

java -Dpolyglot.js.ecmascript-version=2022 MyApplication

Or, options to the JavaScript engine can be set programmatically from within a Java application before creating ScriptEngine. This, however, only works for the options passed to the JavaScript engine (such as js.ecmascript-version), and not for the options mentioned in the example that can be set via Bindings. Another caveat is that those system properties are shared by all concurrently executed ScriptEngines.

Manually Creating Context for More Flexibility

Context options can also be passed to GraalJSScriptEngine directly, via an instance of Context.Builder:

ScriptEngine engine = GraalJSScriptEngine.create(null,
        Context.newBuilder("js")
        .allowHostAccess(HostAccess.ALL)
        .allowHostClassLookup(s -> true)
        .option("js.ecmascript-version", "2022"));
engine.put("javaObj", new Object());
engine.eval("(javaObj instanceof Java.type('java.lang.Object'));");

This enables setting all options available in GraalJS. It does come at the cost of a hard dependency on GraalJS, for example, the GraalJSScriptEngine and Context classes.

Supported File Extensions

The GraalJS implementation of javax.script.ScriptEngine supports the js file extension for JavaScript source files, as well as the mjs extension for ES modules.

Frequently Asked Questions

Below are the most frequently asked questions and answers about JavaScript running on GraalVM.

Compatibility

Is GraalJS compatible with the JavaScript language?

GraalJS is compatible with the ECMAScript 2026 specification and is further developed alongside the 2027 draft specification. The compatibility of GraalJS is verified by external sources, such as the Kangax ECMAScript compatibility table.

GraalJS is tested against a set of test engines, such as the official test suite of ECMAScript, test262, as well as tests published by V8 and Nashorn, Node.js unit tests, and GraalJS’s own unit tests.

For reference documentation describing the JavaScript APIs that GraalVM supports, see GraalJS Compatibility.

My application used to run on Nashorn, why does it not work on GraalJS?

Reason:

  • GraalJS tries to be compatible with the ECMAScript specification, as well as competing engines (including Nashorn). In some cases, this is a contradicting requirement; in these cases, ECMAScript is given precedence. Also, there are cases where GraalJS does not exactly replicate Nashorn features intentionally, for example, for security reasons.

Solution:

  • Enable GraalJS’s Nashorn compatibility mode to add features not enabled by default—this should resolve most cases. However, note that this can have negative effects on application security! See the Nashorn Migration Guide for details.

Specific applications:

  • For JSR 223 ScriptEngine, you might want to set the system property polyglot.js.nashorn-compat to true in order to use the Nashorn compatibility mode.
  • For ant, use the ANT_OPTS environment variable (ANT_OPTS="-Dpolyglot.js.nashorn-compat=true") when using GraalJS via ScriptEngine.

Why are built-in functions such as array.map() or fn.apply() not available on non-JavaScript objects such as ProxyArrays from Java?

Reason:

  • Java objects provided to JavaScript are treated as closely as possible to their JavaScript counterparts. For example, Java arrays provided to JavaScript are treated like JavaScript Array exotic objects (JavaScript arrays) whenever possible; the same is true for functions. One obvious difference is that such object’s prototype is null. This means that while you can, for example, read the length or read and write the values of a Java array in JavaScript code, you cannot call sort() on it, as the Array.prototype is not provided by default.

Solution:

  • While the objects do not have the methods of the prototype assigned, you can explicitly call them, for example, Array.prototype.call.sort(myArray).
  • We offer the option js.foreign-object-prototype. When enabled, objects on the JavaScript side get the most applicable prototype set (such as Array.prototype, Function.prototype, Object.prototype) and can thus behave more similarly to native JavaScript objects of the respective type. Normal JavaScript precedence rules apply here, for example, an object’s own properties (of the Java object in that case) take precedence over and hide properties from the prototype.

Note that while the JavaScript built-in functions, for example, from Array.prototype can be called on the respective Java types, those functions expect JavaScript semantics. This means that operations might fail (typically with a TypeError: Message not supported) when an operation is not supported in Java. Consider Array.prototype.push as an example: arrays can grow in size in JavaScript, whereas they are fixed-size in Java, thus pushing a value is semantically not possible and will fail. In such cases, you can wrap the Java object and handle that case explicitly. Use the interfaces ProxyObject and ProxyArray for that purpose.

Performance

Why is my application slower on GraalJS than on another engine?

Reason:

  • Ensure your benchmark considers warmup. During the first few iterations, GraalJS may be slower than other engines, but after sufficient warmup, this difference should level out.
  • GraalJS is shipped in two different standalones: Native (default) and JVM (with a -jvm infix). The default Native mode offers faster startup and lower latency, but it might exhibit slower peak performance (lower throughput) once the application is warmed up. In JVM mode, your application might need hundreds of milliseconds more to start, but typically exhibits better peak performance.
  • Repeated execution of code via newly created org.graalvm.polyglot.Context is slow, despite the same code being executed every time.

Solution:

  • Use proper warmup in your benchmark, and disregard the first few iterations where the application still warms up.
  • When embedding GraalJS in a Java application, ensure you’re running on a GraalVM JDK for best performance.
  • Use a JVM standalone for slower startup, but higher peak performance.
  • Double check you have no options set that might lower your performance, for example, -ea/-esa.
  • When running code via org.graalvm.polyglot.Context, make sure that one org.graalvm.polyglot.Engine object is shared and passed to each newly created Context. Use org.graalvm.polyglot.Source objects and cache them when possible. Then, GraalVM shares existing compiled code across the Contexts, leading to improved performance. See Code Caching Across Multiple Contexts for more details and an example.
  • Try to reduce the problem to its root cause and file an issue so the GraalVM team can have a look.

How can I achieve the best peak performance?

Here are a few tips you can follow to analyze and improve peak performance:

  • When measuring, ensure you have given the Graal compiler enough time to compile all hot methods before starting to measure peak performance. A useful command line option for that is --engine.TraceCompilation=true—this outputs a message whenever a (JavaScript) method is compiled. Do not begin your measurement until this message becomes less frequent.
  • Compare the performance between Native Image and JVM mode if possible. Depending on the characteristics of your application, one or the other might show better peak performance.
  • The Polyglot API comes with several tools and options to inspect the performance of your application:
    • --cpusampler and --cputracer will print a list of the hottest methods when the application is terminated. Use that list to figure out where most time is spent in your application.
    • --experimental-options --memtracer can help you understand the memory allocations of your application. Refer to Profiling Command Line Tool for more detail.

What is the difference between running GraalJS in Native Image compared to the JVM?

In essence, the GraalJS engine is a plain Java application. Running it on any JVM (JDK 21 or later) is possible, but, for a better result, it should be a GraalVM JDK, or a compatible Oracle JDK using the Graal compiler. This mode gives the JavaScript engine full access to Java at runtime, but also requires the JVM to first (just-in-time) compile the JavaScript engine when executed, just like any other Java application.

Running in Native Image means that the JavaScript engine, including all its dependencies from, for example, the JDK, is precompiled into a native executable. This will tremendously reduce the startup of any JavaScript application, as GraalVM can immediately start to compile JavaScript code, without itself requiring to be compiled first. This mode, however, will only give GraalVM access to Java classes known at the time of image creation. Most significantly, this means that the JavaScript-to-Java interoperability features are not available in this mode, as they would require dynamic class loading and execution of arbitrary Java code at runtime.

Errors

TypeError: Access to host class com.myexample.MyClass is not allowed or does not exist

Reason:

  • You are trying to access a Java class that is unknown to the js process, or is not among the allowed classes that your code can access.

Solution:

  • Ensure there is no typo in the class name.
  • Ensure the class is on the class path. Use the --vm.cp=<classpath> option.
  • Ensure access to the class is permitted, by having a @HostAccess.Export annotation on your class and/or the Context.Builder.allowHostAccess() set to a permissive setting. See org.graalvm.polyglot.Context.

TypeError: UnsupportedTypeException

TypeError: execute on JavaObject[Main$$Lambda$63/1898325501@1be2019a (Main$$Lambda$63/1898325501)] failed due to: UnsupportedTypeException

Reason:

  • GraalJS in some cases does not allow concrete callback types when calling from JavaScript to Java. A Java function expecting, for example, a Value object, might fail with the quoted error message due to that.

Solution:

  • Change the signature in the Java callback method.

Status:

Example:

import java.util.function.Function;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.HostAccess;

public class Minified {
  public static void main(String ... args) {
    //change signature to Function<Object, String> to make it work
    Function<Value, String> javaCallback = (test) -> {
      return "passed";
    };
    try(Context ctx = Context.newBuilder()
    .allowHostAccess(HostAccess.ALL)
    .build()) {
      Value jsFn = ctx.eval("js", "f => function() { return f(arguments); }");
      Value javaFn = jsFn.execute(javaCallback);
      System.out.println("finished: "+javaFn.execute());
    }
  }
}

TypeError: Message not supported

TypeError: execute on JavaObject[Main$$Lambda$62/953082513@4c60d6e9 (Main$$Lambda$62/953082513)] failed due to: Message not supported.

Reason:

  • You are trying to execute an operation (a message) on a polyglot object that this object does not handle. For example, you are calling Value.execute() on a non-executable object.
  • A security setting (for example, org.graalvm.polyglot.HostAccess) might prevent the operation.

Solution:

  • Ensure the object (type) in question does handle the respective message.
  • Specifically, ensure the JavaScript operation you try to execute on a Java type is possible semantically in Java. For example, while you can push a value to an array in JavaScript and thus automatically grow the array, arrays in Java are of fixed length and trying to push to a Java array will result in a Message not supported failure. You might want to wrap Java objects for such cases, for example, as a ProxyArray.
  • Ensure access to the class is permitted, by having a @HostAccess.Export annotation on your class and/or the Context.Builder.allowHostAccess() set to a permissive setting. See org.graalvm.polyglot.Context.
  • Are you trying to call a Java Lambda expression or Functional Interface? Annotating the proper method with a @HostAccess.Export annotation can be a pitfall. While you can annotate the method to which the functional interface refers, the interface itself (or the Lambda class created in the background) fails to be properly annotated and recognized as exported. See below for examples highlighting the problem and a working solution.

An example that triggers a Message not supported error with certain HostAccess settings, e.g., HostAccess.EXPLICIT:

{
  ...
  //a JS function expecting a function as argument
  Value jsFn = ...;
  //called with a functional interface as argument
  jsFn.execute((Function<Integer, Integer>)this::javaFn);
  ...
}

@Export
public Object javaFn(Object x) { ... }

@Export
public Callable<Integer> lambda42 = () -> 42;

In the example above, the method javaFn is seemingly annotated with @Export, but the functional interface passed to jsFn is not, as the functional interface behaves like a wrapper around javaFn, thus hiding the annotation. Neither is lambda42 properly annotated—that pattern annotates the field lambda42, nor its executable function in the generated lambda class.

In order to add the @Export annotation to a functional interface, use this pattern instead:

import java.util.function.Function;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.HostAccess;

public class FAQ {
  public static void main(String[] args) {
    try(Context ctx = Context.newBuilder()
    .allowHostAccess(HostAccess.EXPLICIT)
    .build()) {
      Value jsFn = ctx.eval("js", "f => function() { return f(arguments); }");
      Value javaFn = jsFn.execute(new MyExportedFunction());
      System.out.println("finished: " + javaFn.execute());
    }
  }

  @FunctionalInterface
  public static class MyExportedFunction implements Function<Object, String> {
    @Override
    @HostAccess.Export
    public String apply(Object s) {
      return "passed";
    }
  };
}

Another option is to allow access to java.function.Function’s apply method. However, note that this allows access to ALL instances of this interface—in most production environments, this will be too permissive and open potential security holes.

HostAccess ha = HostAccess.newBuilder(HostAccess.EXPLICIT)
  //warning: too permissive for use in production
  .allowAccess(Function.class.getMethod("apply", Object.class))
  .build();

Warning: Implementation does not support runtime compilation.

If you get the following warning, you are not running on GraalVM JDK, or a compatible Oracle JDK or OpenJDK using the Graal Compiler:

[engine] WARNING: The polyglot context is using an implementation that does not support runtime compilation.
The guest application code will therefore be executed in interpreted mode only.
Execution only in interpreted mode will strongly impair guest application performance.
To disable this warning, use the '--engine.WarnInterpreterOnly=false' option or the '-Dpolyglot.engine.WarnInterpreterOnly=false' system property.

To resolve this, use GraalVM or see how to Run GraalJS on a Stock JDK guide for instructions on how to set up the Graal compiler on a compatible Graal-enabled stock JDK.

Nevertheless, if this is intentional, you can disable the warning and continue to run with degraded performance by setting the above mentioned option, either via the command line or using the Context.Builder, for example:

try (Context ctx = Context.newBuilder("js")
    .option("engine.WarnInterpreterOnly", "false")
    .build()) {
  ctx.eval("js", "console.log('Greetings!');");
}

Note that when using an explicit polyglot engine, the option has to be set on the Engine, for example:

try (Engine engine = Engine.newBuilder()
    .option("engine.WarnInterpreterOnly", "false")
    .build()) {
  try (Context ctx = Context.newBuilder("js").engine(engine).build()) {
    ctx.eval("js", "console.log('Greetings!');");
  }
}