Nashorn: JavaScript made great in Java 8

JavaScript

A first look at Java's sleeker JavaScript compiler and command-line tools

Nashorn, pronounced "nass-horn," is German for "rhinoceros," and it's one of the animal names for a German tank destroyer used in World War II. It's also the name of the replacement -- introduced with Java 8 -- for the old, slow Rhino JavaScript engine. Both Rhino and Nashorn are implementations of the JavaScript language written to run on the Java virtual machine, or JVM.

Obligatory rant: JavaScript may have Java as part of its name, but the two languages are very different in spirit and design, as well as in their implementations. Nevertheless, one way of implementing a JavaScript interpreter is to compile JavaScript into Java byte codes, which is what Rhino and Nashorn were designed to do.

You probably think of JavaScript in terms of scripting Web browsers, and you'd be right for the most part. It's also used for servers. For example, Node.js is used to build fast, lightweight servers based on the V8 JavaScript engine from Google Chrome. JavaScript engines in Web browsers have access to the HTML document object model (DOM) and can manipulate HTML elements through the DOM. Given that different Web browsers have different DOMs and JavaScript engines, frameworks such as jQuery try to hide the implementation details from the programmer.

Nashorn, and Rhino before it, explicitly do not support the browser DOM. Implemented on the JVM, they are typically called in for end-user scripting in Java applications. Nashorn and Rhino can be embedded in Java programs and used as command-line shells. Of course, the additional magic needed when you are scripting Java from JavaScript is bridging the data and type mismatches between the two languages.

Problems with Rhino

Rhino development started at Netscape in 1997 for an ill-fated "Javagator" project and was released to Mozilla.org in 1998. It was then licensed to Sun and others. Honestly, 1998 might as well be the Jurassic Period, as Internet development goes -- 16 years later, Rhino has clearly shown its age. According to Jim Laskey of Oracle, the principal developer of Nashorn:

I'm sure this is all true, but as a jaded developer and development manager I find the last sentence highly amusing. After all, major rewrites are never fun. Starting from scratch is always fun.

Goals of Nashorn

Laskey described his goals for Nashorn as follows:

  • Nashorn will be based on the ECMAScript-262 Edition 5.1 language specification and must pass the ECMAScript-262 compliance tests.
  • Nashorn will support the javax.script (JSR 223) API.
  • Support will be provided for invoking Java code from JavaScript and for Java to invoke JavaScript code. This includes direct mapping to JavaBeans.
  • Nashorn will define a new command-line tool, jjs, for evaluating JavaScript code in "shebang" scripts, here documents, and edit strings.
  • Performance and memory usage of Nashorn applications should be significantly better than Rhino.
  • Nashorn will not expose any additional security risks.
  • Supplied libraries should function correctly under localization.
  • Error messages and documentation will be internationalized.

Laskey also explicitly limited the scope of the project with some "non-goals":

  • Nashorn will only support ECMAScript-262 Edition 5.1. It will not support any features of Edition 6 or any nonstandard features provided by other JavaScript implementations.
  • Nashorn will not include a browser plug-in API.
  • Nashorn will not include support for DOM/CSS or any related libraries (such as jQuery, Prototype, or Dojo).
  • Nashorn will not include direct debugging support.

So what does it mean to be based on ECMAScript-262 Edition 5.1? The differentiator here is that Rhino was based on the older, less capable Edition 3. The javax.script (JSR 223) API is for calling back into JavaScript from Java.

The lack of debugging support in Nashorn is a step backward from Rhino, which has its own JavaScript debugger. However, you'll find workarounds for this deliberate omission in at least two popular IDEs.

Nashorn command-line tools: Installing jjs and jrunscript

After reading about Nashorn's command-line tool, jjs, I was eager to try out the shell on my iMac, but after installing Java 8 it wasn't available to the bash shell. It turns out the documentation and implementation weren't completely in sync.

I knew the installation had been successful:


>java -version
java version "1.8.0"
Java(TM) SE Runtime Environment (build 1.8.0-b132)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)

but running jjs returned -bash: jjs: command not found. A little poking around brought me to the /usr/bin/ directory:


>which java
/usr/bin/java

There I found something called jrunscript, which turned out to be a variant of jjs that runs an extra startup script. That should have satisfied me, but I was puzzled as to why the documented jjs tool wasn't installed in /usr/bin/ with the rest of the Java 8 runtime. A little research led me to look at the JavaVirtualMachines installation for Java 8. On a Mac, look for jjs in /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/bin/ or /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/bin/.

You can define an alias for jjs in the latter directory and add it to your shell configuration if you need it for scripting on a Mac or Linux. On a PC, you can add the correct jre/bin/ directory to your PATH. In his video from the Java 8 launch, Jim Laskey suggests copying jjs to the /usr/bin/ directory, but when I did that I found that jjs couldn't find the JRE properly at runtime.

Running JavaScript scripts

Why the two command-line tools for running JavaScript scripts? I'm not completely clear on what the development team was thinking, but jjs has capabilities that jrunscript doesn't, and jrunscript has an initialization file. Below are a few simple examples of jjs and jrunscript use.


