3 Using Java from Scripts

This section describes how to access Java classes and interfaces from scripts.

Note:

The Nashorn engine is deprecated in JDK 11 in preparation for removal in a future release.

The code snippets are written in JavaScript, but you can use any scripting language compliant with JSR 223. Examples can be used as script files, or can be run in an interactive shell one expression at a time. The syntax for accessing fields and methods of objects is the same in JavaScript as it is in Java.

Accessing Java Classes

To access primitive and reference Java types from JavaScript, call the Java.type() function, which returns a type object that corresponds to the full name of the class passed in as a string. The following example shows you how to get various type objects:

var ArrayList = Java.type("java.util.ArrayList");
var intType = Java.type("int");
var StringArrayType = Java.type("java.lang.String[]");
var int2DArrayType = Java.type("int[][]");

The type object returned by the Java.type() function can be used in JavaScript code similar to how a class name is used in Java. For example, you can can use it to instantiate new objects as follows:

var anArrayList = new Java.type("java.util.ArrayList");

Java type objects can be used to instantiate new Java objects. The following example shows you how to instantiate new objects using the default constructor and by passing arguments to another constructor:

var ArrayList = Java.type("java.util.ArrayList");
var defaultSizeArrayList = new ArrayList;
var customSizeArrayList = new ArrayList(16);

You can use the type object returned by the Java.type() function to access static fields and methods as follows:

var File = Java.type("java.io.File");
File.createTempFile("nashorn", ".tmp");

To access a static inner class, use the dollar sign ($) in the argument passed to the Java.type() method. The following example shows how to return the type object of the Float inner class in java.awt.geom.Arc2D:

var Float = Java.type("java.awt.geom.Arc2D$Float");

If you already have the outer class type object, then you can access the inner class as a property of the outer class as follows:

var Arc2D = Java.type("java.awt.geom.Arc2D")
var Float = Arc2D.Float

In case of a nonstatic inner class, you must pass an instance of the outer class as the first argument to the constructor.

Although a type object in JavaScript is used similar to the Java class, it is distinct from the java.lang.Class object, which is returned by the getClass() method. You can obtain one from the other using the class and static properties. The following example shows this distinction:

var ArrayList = Java.type("java.util.ArrayList");
var a = new ArrayList;

// All of the following are true:
print("Type acts as target of instanceof: " + (a instanceof ArrayList));
print("Class doesn't act as target of instanceof: " + !(a instanceof a.getClass()));
print("Type is not the same as instance's getClass(): " + (a.getClass() !== ArrayList));
print("Type's `class` property is the same as instance's getClass(): " + (a.getClass() === ArrayList.class));
print("Type is the same as the `static` property of the instance's getClass(): " + (a.getClass().static === ArrayList));

Syntactically and semantically, this distinction between compile-time class expressions and runtime class objects makes JavaScript similar to Java code. However, there is no equivalent of the static property for a Class object in Java, because compile-time class expressions are never expressed as objects.

Importing Java Packages and Classes

To access Java classes by their simple names, you can use the importPackage() and importClass() functions to import Java packages and classes. These functions are built into the compatibility script (mozilla_compat.js).

Note:

Avoid importing packages in production application code. Instead, import classes with the importClass() function and save them as global variables. Limit the use of the importPackage() function to simple prototyping with Java classes.

The following example shows you how to use the importPackage() and importClass() functions:

// Load compatibility script
load("nashorn:mozilla_compat.js");
// Import the java.awt package
importPackage(java.awt);
// Import the java.awt.Frame class
importClass(java.awt.Frame);
// Create a new Frame object
var frame = new java.awt.Frame("hello");
// Call the setVisible() method
frame.setVisible(true);
// Access a JavaBean property
print(frame.title);

You can access Java packages using the Packages global variable (for example, Packages.java.util.Vector or Packages.javax.swing.JFrame), but standard Java SE packages have shortcuts (java for Packages.java, javax for Packages.javax, and org for Packages.org).

The java.lang package is not imported by default, because its classes would conflict with Object, Boolean, Math, and other built-in JavaScript objects. Furthermore, importing any Java package or class can lead to conflicts with the global variable scope in JavaScript. To avoid this, define a JavaImporter object and use the with statement to limit the scope of the imported Java packages and classes, as shown in the following example:

// Create a JavaImporter object with specified packages and classes to import
var Gui = new JavaImporter(java.awt, javax.swing);

// Pass the JavaImporter object to the "with" statement and access the classes
// from the imported packages by their simple names within the statement's body
with (Gui) {
    var awtframe = new Frame("AWT Frame");
    var jframe = new JFrame("Swing JFrame");
};

Using Java Arrays

To create a Java array object, you first have to get the Java array type object, and then instantiate it. The syntax for accessing array elements and the length property in JavaScript is the same as in Java, as shown in the following example:

var StringArray = Java.type("java.lang.String[]");
var a = new StringArray(5);

// Set the value of the first element
a[0] = "Scripting is great!";
// Print the length of the array
print(a.length);
// Print the value of the first element
print(a[0]);

Given a JavaScript array, you can convert it to a Java array using the Java.to() method. You must pass the JavaScript array variable to this method and the type of array to be returned, either as a string or a type object. You can also omit the array type argument to return an Object[] array. Conversion is performed according to the ECMAScript conversion rules. The following example shows you how to convert a JavaScript array to a Java array using various Java.to() method arguments:

// Create a JavaScript array
var anArray = [1, "13", false];

// Convert the JavaScript array to a Java int[] array
var javaIntArray = Java.to(anArray, "int[]");
print(javaIntArray[0]); // prints the number 1
print(javaIntArray[1]); // prints the number 13
print(javaIntArray[2]); // prints the number 0

// Convert the JavaScript array to a Java String[] array
var javaStringArray = Java.to(anArray, Java.type("java.lang.String[]"));
print(javaStringArray[0]); // prints the string "1"
print(javaStringArray[1]); // prints the string "13"
print(javaStringArray[2]); // prints the string "false"

// Convert the JavaScript array to a Java Object[] array
var javaObjectArray = Java.to(anArray);
print(javaObjectArray[0]); // prints the number 1
print(javaObjectArray[1]); // prints the string "13"
print(javaObjectArray[2]); // prints the boolean value "false"

Given a Java array, you can convert it to a JavaScript array using the Java.from() method. The following example shows you how to convert a Java array that contains a list of files in the current directory to a JavaScript array with the same contents:

// Get the Java File type object
var File = Java.type("java.io.File");
// Create a Java array of File objects
var listCurDir = new File(".").listFiles();
// Convert the Java array to a JavaScript array
var jsList = Java.from(listCurDir);
// Print the JavaScript array
print(jsList);

Note:

In most cases, you can use the Java array in scripts without converting the Java array to a JavaScript array.

Implementing Java Interfaces

The syntax for implementing a Java interface in JavaScript is similar to how anonymous classes are declared in Java. You instantiate an interface and implement its methods (as JavaScript functions) in the same expression. The following example shows you how to implement the Runnable interface:

// Create an object that implements the Runnable interface by implementing
// the run() method as a JavaScript function
var r  = new java.lang.Runnable() {
    run: function() {
        print("running...\n");
    }
};

// The r variable can be passed to Java methods that expect an object implementing
// the java.lang.Runnable interface
var th = new java.lang.Thread(r);
th.start();
th.join();

If a method expects an object that implements an interface with only one method, you can pass a script function to this method instead of the object. For instance, in the previous example, the Thread() constructor expects an object that implements the Runnable interface, which defines only one method. You can take advantage of automatic conversion and pass a script function to the Thread() constructor instead of the object. The following example shows you how you can create a Thread object without implementing the Runnable interface:

// Define a JavaScript function
function func() {
    print("I am func!");
};

// Pass the JavaScript function instead of an object that implements
// the java.lang.Runnable interface
var th = new java.lang.Thread(func);
th.start();
th.join();

You can implement multiple interfaces in a subclass by passing the relevant type objects to the Java.extend() function; see Extending Concrete Java Classes.

Extending Abstract Java Classes

You can instantiate an anonymous subclass of an abstract Java class by passing to its constructor a JavaScript object with properties whose values are functions that implement the abstract methods. If a method is overloaded, then the JavaScript function will provide implementations for all variants of the method. The following example shows you how to instantiate a subclass of the abstract TimerTask class:

var TimerTask =  Java.type("java.util.TimerTask");
var task = new TimerTask({ run: function() { print("Hello World!") } });

Instead of invoking the constructor and passing an argument to it, you can provide the argument directly after the new expression. The following example shows you how this syntax (similar to Java anonymous inner class definition) can simplify the second line in the previous example:

var task = new TimerTask {
    run: function() {
        print("Hello World!")
    }
};

If the abstract class is a functional interface (it has a single abstract method), then instead of passing a JavaScript object to the constructor, you can pass the function that implements the method. The following example shows how you can simplify the syntax when using a functional interface:

var task = new TimerTask(function() { print("Hello World!") });

Whichever syntax you choose, if you need to invoke a constructor with arguments, you can specify the arguments before the implementation object or function.

If you want to invoke a Java method that takes a functional interface as the argument, you can pass a JavaScript function to the method. Nashorn will instantiate a subclass of the expected class and use the function to implement its only abstract method. The following example shows you how to invoke the Timer.schedule() method, which expects a TimerTask object as the argument:

var Timer = Java.type("java.util.Timer");
Timer.schedule(function() { print("Hello World!") });

Note:

