Home | Send Feedback

Migration from Nashorn to GraalVM JavaScript

Published: April 01, 2020  •  java

Oracle released Java 14 just a few weeks ago, and the focus shifts to the next Java 15 version (September 2020). Java 15 currently (April 2020) contains four confirmed features/changes: two new garbage collectors, text blocks, which is a preview feature in Java 13 and 14 and now moves into the language.

The last change concerns the Nashorn JavaScript Engine, which will be removed in Java 15. In this blog post, we take a look at how you can migrate to another engine.

Visit the OpenJDK project page for an up to date list of the upcoming features and changes in Java 15:
https://openjdk.java.net/projects/jdk/15/


The Java runtime had a built-in JavaScript engine for quite some time. It started with Java SE 6 when Sun bundled Java with Rhino. In Java 8, they replaced Rhino with a more modern engine: Nashorn. Oracle announced a couple of years ago that this engine would be removed from the JDK. Since Java 11, the engine is deprecated, and in Java 15, they finally remove the engine.

Note that this does not affect the javax.script API. This API stays a part of Java.

The removal of the engine does not mean that you have to remove your Nashorn based code from your project. Fortunately, there is a successor, and it's called: GraalVM JavaScript. GraalVM is a universal virtual machine for running applications written in JavaScript, Python, Ruby, R, JVM-based, and LLVM-based languages.

If you want to learn more about GraalVM, visit the project site: https://www.graalvm.org

To learn more about the JavaScript engine, visit the reference documentation.


You don't have to run your application on the Graal virtual machine to use the new Javascript engine. The GraalVM JavaScript engine is a regular Java library hosted on the Maven central repository.

The advantage of switching to the new engine, besides the point that your application runs on the latest JVM, is that you also get a more modern JavaScript engine. Nashorn only supports ECMAScript 5.1. GraalVM JavaScript, on the other hand, is in version 20.0.0 fully compatible with ECMAScript 2019, and it even supports most of the ES2020 features. Starting with GraalVM 20.1.0, ECMAScript 2020 is fully supported.

Migration

Here a simple example of Java code that executes JavaScript code. The javax.script abstraction handles all the interactions with the underlying engine. The only thing you have to specify is the name of the engine (nashorn)

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class Simple {

  public static void main(String[] args) throws ScriptException {
    // Nashorn
    ScriptEngine nashornEngine = new ScriptEngineManager().getEngineByName("nashorn");
    nashornEngine.eval("print('Hello World!');");

Simple.java


This example will no longer run on Java 15+, so we switch to the GraalVM JavaScript engine. First, add the required dependencies to your project.

    <dependency>
      <groupId>org.graalvm.js</groupId>
      <artifactId>js</artifactId>
      <version>20.0.0</version>
    </dependency>  
    <dependency>
      <groupId>org.graalvm.js</groupId>
      <artifactId>js-scriptengine</artifactId>
      <version>20.0.0</version>
    </dependency>

pom.xml

Then change the engine name to graal.js.

    // Graal
    ScriptEngine graalEngine = new ScriptEngineManager().getEngineByName("graal.js");
    graalEngine.eval("print('Hello World!');");

Simple.java

With this simple change, your code runs on the GraalVM JavaScript engine, and it works on Java 15+ virtual machines.

You don't have to wait until Oracle removes the Nashorn engine. This code also works with Java 11. The benefit is that you get an engine that supports the latest JavaScript specification.

Example

Another example where the Java code calls a JavaScript function

function fibonacci(num) {
  var a = 1, b = 0, temp;

  while (num >= 0){
    temp = a;
    a = a + b;
    b = temp;
    num--;
  }

  return b;
}

fibonacci.js

import java.io.BufferedReader;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class Fibonacci {
  public static void main(String[] args)
      throws ScriptException, IOException, URISyntaxException, NoSuchMethodException {

    Path jsPath = Paths.get(Fibonacci.class.getResource("/fibonacci.js").toURI());

    // Nashorn
    ScriptEngine nashornEngine = new ScriptEngineManager().getEngineByName("nashorn");
    try (BufferedReader reader = Files.newBufferedReader(jsPath)) {
      nashornEngine.eval(reader);

      Invocable invocable = (Invocable) nashornEngine;
      Object result = invocable.invokeFunction("fibonacci", 1_000);

      System.out.println(result);
    }

    // Graal
    ScriptEngine graalEngine = new ScriptEngineManager().getEngineByName("graal.js");
    try (BufferedReader reader = Files.newBufferedReader(jsPath)) {
      graalEngine.eval(reader);

      Invocable invocable = (Invocable) graalEngine;
      Object result = invocable.invokeFunction("fibonacci", 1_000);

      System.out.println(result);
    }

  }
}

Fibonacci.java

Except for the engine name, the code is the same.

Extension

The Nashorn engine adds a few extensions to the JavaScript engine. For example, it is possible to use Java types in JavaScript. The following function uses the java.math.BigDecimal class for the calculation.

function factorialize(num) {
  
  if (num === 0 || num === 1) {
    return 1;
  }
  
  var result = new java.math.BigDecimal(String(num));
  for (var i = num - 1; i >= 1; i--) {
    result *= i;
  }
  
  return result.toString();

}

factorialize.js

By default, this code does not run on the graal.js engine. If you want to use these features, you have to enable the Nashorn compatibility mode. In this example, we do that with a call to System.setProperty.

    // GraalVM
    System.setProperty("polyglot.js.nashorn-compat", "true");

    ScriptEngine graalEngine = new ScriptEngineManager().getEngineByName("graal.js");
    try (BufferedReader reader = Files.newBufferedReader(jsPath)) {
      graalEngine.eval(reader);

      Invocable invocable = (Invocable) graalEngine;
      Object result = invocable.invokeFunction("factorialize", 5);

      System.out.println(result);
      System.out.println(result.getClass());
    }

Extension.java

To learn more about the compatibility mode, read the reference documentation:
https://www.graalvm.org/docs/reference-manual/languages/js/#nashorn-compatibility-mode

I also recommend to check out the migration guide:
https://github.com/graalvm/graaljs/blob/master/docs/user/NashornMigrationGuide.md#extensions-only-available-in-nashorn-compatibility-mode