Technical Articles

Tech Articles from your friends at Oracle and the developer community.

Practical Nashorn, Part 1: Introducing JavaScript, ECMAScript, and Nashorn

The Nashorn engine has been deprecated in JDK 11 as part of JEP 335 and and has been removed from JDK15 as part of JEP 372. To learn more, please read Migration Guide from Nashorn to GraalVM JavaScript.

This series of articles introduces Nashorn, a blazing fast JavaScript runtime engine that shipped with Java SE 8 to provide a lightweight environment for extending, supplementing, and often even replacing Java code. Full Java Virtual Machine (JVM) capabilities and dynamic typing represent an effective tooling that will appeal to developers and admins alike.

Nashorn is a Java implementation of JavaScript that was developed under JSR 292: Supporting Dynamically Typed Languages on the Java Platform, as part of a Da Vinci Machine Project inside the JVM group. Dynamic typing is achieved through the use of invokedynamic bytecode that delegates invocations on Java objects. This allows a seamless integration between Java and JavaScript and requires only a minimal amount of boilerplate to start programming on the JVM without the need to write or compile Java code.

The Nashorn engine is a direct successor to Mozilla Rhino, and it continues the open source model under the OpenJDK project. The current implementation of Nashorn conforms to the ECMAScript 5.1 specification with the addition of new ECMAScript 6 (ES6) features in subsequent JDK releases under JEP 292: Implement Selected ECMAScript 6 Features in Nashorn. Starting with Java SE 8, each Java runtime environment (JRE) and Java Development Kit (JDK) ships with Nashorn by default, which makes Nashorn a very easy and accessible tool for gluing together all sorts of applications, databases, and clouds.

Running Nashorn

On systems with Java SE 8 or newer installed, Nashorn is already available for use. Most of the Linux distributions might simply choose to install the java-1.8.0-openjdk package to quickly deploy the JDK. An easy way to try out Nashorn is a Docker container with the latest stable OpenJDK release, as shown in Listing 1 (the official enterprise-grade JRE container store/oracle/serverjre:8 can be used instead for production environments).

$ jjs -v
nashorn 1.8.0_141
jjs>

C:\> jjs.exe -v
nashorn 1.8.0_141
jjs>

$ docker run openjdk jjs -v
nashorn 1.8.0_141
jjs>

Listing 1. Running the Nashorn shell on Linux or macOS, on Windows, or on Docker.

The binary program jjs is used to launch the internal Nashorn shell, which simply instantiates the jdk.nashorn.tools.Shell class in Java 8 or jdk.scripting.nashorn.shell in Java 9. The interactive prompt is quite like the interactive interpreters from Python or Ruby. To quit the prompt run the exit() function or press Control-C, where exit can optionally take a status code as a parameter. A commonly known DBA trick is to run interactive interpreters with the rlwrap wrapper to benefit from persistent history and typeahead search, which makes a lot of sense for shells such as jjs (rlwrap package is part of the Fedora EPEL repo).

Because Nashorn is simply a Java program, all JVM-specific attributes can be provided on the command line, the most important being the classpath and system properties, which are specified with the -cp and -D switches, respectively. Instead of the built-in -cp or -classpath, it's often better to use the -J-Djava.class.path attribute to avoid class resolution issues—for example, with JDBC drivers—caused by nesting execution context.

When JVM code comes into play, Nashorn will not throw the entire Java stack trace by default. To achieve better traceability, jjs must be invoked as jjs --dump-on-error or simply jjs -doe.

To quickly determine all running instances of Nashorn, jcmd can be used to list their process IDs and parameters. This can be used to quickly confirm correct classpath parameters, ES6 compatibility, or the scripting mode, as shown in Listing 2.

$ jcmd
2660 jdk.nashorn.tools.Shell -scripting
7476 jdk.nashorn.tools.Shell --language=es6 -ot
15640 sun.tools.jcmd.JCmd

Listing 2. Viewing running Nashorn shell instances with jcmd.

ES6 and Rhino

When Java 8 debuted in 2014, the latest ECMAScript specification available at the time was 5.1. That version became Nashorn's baseline. Shortly thereafter, work on JEP 292 started and most important ES6 features started shipping. The single largest addition was the introduction of JEP 203: Nashorn: Lexically-Scoped Variable & Constant Declarations in Java 8 Update 40, which fixed one of the biggest gripes of ES5 JavaScript: the lack of variable block scope. Nashorn's ES6 efforts and JEP 203 have fixed this with the introduction of the let and const keywords, while preserving the var keyword. Listing 3 demonstrates the importance of using let and const for programmers coming to JavaScript from other backgrounds. Larger Nashorn codebases will certainly benefit from running the ES6 mode by default to avoid unexpected scoping behavior unless JavaScript 5 compatibility is intended.

var x = 1;
function es5(b) {
    if (b) {
        var x = 2;
    }
    print(x);
}

let y = 1;
function es6(b) {
        let y = 2;
    if (b) {
    }
    print(y); 
}

jjs> es5(true);
2

jjs> es5(false);
undefined

jjs> es6(true);
1

jjs> es6(false);
1

Listing 3. Nashorn implementation of ES6 variable scope.

Mozilla Rhino–compatible programs can be safely executed on Nashorn by loading the compatibility library, which implements the importClass, importPackage, and JavaAdapter functions, among others. To enable compatibility, execute load("nashorn:mozilla_compat.js") as the first step in the code. Note that this feature has been kept only for backwords-compatibility purposes; using Rhino extensions by default is not recommended.

Due to the rapid development track that Nashorn has undergone across the Java 8 lifespan, make sure you work with a recent Nashorn release. An easy way to ensure a consistent Nashorn version is being used is by running jjs -v, which displays version information as the first line of output (jjs -fv displays full version information).

JavaScript 101

JavaScript has proven itself to be a very easy language to comprehend and immediately put into use. However, it comes with a set of basic principles that could be baffling to newcomers:

  • While the ECMAScript specification lists semicolons as optional, using them in all assignment statements and function declarations can protect against unintentional results.
  • Variables are undefined by default which is a different from being null. To check if variable is defined, the basic condition if (variable) is a sufficient check.
  • JavaScript introduces the concept of truthy and falsy (or falsey) values. All values except undefined, null, false, -0, +0, NaN, and '' are truthy and evaluate to true during comparison.
  • Loose typing requires understanding rules behind type coercion: 3 + 3 + "3" evaluates to 63 whereas "3" + 3 + 3 yields 333. This implicit conversion stems from left-to-right arithmetic and when a string object is found, the operation continues as a concatenation.
  • The same type of coercion applies to a comparison invoked with the == operator. To avoid coercing the type when comparing values, it's good to use the === operator, which compares both the value and the type, for example, 1 == true but 1 !== true.
  • For all operations involving numbers, a good habit is to use the built-in parseInt and parseFloat functions or a shorter +variable expression syntax to perform number conversion.
  • Just like Bash, conditional expressions could be simplified with the && and || operators so that 1 == "1" && print("yes") simulates the longer if (1 == "1") { print("yes"); } syntax.
  • A function always expects an arbitrary number of positional arguments, rendering missing values as undefined. The predefined array-like arguments variable exposes the index access with square brackets. The only ways to access this variable are through arguments[i] and arguments.length().
Module System

Node.js developers attempting to execute the require() function to include an external module will be immediately puzzled by receiving the ReferenceError: "require" is not defined error. Nashorn itself doesn't incorporate any module system built in and relies only on the load() function to import dependencies. Nevertheless, require() is totally possible with the use of third-party scripts implementing the CommonJS specification, such as Nodyn . See Listing 4 for a demonstration of the popular JavaScript unit testing framework QUnit used against Java code.

/* unittest.js */
load("jvm-npm.js");
var QUnit = require("qunit-2.4.0.js");

QUnit.done(function(details) {
    print(JSON.stringify(details, null, 4));
});

QUnit.test("Running at least Java 8", function(assert) {
    assert.ok(java.lang.System.getProperty("java.version") >= "1.8");
});

QUnit.test("Always failing", function(assert) {
    assert.fail();
});

QUnit.load();

$ jjs unittest.js
{
    "passed": 1,
    "failed": 1,
    "total": 2,
    "runtime": 295
}

Listing 4. Using the JavaScript QUnit unit testing framework from Nashorn.

Even though JavaScript does not offer syntax for implementing modules, a commonly used pattern can be used with Nashorn to leverage the built-in load() function. For even fairly complex code, modules are inevitable for readability and maintainability in the long run. Listing 5 shows a typical module implementation that covers object-oriented programming aspects—with the possibility of addressing global scope, even in the absence of a browser's window or console object in the language specification.

/* module.js */
(function(global) {
    return function(moduleArg) {
        var privateMethod = function(v) {
            print("This is private");
            global["v"] = v;
        }

        this.publicMethod = function() {
            print("This is public");
        }

        __proto__.staticMethod = function() {
            print("This is static");
        }

        {
            print("This is constructor");
            privateMethod(42);
        }
    }
})(this);

jjs> var Module = load('module.js');

jjs> Module.staticMethod()
This is static

jjs> var m = new Module();
This is constructor
This is private

jjs> Module.privateMethod()
<shell>:1 TypeError: Module.privateMethod is not a function

Listing 5. Nashorn module pattern.

Summary

JavaScript's exponential growth, fueled by progress in web development, positions the language high in the ranks. The work that was started by Mozilla and Sun continues to receive a lot attention at Oracle, with a growing number of tools and products leveraging Nashorn for extensibility and scripting. The language that stems from the web has successfully entered the server space and, thanks to ScriptEngine and JSR 223, it has become a first-class JVM citizen. Subsequent articles in this series demonstrate first-hand examples of Nashorn put into use.

About the Authors

Przemyslaw Piotrowsk is principal software engineer with 10+ years of experience in design, development and maintenance of database systems. He is an Oracle Database 11 g Certified Master, an Oracle Database 12 c Certified Master, and an Oracle Database Cloud Certified Master, focusing on database engineering and infrastructure automation.

Latest content

Explore and discover our latest tutorials

Serverless functions

Serverless functions are part of an evolution in cloud computing that has helped free organizations from many of the constraints of managing infrastructure and resources. 

What is a blockchain?

In broad terms, a blockchain is an immutable transaction ledger, maintained within a distributed peer-to-peer (p2p) network of nodes. In essence, blockchains serve as a decentralized way to store information.

OCI CLI

The CLI is a small-footprint tool that you can use on its own or with the Console to complete Oracle Cloud Infrastructure tasks. The CLI provides the same core functionality as the Console, plus additional commands.