Bridging Worlds: The Nashorn JavaScript Engine in Java

The Nashorn JavaScript engine (pronounced Nass-horn, German for "rhinoceros") was a significant component introduced in Java 8 that allowed developers to execute JavaScript code directly within the JVM. It served as a powerful bridge between the Java and JavaScript ecosystems, enabling unique integration scenarios before the rise of more modern alternatives.

This article explores Nashorn's capabilities, practical usage, and its eventual deprecation and removal from the JDK.


What Was Nashorn?

Nashorn was a JavaScript engine developed entirely in Java, complying with the ECMAScript 5.1 specification. It replaced the original Rhino engine and was a key part of the JDK from Java 8 until its removal in Java 15.

Key Characteristics:

  • High Performance: Utilized the Java InvokeDynamic API for JIT compilation of JavaScript to bytecode
  • Tight JVM Integration: JavaScript could call Java methods and vice versa
  • JDK-Bundled: Available out-of-the-box in Java 8-14 (as a deprecated component in later versions)

Core Usage: Executing JavaScript from Java

The primary interface for working with Nashorn was through the ScriptEngine API (JSR-223).

Basic Example: Hello World

import javax.script.*;
public class NashornBasic {
public static void main(String[] args) throws ScriptException {
// Get the Nashorn script engine
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
// Execute JavaScript code
engine.eval("print('Hello from JavaScript!');");
// Evaluate expressions with return values
Object result = engine.eval("2 + 3 * 4;");
System.out.println("Result: " + result); // Output: Result: 14.0
}
}

Working with Variables

public class NashornVariables {
public static void main(String[] args) throws ScriptException {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
// Put Java variables into JavaScript context
engine.put("name", "Java Developer");
engine.put("count", 42);
// Use variables in JavaScript
engine.eval("print('Hello, ' + name);");
engine.eval("print('Count times 2: ' + (count * 2));");
// Get variables back from JavaScript
engine.eval("var jsVar = 'Hello from JS';");
String jsValue = (String) engine.eval("jsVar;");
System.out.println("From JS: " + jsValue);
}
}

Bidirectional Integration: Java ⇄ JavaScript

Nashorn's most powerful feature was the seamless interoperability between Java and JavaScript.

Calling Java from JavaScript

public class JavaFromJS {
public static void main(String[] args) throws ScriptException {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
// Create Java objects and call methods from JavaScript
engine.eval(""
+ "var ArrayList = Java.type('java.util.ArrayList');"
+ "var list = new ArrayList();"
+ "list.add('Hello');"
+ "list.add('World');"
+ "print('List size: ' + list.size());"
+ "print('First element: ' + list.get(0));"
+ ""
);
// Using Java arrays
engine.eval(""
+ "var IntArray = Java.type('int[]');"
+ "var array = new IntArray(5);"
+ "for (var i = 0; i < array.length; i++) {"
+ "    array[i] = i * 2;"
+ "}"
+ "print('Array length: ' + array.length);"
);
// Calling static methods
engine.eval(""
+ "var System = Java.type('java.lang.System');"
+ "System.out.println('Current time: ' + System.currentTimeMillis());"
);
}
}

Implementing Java Interfaces in JavaScript

