Java LanguageNashorn JavaScript engine

Introduction

Nashorn is a JavaScript engine developed in Java by Oracle, and has been released with Java 8. Nashorn allows embedding Javascript in Java applications via JSR-223 and allows to develop standalone Javascript applications, and it provides better runtime performance and better compliance with the ECMA normalized Javascript specification.

Syntax

  • ScriptEngineManager // Provides a discovery and installation mechanism for ScriptEngine classes; uses a SPI (Service Provider Interface)
  • ScriptEngineManager.ScriptEngineManager() // Recommended constructor
  • ScriptEngine // Provides the interface to the scripting language
  • ScriptEngine ScriptEngineManager.getEngineByName(String shortName) // Factory method for the given implementation
  • Object ScriptEngine.eval(String script) // Executes the specified script
  • Object ScriptEngine.eval(Reader reader) // Loads and then executes a script from the specified source
  • ScriptContext ScriptEngine.getContext() // Returns the default bindings, readers and writers provider
  • void ScriptContext.setWriter(Writer writer) // Sets the destination to send script output to

Remarks

Nashorn is a JavaScript engine written in Java and included in Java 8. Everything you need is bundled in the javax.script package.

Note that the ScriptEngineManager provides a generic API allowing you to obtain script engines for various scripting languages (i.e. not only Nashorn, not only JavaScript).

Set global variables

// Obtain an instance of JavaScript engine
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("nashorn");

// Define a global variable
engine.put("textToPrint", "Data defined in Java.");

// Print the global variable
try {
    engine.eval("print(textToPrint);");
} catch (ScriptException ex) {
    ex.printStackTrace();
}

// Outcome:
// 'Data defined in Java.' printed on standard output

Hello Nashorn

// Obtain an instance of JavaScript engine
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("nashorn");

// Execute an hardcoded script
try {
    engine.eval("print('Hello Nashorn!');");
} catch (ScriptException ex) {
    // This is the generic Exception subclass for the Scripting API
    ex.printStackTrace();
}

// Outcome:
// 'Hello Nashorn!' printed on standard output

Execute JavaScript file

// Required imports
import javax.script.ScriptEngineManager;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import java.io.FileReader;
import java.io.FileNotFoundException;

// Obtain an instance of the JavaScript engine
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("nashorn");

// Load and execute a script from the file 'demo.js'
try {
    engine.eval(new FileReader("demo.js"));
} catch (FileNotFoundException ex) {
    ex.printStackTrace();
} catch (ScriptException ex) {
    // This is the generic Exception subclass for the Scripting API
    ex.printStackTrace();
}

// Outcome:
// 'Script from file!' printed on standard output

demo.js:

print('Script from file!');

Intercept script output

// Obtain an instance of JavaScript engine
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("nashorn");

// Setup a custom writer
StringWriter stringWriter = new StringWriter();
// Modify the engine context so that the custom writer is now the default
// output writer of the engine
engine.getContext().setWriter(stringWriter);

// Execute some script
try {
    engine.eval("print('Redirected text!');");
} catch (ScriptException ex) {
    ex.printStackTrace();
}

// Outcome:
// Nothing printed on standard output, but
// stringWriter.toString() contains 'Redirected text!'

Evaluate Arithmetic Strings

// Obtain an instance of JavaScript engine
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");

//String to be evaluated
String str = "3+2*4+5";
//Value after doing Arithmetic operation with operator precedence will be 16

//Printing the value
try {
    System.out.println(engine.eval(str));
} catch (ScriptException ex) {
    ex.printStackTrace();
}

//Outcome:
//Value of the string after arithmetic evaluation is printed on standard output.
//In this case '16.0' will be printed on standard output.

Usage of Java objects in JavaScript in Nashorn

It's possible to pass Java objects to Nashorn engine to be processed in Java code. At the same time, there are some JavaScript (and Nashorn) specific constructions, and it's not always clear how they work with java objects.

Below there is a table which describes behaviour of native Java objects inside JavaScript constructions.

Tested constructions:

  1. Expression in if clause. In JS expression in if clause doesn't have to be boolean unlike Java. It's evaluated as false for so called falsy values (null, undefined, 0, empty strings etc)
  2. for each statement Nashorn has a special kind of loop - for each - which can iterate over different JS and Java object.
  3. Getting object size. In JS objects have a property length, which returns size of an array or a string.

Results:

TypeIffor each.length
Java nullfalseNo iterationsException
Java empty stringfalseNo iterations0
Java stringtrueIterates over string charactersLength of the string
Java Integer/Longvalue != 0No iterationsundefined
Java ArrayListtrueIterates over elementsLength of the list
Java HashMaptrueIterates over valuesnull
Java HashSettrueIterates over itemsundefined

Recommendatons:

  • It's advisable to use if (some_string) to check if a string is not null and not empty
  • for each can be safely used to iterate over any collection, and it doesn't raise exceptions if the collection is not iterable, null or undefined
  • Before getting length of an object it must be checked for null or undefined (the same is true for any attempt of calling a method or getting a property of Java object)

Implementing an interface from script

import java.io.FileReader;
import java.io.IOException;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class InterfaceImplementationExample {
    public static interface Pet {
        public void eat();
    }

    public static void main(String[] args) throws IOException {
        // Obtain an instance of JavaScript engine
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("nashorn");

        try {
            //evaluate a script
            /* pet.js */
            /*
                var Pet = Java.type("InterfaceImplementationExample.Pet");
                
                new Pet() {
                    eat: function() { print("eat"); }
                }            
            */
            
            Pet pet = (Pet) engine.eval(new FileReader("pet.js"));
            
            pet.eat();
        } catch (ScriptException ex) {
            ex.printStackTrace();
        }

        // Outcome:
        // 'eat' printed on standard output
    }
}

Set and get global variables

// Obtain an instance of JavaScript engine
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("nashorn");

try {
    // Set value in the global name space of the engine
    engine.put("name","Nashorn");
    // Execute an hardcoded script
    engine.eval("var value='Hello '+name+'!';");
    // Get value
    String value=(String)engine.get("value");
    System.out.println(value);
} catch (ScriptException ex) {
    // This is the generic Exception subclass for the Scripting API
    ex.printStackTrace();
}

// Outcome:
// 'Hello Nashorn!' printed on standard output