Technical Articles

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

Practical Nashorn, Part 2: The Java in JavaScript

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

After Sun Microsystems sealed the licensing deal in December 1995, the language that started at Netscape as Mocha and later LiveScript, became JavaScript—having literally nothing in common with Java. The only reason for rebranding at the time was to increase JavaScript's popularity given the recent release of Java 1.0. The release of Nashorn brings an interesting closure to the story, given how closely integrated these two languages are. Both Sun and, later, Oracle have attempted to explore the possibilities of dynamic runtimes and were largely successful in bringing rapid development capabilities to the Java Virtual Machine (JVM) platform.

The introduction of the Java Scripting API in Java 6 changed how the JVM is perceived in the software world. Whereas Java remains the primary citizen, there is now fair share of JVM development running Scala, Ruby, or Python code through the Java Scripting API. With the inclusion of Nashorn in Java SE 8, it's now possible to leverage scripting without third-party dependencies, because Nashorn became part of the official JDK and Java runtime environment (JRE) offerings.

Scripting for the Java Platform

The Java Scripting API allows for seamless integration of any compatible language directly with the ScriptEngine object. JSR 223 (Scripting for the Java Platform) languages are discovered by the JVM automatically by providing a ScriptEngineFactory implementation to the Service Provider Interface (SPI).

Listing 1 demonstrates the use of the Java Scripting API to enumerate all JSR 223–compatible runtimes available to the JVM. Note that the jython-standalone-2.7.1.jar file has been added to the classpath, whereas Nashorn does not require any declaration and is always available. The printEngine function lists typical properties of a script factory, providing consistent output information about engines at run time.

 
/* engines.js */
    var printEngine = function(e) {
        print(java.lang.String.format("%s %s -- %s",
            e.getEngineName(), 
            e.getEngineVersion(), 
            e.getExtensions(), 
            e.getLanguageName()));
    }
    
    var engines = new javax.script.ScriptEngineManager().getEngineFactories();
    engines.stream().forEach(printEngine); 
    
    $ jjs -J-Djava.class.path=lib/jython-standalone-2.7.1.jar engines.js
    Oracle Nashorn 1.8.0_144 -- [js]
    jython 2.7.1 -- [py]

Listing 1. Nashorn running along with Jython on the JVM.

In the example in Listing 1, along with the ScriptEngine code, Nashorn is directly using the Java 8 Stream interface and expression closure supplied to forEach—if the function has a simple return type, the braces and return keyword may be omitted. An expression such as function(v) v is a convenient equivalent of the function(v) { return v; } declaration, which is very useful for working with Java 8 APIs.

The JDK ships with a dedicated launcher for JSR 223 programs called jrunscript. If it is called without parameters, it goes straight to the Nashorn engine. That's yet another way to invoke Nashorn, which is especially useful for one-liners. In Listing 2, Nashorn communicates with the Java Management Extensions (JMX) operating system's MXBean to pull information about the total amount of physical memory available to the JVM.

 
$ jrunscript.exe -e "print(java.lang.management.ManagementFactory.getOperatingSystemMXBean().getTotalPhysicalMemorySize())"
    12566515712

Listing 2. Using the jrunscript tool to invoke inline Nashorn code on operating system MXBean.

Nashorn from Java

The key element of the Java Scripting API is the ScriptEngine object, which binds engines to the JVM and allows full interactivity. Objects can be sent to and from the engine with simple get and put methods. Functions are accessed with the invoke family of methods, and entire scripts may be sent to the engine with the combination of the eval function and the FileReader object.

The script engine manager facility is very straightforward to instantiate, as illustrated in Listing 3. Java can feed ScriptEngine with evaluated code or this can be done through the put method. What comes from the engine is the ScriptObjectMirror object, which requires some processing to get cast to Java types, unless primitive types such as int or String are returned.

In the example in Listing 3, the intArray JavaScript variable needs casting to an integer array to initialize the intArray variable. Similarly, the Java string array stringList is put in, and then cast back to the string array and converted to a list of strings. The key difference in how ScriptEngine objects are retrieved is that Nashorn objects must be retrieved as a ScriptObjectMirror wrapper, whereas native Java objects that were put into the engine can be pulled back with no interim objects. If the returning object shows up as [Object object] through the toString() method, this means it needs casting and conversion to be accessible with Java.

 
/* NashornDemo.java */
    import javax.script.ScriptEngine;
    import javax.script.ScriptEngineManager;
    import javax.script.ScriptException;
    import java.util.Arrays;
    import java.util.List;
    import jdk.nashorn.api.scripting.ScriptObjectMirror;
    
    public class NashornDemo {
        public static void main(String[] args) throws ScriptException {
            ScriptEngineManager manager = new ScriptEngineManager();
            ScriptEngine engine = manager.getEngineByName("nashorn");
    
            engine.eval("var intArray = [1, 2, 3];");
            ScriptObjectMirror mirror = (ScriptObjectMirror) engine.get("intArray");
            Integer[] intArray = mirror.to(Integer[].class);
            System.out.println(intArray.length);
    
            engine.put("stringList", new String[]{"jeden", "dwa", "trzy"});
            List<String> stringList = Arrays.asList((String[]) engine.get("stringList"));
            System.out.println(stringList);
        }
    }
    
    $ javac NashornDemo.java && java NashornDemo
    3
    [jeden, dwa, trzy]
    4534

Listing 3. Exchanging simple objects over the Nashorn ScriptEngine.

A common use case for deploying ScriptEngine in Java applications is to provide extensibility without the need for using the Service Provider Interface (SPI), Open Source Gateway Initiative (OSGi), or other plugin frameworks. The ability to allow custom code inside an application can greatly enhance adoption and work around steep programming learning curves for less advanced users.

Nashorn can also allow third-party JavaScript modules to be used directly in Java code. Consider the sample program in Listing 4, which executes a function on the popular Moment.js library through the script engine interface. The eval method is used on the FileReader object to load the JavaScript library and return a string value. There is no need for putting ScriptObjectMirror in between because primitives such as String or int are recognized as Java primitives.

 
import javax.script.ScriptEngine;
    import javax.script.ScriptEngineManager;
    import java.io.FileReader;
    
    public class NashornMoment {
        public static void main(String[] args) throws Exception {
            ScriptEngineManager manager = new ScriptEngineManager();
            ScriptEngine engine = manager.getEngineByExtension("js");
            engine.eval(new FileReader("moment.js"));
            System.out.println(engine.eval("moment().format('LLL')"));
        }
    }

Listing 4. Invoking a method on an external JavaScript module.

All engines created with ScriptEngineManager with the getEngine family of functions do not accept extra arguments. That shortcoming can be worked around in Nashorn by supplying the JVM with the system property -Dnashorn.args. In Java code, ScriptEngineManager can be replaced with NashornScriptEngineFactory to be able to provide custom properties such as ECMAScript 6 (ES6) compatibility or a custom class loader. If ES6 compatibility was omitted in Listing 5, the script would throw ScriptException saying <eval>:1:0 Expected an operand but found const. To learn all the ways parameters can be provided to the getScriptEngine method, refer to the JDK API reference for the NashornScriptEngineFactory class. An equivalent way of invoking this engine with just ScriptEngine would be by providing -Dnashorn.args=--language=es6 to the list of JVM arguments.

 
import javax.script.ScriptEngine;
    import javax.script.ScriptException;
    import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
    
    public class NashornFactory {
        public static void main(String[] args) throws ScriptException {
            NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
            ScriptEngine engine = factory.getScriptEngine("--language=es6");
            engine.eval("const a = 100");
        }
    }

Listing 5. Nashorn script engine factory used to create an engine with extra attributes.

Not everything has to be pulled back to Java for processing. JavaScript's specification contains a built-in JSON Object that is used for encoding and decoding JSON format. Nashorn can effectively work around a lack of this functionality in JDK 8 and earlier versions. The example in Listing 6 illustrates how Nashorn can leverage web content fetched with Java to then extract required weather information with JSON. (The OpenWeatherMap API requires the appid key which was sanitized from the source code.)

 
import javax.script.ScriptEngine;
    import javax.script.ScriptEngineManager;
    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.net.URL;
    import java.util.stream.Collectors;
    
    public class NashornWeather {
    
        public static void main(String[] args) throws Exception {
            String response;
            String url = "http://api.openweathermap.org/data/2.5/weather?q=Krakow,PL";
            try (
                InputStreamReader is = new InputStreamReader(new URL(url).openStream());
                BufferedReader reader = new BufferedReader(is)) {
                response = reader.lines().collect(Collectors.joining());
            }
            ScriptEngineManager manager = new ScriptEngineManager();
            ScriptEngine engine = manager.getEngineByName("nashorn");
            engine.put("response", response);
            System.out.println(engine.eval("JSON.parse(response).weather[0].description"));
        }
    }