import javax.script.*;
public class JSInterface {
public static void main(String[] args) throws ScriptException {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
// Define a JavaScript object that implements a Java interface
engine.eval(""
+ "var runnable = {"
+ "    run: function() {"
+ "        print('JavaScript code running in a thread!');"
+ "    }"
+ "};"
);
// Get the JavaScript object and use it as Java Runnable
Object jsRunnable = engine.get("runnable");
// Run in a Java thread
Thread thread = new Thread((Runnable) jsRunnable);
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

Advanced Features

Function Binding and Invocation

public class FunctionBinding {
public static void main(String[] args) throws ScriptException, NoSuchMethodException {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
// Define a JavaScript function
engine.eval("function greet(name) { return 'Hello, ' + name + '!'; }");
// Get the function as a Java object
Invocable invocable = (Invocable) engine;
// Call the JavaScript function from Java
String result = (String) invocable.invokeFunction("greet", "Java Developer");
System.out.println(result); // Output: Hello, Java Developer!
// Multiple parameters
engine.eval("function calculate(a, b) { return a * b + 10; }");
Number calcResult = (Number) invocable.invokeFunction("calculate", 5, 3);
System.out.println("Calculation result: " + calcResult); // Output: 25.0
}
}

Loading External JavaScript Files

public class ExternalScripts {
public static void main(String[] args) throws ScriptException {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
// Load and execute external JavaScript file
try {
engine.eval(new java.io.FileReader("utils.js"));
// Now we can use functions defined in the external file
Invocable invocable = (Invocable) engine;
Object result = invocable.invokeFunction("externalFunction", "parameter");
System.out.println("External function result: " + result);
} catch (java.io.FileNotFoundException e) {
System.err.println("JavaScript file not found: " + e.getMessage());
}
}
}

utils.js:

function externalFunction(param) {
return "Processed: " + param + " at " + new Date();
}
// Utility functions available to Java
function formatName(first, last) {
return last + ", " + first;
}

The Decline and Removal of Nashorn

Timeline:

  • Java 8 (2014): Nashorn introduced as replacement for Rhino
  • Java 11 (2018): Nashorn deprecated, with warning of future removal
  • Java 15 (2020): Nashorn completely removed from the JDK

Reasons for Deprecation:

  1. Rapid JavaScript Evolution: ECMAScript standards evolved much faster than Nashorn could keep up with
  2. Competition from GraalVM: GraalVM's JavaScript engine offered better performance and ES6+ support
  3. Maintenance Burden: Keeping Nashorn updated with new JavaScript features was resource-intensive
  4. Shift in Ecosystem: Node.js and modern JavaScript runtimes reduced the need for embedded JS engines

Modern Alternatives to Nashorn

1. GraalVM JavaScript Engine

GraalVM provides a modern, high-performance JavaScript engine that supports latest ECMAScript features.

// Using GraalVM JavaScript (when running on GraalVM)
import org.graalvm.polyglot.*;
public class GraalVMJS {
public static void main(String[] args) {
try (Context context = Context.create()) {
Value result = context.eval("js", "2 + 3 * 4;");
System.out.println("Result: " + result.asInt());
// More modern JavaScript features
context.eval("js", ""
+ "const arrowFunc = (x, y) => x + y;"
+ "print('Arrow function: ' + arrowFunc(5, 3));"
+ ""
+ "// ES6 classes"
+ "class Calculator {"
+ "  multiply(a, b) { return a * b; }"
+ "}"
+ "const calc = new Calculator();"
+ "print('Class method: ' + calc.multiply(4, 5));"
);
}
}
}

2. Standalone JavaScript Integration

For many use cases, better alternatives exist:

  • Command-line execution of Node.js scripts
  • REST APIs between Java and Node.js services
  • GraalVM polyglot applications
  • Custom DSLs instead of full JavaScript engines

Legacy Nashorn Code Migration Example

Original Nashorn Code:

// Old Nashorn approach
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval("function process(data) { return data.toUpperCase(); }");
Invocable invocable = (Invocable) engine;
String result = (String) invocable.invokeFunction("process", "hello");

Modern GraalVM Equivalent:

// Modern GraalVM approach
try (Context context = Context.create()) {
context.eval("js", "function process(data) { return data.toUpperCase(); }");
Value result = context.eval("js", "process('hello')");
System.out.println(result.asString());
}

Conclusion

Nashorn represented an important era in Java's evolution, demonstrating the JVM's versatility as a multi-language platform. While it's no longer part of the JDK, its legacy lives on in several ways:

  1. Proved JVM's Capability: Showed that the JVM could efficiently run dynamic languages
  2. Paved the Way for GraalVM: Many concepts evolved into the more advanced GraalVM ecosystem
  3. Educational Value: Taught important lessons about language integration and engine design

For new projects requiring JavaScript execution on the JVM, GraalVM is the recommended path forward. For existing Nashorn-based applications, migration to GraalVM or architectural reconsideration is necessary to ensure long-term maintainability and access to modern JavaScript features.

The story of Nashorn serves as a reminder of the rapidly evolving nature of both the Java and JavaScript ecosystems, and the importance of choosing technologies with sustainable long-term support.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper