Frequently Asked Questions

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

Compatibility

Is GraalVM compatible with the JavaScript language?

GraalVM is compatible with the ECMAScript 2020 specification and is further developed alongside the 2021 draft specification. The compatibility of GraalVM’s JavaScript runtime is verified by external sources, like the Kangax ECMAScript compatibility table.

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

For a reference of the JavaScript APIs that GraalVM supports, see GRAAL.JS-API.

Is GraalVM compatible with the original node implementation?

Node.js based on GraalVM 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 with GraalVM. In fact, out of the 95k modules we test, more than 90% of them pass all tests. Still, several sources of differences have to be considered:

Since GraalVM 21.1, Node.js and all related executables (e.g., node, npm, etc.) are not included by default in the GraalVM binary. Node.js support is now packaged in a separate component that can be installed with the GraalVM Updater using $GRAALVM/bin/gu install nodejs.

In addition, GraalVM uses the following approaches to check and retain compatibility with Node.js code:

My application used to run on Nashorn, it does not work on GraalVM JavaScript?

Reason:

Solution:

Specific applications:

Builtin functions like array.map() or fn.apply() are not available on non-JavaScript objects like ProxyArrays from Java

Reason:

Solution:

Note that while the JavaScript builtin functions. e.g., from Array.prototype can be called on the respective Java types, those functions expect JavaScript semantics. This for instance 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: while arrays can grow in size in JavaScript, 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.

How can one verify GraalVM works on their application?

If your module ships with tests, execute them with GraalVM. Of course, this will only test your application, but not its dependencies. You can use the Compatibility tool to find whether the module you are interested in is tested on GraalVM, and whether the tests pass successfully. Additionally, you can upload your package-lock.json or package.json file into that tool and it will analyze all your dependencies at once.

Performance

My application is slower on GraalVM JavaScript than on another engine

Reason:

Solution:

How to achieve the best peak performance?

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

What is the difference between running GraalVM’s JavaScript in a Native Image compared to the JVM?

In essence, the JavaScript engine of GraalVM is a plain Java application. Running it on any JVM (JDK 8 or higher) is possible, but, for a better result, it should be GraalVM or a compatible JVMCI-enabled JDK using the GraalVM 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 a Native Image means that the JavaScript engine, including all its dependencies from, e.g., the JDK, is pre-compiled into a native binary. This will tremendously speed up 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.

Can npm packages be installed globally?

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

While the original Node.js implementation has one main folder (NODE/bin) to put binaries and globally installed packages and their commandline tools, GraalVM has several: the main GRAALVM/bin folder, and separate folders for each language, e.g. GRAALVM/jre/languages/js/bin. When installing npm packages globally in GraalVM, links to the executables e.g. for command line interface tools are put to the JavaScript-specific folder. In order for globally installed packages to function properly, you might need to add GRAALVM/jre/languages/js/bin to your $PATH.

Another option is to specify the global installation folder 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.

Errors

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

Reason:

Solution:

TypeError: UnsupportedTypeException

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

Reason:

Solution:

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:

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, not 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 setups, 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();