A change in Java 15 is the removal of the Nashorn JavaScript Engine. This blog post shows you how to migrate to another engine.
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. Then, 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 removed 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 Java library hosted on the Maven central repository.
The advantage of switching to the new engine is getting a more modern JavaScript engine. Nashorn only supports ECMAScript 5.1. GraalVM JavaScript, on the other hand, is in version 22.0.0 is compatible with ECMAScript 2022.
Migration ¶
Here is 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
).
Note you can also get a reference to the engine with getEngineByName("javascript")
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!');");
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.
<groupId>org.graalvm.js</groupId>
<artifactId>js</artifactId>
<version>23.0.2</version>
</dependency>
<dependency>
<groupId>org.graalvm.js</groupId>
<artifactId>js-scriptengine</artifactId>
<version>23.1.1</version>
</dependency>
</dependencies>
Then change the engine name to graal.js
.
// Graal
ScriptEngine graalEngine = new ScriptEngineManager().getEngineByName("graal.js");
graalEngine.eval("print('Hello World!');");
With this simple change, your code runs on the GraalVM JavaScript engine, and it works on Java 15+ virtual machines.
If your code calls getEngineByName("javascript")
you don't have to change any code. Add the GraalVM JavaScript engine
to the class path and your code should run fine without any modifications.
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;
}
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);
}
}
}
Except for the engine name, the code is the same.
You can also get the engine with getEngineByName("javascript")
. It will automatically get a reference to the
installed JavaScript engine.
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();
}
This code does not run on the graal.js
engine by default. If you want to use these features, you must 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());
}
To learn more about the compatibility mode, read the reference documentation:
https://www.graalvm.org/reference-manual/js/NashornMigrationGuide/#nashorn-compatibility-mode
I also recommend checking out the migration guide:
https://github.com/oracle/graaljs/blob/master/docs/user/NashornMigrationGuide.md#extensions-only-available-in-nashorn-compatibility-mode