Nashorn: JavaScript made great in Java 8
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.
Copyright © 2014 IDG Communications, Inc.