The previous syntax assumes that the expected functional interface is either an interface or it has a default constructor, which is used by Nashorn to instantiate a subclass of the expected class. It is not possible to use a non-default constructor.

Extending Concrete Java Classes

To avoid ambiguity, the syntax for extending abstract classes is not allowed for concrete classes. Because a concrete class can be instantiated, such syntax may be interpreted as an attempt to create a new instance of the class and pass to it an object of the type expected by the constructor (in case when the expected object type is an interface). As an illustration of this, consider the following example:

var t = new java.lang.Thread({ run: function() { print("Thread running!") } });

This code can be interpreted both as extending the Thread class with the specified implementation of the run() method, and the instantiation of the Thread class by passing to its constructor an object that implenents the Runnable interface. See Implementing Java Interfaces.

To extend a concrete Java class, pass its type object to the Java.extend() function that returns a type object of the subclass. Then, use the type object of the subclass as a JavaScript-to-Java adapter to create instances of the subclass with the specified method implementations. The following example shows you how to extend the Thread class with the specified implementation of the run() method:

var Thread = Java.type("java.lang.Thread");
var threadExtender = Java.extend(Thread);
var t = new threadExtender() {
    run: function() { print("Thread running!") }};

The Java.extend() function can take a list of multiple type objects. You can specify no more than one type object of a Java class, and as many type objects of Java interfaces as you want. The returned type object extends the specified class (or java.lang.Object if no class is specified) and implements all interfaces. The class type object does not have to be first in the list.

Accessing Methods of a Superclass

To access methods in the superclass, you can use the Java.super() function. Example 3-1 shows you how to extend the java.lang.Exception class and access the methods in the superclass.

If you run the code in Example 3-1, the following will be printed to standard output:

jdk.nashorn.javaadapters.java.lang.Exception: MY EXCEPTION MESSAGE

Example 3-1 Accessing Methods of a Supreclass (super.js)

var Exception = Java.type("java.lang.Exception");
var ExceptionAdapter = Java.extend(Exception);

var exception = new ExceptionAdapter("My Exception Message") {
    getMessage: function() {
        var _super_ = Java.super(exception);
        return _super_.getMessage().toUpperCase();
    }
}

try {
    throw exception;
} catch (ex) {
    print(exception);
}

Binding Implementations to Classes

The previous sections described how to extend Java classes and implement interfaces using an extra JavaScript object parameter in the constructor that specifies the implementation. The implementation is therefore bound to the actual instance created with new, and not to the whole class. This has some advantages, for example, in the memory footprint of the runtime, because Nashorn can create a single universal adapter for every combination of types being implemented. However, the following example shows that different instances have the same Java class regardless of them having different JavaScript implementation objects:

var Runnable = java.lang.Runnable;
var r1 = new Runnable(function() { print("I'm runnable 1!") });
var r2 = new Runnable(function() { print("I'm runnable 2!") });
r1.run();
r2.run();
print("We share the same class: " + (r1.class === r2.class));

The previous example prints the following:

I'm runnable 1!
I'm runnable 2!
We share the same class: true

If you want to pass the class for instantiation to an external API (for example, when using the JavaFX framework, the Application class is passed to the JavaFX API, which instantiates it), you must extend a Java class or implement an interface with the implementation bound to the class, rather than to its instances. You can bind the implementation to the class by passing a JavaScript object with the implementation as the last argument to the Java.extend() function. This creates a class with the same constructors as the original class, because they do not need an extra implementation object parameter. The following example shows you how to bind implementations to the class, and demonstrates that in this case the implementation classes for different invocations are different:

var RunnableImpl1 = Java.extend(java.lang.Runnable, function() { print("I'm runnable 1!") });
var RunnableImpl2 = Java.extend(java.lang.Runnable, function() { print("I'm runnable 2!") });
var r1 = new RunnableImpl1();var r2 = new RunnableImpl2();
r1.run();
r2.run();
print("We share the same class: " + (r1.class === r2.class));

The previous example prints the following:

I'm runnable 1!
I'm runnable 2!
We share the same class: false

Moving the implementation objects from the constructor invocations to the invocations of the Java.extend() functions eliminates the need for an extra argument in the constructor invocations. Every invocation of the Java.extend() function with a class-specific implementation object produces a new Java adapter class.The adapter classes with class-bound implementations can still take an additional constructor argument to further override the behavior for certain instances. Thus, you can combine the two approaches: you can provide part of the implementation in a class-based JavaScript implementation object passed to the Java.extend() function, and provide implementations for instances in objects passed to the constructor. A function defined by the object passed to the constructer overrides the function defined by the class-bound object. The following example shows you how to override the function defined in the class-bound object with a function passed to the constructor:

var RunnableImpl = Java.extend(java.lang.Runnable, function() { print("I'm runnable 1!") });
var r1 = new RunnableImpl();
var r2 = new RunnableImpl(function() { print("I'm runnable 2!") });
r1.run();
r2.run();
print("We share the same class: " + (r1.class === r2.class));

The previous example prints the following:

I'm runnable 1!
I'm runnable 2!
We share the same class: true

Selecting Method Overload Variant

Java methods can be overloaded by argument types. The Java Compiler (javac) selects the correct method variant during compilation. Overload resolution for Java methods called from Nashorn is performed when the method is invoked. The correct variant is selected automatically based on the argument types. However, if you run into genuine ambiguity with actual argument types, you can specify a particular overload variant explicitly. This may also improve performance, because the Nashorn engine will not need to perform overload resolution during invocation.

Overload variants are exposed as special properties. You can refer to them in the form of strings that contain the name of the method followed by the argument types within parantheses. The following example shows how to invoke the variant of the System.out.println() method that expects an Object class as the argument, and pass "hello" to it:

var out = java.lang.System.out;
out["println(Object)"]("hello");

In the previous example, the unqualified class name (Object) is sufficient, because it uniquely identifies the correct signature. The only case when you must use the fully qualified class names in the signature is when two overload variants use different parameter types with identical unqualified names (this is possible if parameter types with the same name are from different packages).

Mapping Data Types

Most conversions between Java and JavaScript work as you expect. Previous sections described some of the less evident data type mappings between Java and JavaScript. Arays are automatically converted to Java array types such as java.util.List, java.util.Collection, java.util.Queue, and java.util.Deque. JavaScript functions are automatically converted to SAM types when they are passed as parameters to Java methods. Every JavaScript object implements the java.util.Map interface to enable APIs to receive maps directly. When numbers are passed to a Java API, they are converted to the expected target numeric type, either boxed or primitive. However, if the target type is less specific (for example, Number), you can only expect them to be of type Number, and must test specifically for whether the type is a boxed Double, Integer, Long, and so on. The number can be any boxed type due to internal optimizations. Also, you can pass any JavaScript value to a Java API expecting either a boxed or primitive number, because the ToNumber conversion algorithm defined by the JavaScript specification will be applied to the value. If a Java method expects a String or a Boolean object, the values will be converted using all conversions allowed by the ToString and ToBoolean conversions defined by the JavaScript specification. Nashorn ensures that internal JavaScript strings are converted to java.lang.String when exposed externally.

Passing JSON Objects to Java

The function Java.asJSONCompatible(obj) accepts a script object and returns an object that is compatible with the expectations of most Java JSON libraries: it exposes all arrays as List objects (rather than Map objects) and all other objects as Map objects.

Mapping Data Types mentions that every JavaScript object, when exposed to Java APIs, implements the java.util.Map interface. This is true even for JavaScript arrays. However, this behavior is often not desired or expected when Java code expects JSON-parsed objects. Java libraries that manipulate JSON-parsed objects usually expect arrays to expose the java.util.List interface instead. If you need to expose your JavaScript objects in such a manner that arrays are exposed as lists and not maps, use the Java.asJSONCompatible(obj) function, where obj is the root of your JSON object tree.

Example 3-2 Example of Java.asJSONCompatible() Function

The following example calls the functions JSON.parse() and Java.asJSONCompatible() on the same JSON object. The function JSON.parse() parses the array [2,4,5] as a map while the function Java.asJSONCompatible() parses the same array as a list.

import javax.script.*;
import java.util.*;

public class JSONTest {
  public static void main(String[] args) throws Exception {
    ScriptEngineManager m = new ScriptEngineManager();
    ScriptEngine e = m.getEngineByName("nashorn");

    Object obj1 = e.eval(
      "JSON.parse('{ \"x\": 343, \"y\": \"hello\", \"z\": [2,4,5] }');");
    Map<String, Object> map1 = (Map<String, Object>)obj1;
    System.out.println(map1.get("x"));
    System.out.println(map1.get("y"));
    System.out.println(map1.get("z"));
    Map<Object, Object> array1 = (Map<Object, Object>)map1.get("z");
    array1.forEach((a, b) -> System.out.println("z[" + a + "] = " + b));

    System.out.println();
    
    Object obj2 = e.eval(
      "Java.asJSONCompatible({ \"x\": 343, \"y\": \"hello\", \"z\": [2,4,5] })");
    Map<String, Object> map2 = (Map<String, Object>)obj2;
    System.out.println(map2.get("x"));
    System.out.println(map2.get("y"));
    System.out.println(map2.get("z"));
    List<Object> array2 = (List<Object>)map2.get("z");
    array2.forEach(a -> System.out.println(a));
  }
}

This example prints the following:

343
hello
[object Array]
z[0] = 2
z[1] = 4
z[2] = 5

343
hello
[2, 4, 5]
2
4
5