$ jrunscript
nashorn> alert("hello, InfoWorld");
script error: ReferenceError: "alert" is not defined in <STDIN> at line number 1

This doesn't work because alert() is a browser/DOM function. D'oh! I could have sworn that worked in Rhino, though.


nashorn> print("Hello, InfoWorld");
Hello, InfoWorld

This does work because print() is a core JavaScript function.


nashorn> var a = 1;
nashorn> var b = "1";
nashorn> print (a+b);
11
nashorn> print(a+a);
2
nashorn> quit();
$

In other words, we have a basic REPL (read-execute-print-loop command-line) environment for JavaScript here. If you're surprised by the answer to a+b, consider this:


nashorn> print (typeof(a+b));
string

That's a charming side-effect of the loose typing and overloading of the "+" operator in JavaScript. It's correct behavior according to the JavaScript specification, not a bug.

Nashorn supports the "#" character as a leading line comment marker, so jjs and jrunscript can be used in executable "shebang" scripts written in JavaScript. On a Mac or Linux, you'll have to mark the JavaScript file as executable with the chmod utility to make it runnable.

You'll find a scripting mode in jjs that jrunscript seems to lack. In scripting mode, expressions inside back-ticks are passed to the outer shell for evaluation:


$ jjs -scripting
jjs> print ('ls');
Applications
Applications (Parallels)
Creative Cloud Files
Desktop
...
work

jjs>

Scripting mode also enables an extension for "heredocs," which are basically multiline strings in a format familiar to Perl and Ruby programmers.

By the way, the arrow keys on the Mac keyboard don't work properly for line editing in the jjs shell. But there is a hack for that: You can brew install rlwrap and use that as part of your alias for jjs in your .bashrc or .zshrc file.

Calling JavaScript from Java

To call Nashorn JavaScript from a Java 8 program, you basically need to make a new ScriptEngineManager instance and use that ScriptEngineManager to load the Nashorn script engine by name. (See this Stack Overflow question for a pithy summary of loading and debugging Nashorn.)

Finally, you can pass the Nashorn engine a file or a string to evaluate:


import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
...
try {
    ScriptEngineManager factory = new ScriptEngineManager();
    ScriptEngine engine = factory.getEngineByName("nashorn");
    engine.eval("load(\"" + "src" + "/" + "javascript_sample" + "/" + "test1.js" + "\");");
    } catch (Exception ex) {
            //...
        }
...

try {
    ScriptEngineManager factory = new ScriptEngineManager();
    ScriptEngine engine = factory.getEngineByName("nashorn");
    engine.eval("function hi(){\nvar a = 'PROSPER'.toLowerCase(); \nmiddle(); \nprint('Live long and' + a)}\n function middle(){\n var b = 1; for(var i=0, max = 5; i<max;i++){\nb++;\n}\n print('b is '+b);}hi();");
    } catch (ScriptException ex) {
            //...
        }

Note that scripts can always generate ScriptException errors, so you need to catch them.

Calling Java from JavaScript

Calling Java from Nashorn is about as easy as it can be, since the Java 8 class libraries are built into Nashorn:


print(java.lang.System.currentTimeMillis());

var file =  new java.io.File("sample.js"); 
print(file.getAbsolutePath()); 
print(file.absolutePath); 

Note that Nashorn does not import the java package by default, because references to String or Object conflict with the corresponding types in JavaScript. Hence, a Java string is java.lang.String, not String.

Nashorn and JavaFX

If you invoke jjs with the -fx switch, it will allow you to use visual JavaFX classes in your Nashorn applications. For instance, the following example from the Oracle documentation displays a JavaFX button:


var Button = javafx.scene.control.Button;
var StackPane = javafx.scene.layout.StackPane;
var Scene = javafx.scene.Scene;

function start(primaryStage) {
    primaryStage.title = "Hello World!";
    var button = new Button();
    button.text = "Say 'Hello World'";
    button.onAction = function() print("Hello World!");
    var root = new StackPane();
    root.children.add(button);
    primaryStage.scene = new Scene(root, 300, 250);
    primaryStage.show();
}

Debugging Nashorn

I mentioned earlier that Nashorn doesn't include a debugger of its own. Fortunately, both NetBeans 8 and IntelliJ IDEA 13.1 support debugging Nashorn JavaScript. The Stack Overflow question I mentioned earlier includes a useful NetBeans 8 project that you can use as a sample. You'll find that simply using the debug item from the pop-up menu on JavaScript files will allow you to debug the Nashorn code.

In IntelliJ IDEA 13, you can set breakpoints in the Java and Nashorn JavaScript files using the same shortcut key (Com/Ctrl-F8). When you hit a JavaScript breakpoint, you get all the usual debugging information.

Nashorn was designed to be a better, faster replacement for the old Rhino engine, and by most measures it succeeds. It has some minor warts that I hope will be corrected in future updates, but for now there are reasonable hacks to let you use Nashorn effectively in your projects.

This story, "Nashorn: JavaScript made great in Java 8" was originally published by InfoWorld.

Join the discussion
Be the first to comment on this article. Our Commenting Policies