Listing 6. Using Nashorn for consuming a JSON REST API.

Script engine execution scopes can be isolated from each other with contexts and bindings. Because new code loaded into the Nashorn interpreter could clutter global scope, scripts that are intended for execution in isolation can be launched through the loadWithNewGlobal function. The global scope ENGINE_SCOPE is equivalent to ECMAScript's global, which is accessed through top-level use of the this keyword. For specific use of ScriptContext, check the Nashorn jsr223 engine notes on the OpenJDK wiki.

Java from Nashorn

Nashorn can seamlessly work with Java objects and most of the time requires no extra wrappers. By default, major packages such as com, edu, java, javafx, javax, and org are made available to the global scope, so there's no need to "import" them. Other packages could be referenced with the Packages global object or a recommended way is with the Java.type global method. Along these lines, java.util.HashMap, Packages.java.util.HashMap, and Java.type("java.util.HashMap") are three equivalent ways of accessing the Java hash map from Nashorn.

Because Nashorn is dynamic whereas Java is statically typed, the method signature is determined at runtime. An example of the commonly used overloaded method println is shown in Listing 7. Another interesting addition was introduction of JEP 196: Nashorn Optimistic Typing, which still requires the explicit parameter -ot in JDK 8 but becomes the default behavior in JDK 9. This optimization of the guesswork involved with assumptions about referenced Java types may slightly degrade the JVM startup time but it improves performance greatly during runtime, with warmed-up Nashorn code nearing half the speed of the JavaScript V8 engine.

 
jjs> java.lang.System.out.println
    [jdk.internal.dynalink.beans.OverloadedDynamicMethod
     void java.io.PrintStream.println()
     void java.io.PrintStream.println(boolean)
     void java.io.PrintStream.println(char)
     void java.io.PrintStream.println(char[])
     void java.io.PrintStream.println(double)
     void java.io.PrintStream.println(float)
     void java.io.PrintStream.println(int)
     void java.io.PrintStream.println(long)
     void java.io.PrintStream.println(Object)
     void java.io.PrintStream.println(String)
    ]

Listing 7. Overloaded Java method of a standard output stream as seen by Nashorn.

Although Nashorn doesn't ship with built-in introspection mechanisms by default, such tools can be easily implemented. Refer to Listing 8 for a Nashorn port of Python's dir() function, a well-known method for examining objects' attributes and methods. Exact Python behavior was not retrofitted entirely, because the dynamic nature of class loaders prohibits enumeration of packages without unpacking all classpath entries. In the example, Nashorn's concise syntax is made even cleaner through the use of Java 8 streams and lambdas.

 
/* dir.js */
    var dir = function(obj, search) {
        with(new JavaImporter(java.util, java.util.stream)) {
            var members = new HashMap();
            var signature = function(method) {
                var s = method.getReturnType().getSimpleName() 
                    + " " + method.getName() + "(";
                s = s + Arrays.stream(method.getParameters()).map(function(p) {
                    return p.getType().getSimpleName() + " " + p.getName();
                }).collect(Collectors.joining(", ")) + ")";
                return s;
            }
            for (var c = obj.getClass(); c != null; c = c.getSuperclass()) {
                Arrays.stream(c.getFields()).forEach(function(f) {
                    members.putIfAbsent(f.getName(), f.getName());
                });
                Arrays.stream(c.getMethods()).forEach(function(m) {
                    members.putIfAbsent(m.getName(), signature(m));
                });
            }
            return members.entrySet().stream()
                .sorted(function(e1, e2) e1.getKey().compareTo(e2.getKey()))
                .map(function(e) e.getValue())
                .filter(function(p) !search || p.contains(search))
                .collect(Collectors.toList());
        }
    }
    
    jjs> dir(new java.util.HashMap())
    
    [void clear(), Object clone(), Object compute(Object arg0, BiFunction arg1), Object 
    computeIfAbsent(Object arg0, Function arg1), Object computeIfPresent(Object arg0, 
    BiFunction arg1), boolean containsKey(Object arg0), boolean containsValue(Object arg0),
    Set entrySet(), boolean equals(Object arg0), void forEach(BiConsumer arg0), Object
    get(Object arg0), Class getClass(), Object getOrDefault(Object arg0, Object arg1), int
    hashCode(), boolean isEmpty(), Set keySet(), Object merge(Object arg0, Object arg1, 
    BiFunction arg2), void notify(), void notifyAll(), Object put(Object arg0, Object arg1),
    void putAll(Map arg0), Object putIfAbsent(Object arg0, Object arg1), Object 
    remove(Object arg0), boolean replace(Object arg0, Object arg1, Object arg2), void 
    replaceAll(BiFunction arg0), int size(), String toString(), Collection values(), void wait()]

Listing 8. Using the Nashorn helper dir() function to list object attributes and methods.

The example shown in Listing 8 also exhibits the JavaScript with clause and the JavaImporter object that come from Rhino, and have both are two new features that were ported from Mozilla Rhino to Nashorn for backwards compatibility. In every situation where mixing with global scope is unwanted, the with clause can shield the inner scope, whereas the JavaImporter allows you to quickly expose package contents. The JavaImporter doesn't always have to rely on the with clause; a standalone importer can be convenient too.

Nashorn ships with a number of language extensions that aim to make platform interoperability even more seamless. One of them is the Java.from function for converting Java arrays to native JavaScript arrays. The reverse function Java.to converts an existing JavaScript object to a requested Java type, in accordance with the ECMAScript specification. An example of their use is shown in Listing 9.

 
jjs> var home = new java.io.File(".");
    
    jjs> home.listFiles()
    [Ljava.io.File;@2d1ef81a
    
    jjs> Java.from(home.listFiles())
    .\demo.js,.\dir.js,.\es6.js,.\jvm-npm.js,.\jython-standalone-2.7.1.jar, 
    .\listEngines.js,.\module.js,.\mustache.js,.\qunit-2.4.0.js,.\underscore.js,.\unittest.js

Listing 9. Demonstration of Nashorn array conversion.

Nashorn is fully capable of implementing Java's abstract classes and interfaces using the new operator and non-abstract classes through the Java.extend method. Accordingly, Java superclasses can be references from Nashorn code through the Java.super method. New methods cannot be added through overloading and Nashorn can only implement, extend, and interact with existing methods in this scenario. To add new class attributes at runtime, a special Object.bindProperties method can be used. An example of extending the Callable interface is shown in Listing 10, where the Java Executor framework works seamlessly with Nashorn. Because of dynamic typing used against the submit method, the implementation requires the exact method signature executor["submit(java.util.concurrent.Callable)"].

 
var Callable = Java.type("java.util.concurrent.Callable");
    var Worker = Java.extend(Callable, {
        call: function() {
            return Math.random();
        }
    });
    
    var executor = java.util.concurrent.Executors.newFixedThreadPool(4);
    var random, futures = [];
    try {
        for (var i = 1; i <= 10; i++) {
            futures.push(executor["submit(java.util.concurrent.Callable)"](new Worker()));
        }
    
        var random = futures.map(function(f) f.get());
    } finally {
        executor.awaitTermination(1, java.util.concurrent.TimeUnit.SECONDS);
        executor.shutdownNow();
    }
    
    print(random);

Listing 10. Nashorn implementing Runnable interface.

Extensions

Besides full ECMAScript 5.1 support and selected support for ES6, Nashorn features a number of language extensions that can further enrich development flow, many of them stemming from Mozilla Rhino. Notable enhancements include multiple try-catch statements, for each loops, anonymous functions, and typed arrays—created with Java.type("int[]") and various methods on the built-in Object object. A full list of the extensions can be found at the OpenJDK Nashorn extensions page, a must-have resource for getting started with the language.

Figure 1. The Nashorn extensions page

Summary

Nashorn fills the void between Java and JavaScript that was created when Mozilla developed and rebranded the language. The level of interoperability between Nashorn and Java demonstrates a long-term commitment in sustaining and further developing the language, with new features shipping in even minor JDK releases. Being part of Oracle's JDK and OpenJDK, Nashorn is ready to use on most systems or easily deployed with native package managers.

About the Author

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.