2 The Nashorn Java API
Note:
The Nashorn engine, thejjs
tool, and the modules jdk.scripting.nashorn and jdk.scripting.nashorn.shell are deprecated in JDK 11 in preparation for removal in a future release.
This section contains examples of script statements interpreted by the Nashorn engine in interactive language shell mode. This interactive shell is started by running the jjs
command without any scripts passed to it. This is useful for trying things out, but the main purpose of the Nashorn Java API is to write Java applications as scripts that can be interpreted by the Nashorn engine.
Accessing Java Classes
There are two approaches to access packages and classes using Nashorn: the traditional approach is to use the Packages
global object, and the recommended approach is to use the Java
global object. This section describes both approaches.
The predefined top-level Packages
object enables you to access Java packages and classes using their fully qualified names, as if they are properties of the Packages
object. The following example shows how you can access the MyPackage
package and its MyClass
class if MyPackage.jar
is in your class path:
jjs> Packages.MyPackage
[JavaPackage MyPackage]
jjs> Packages.MyPackage.MyClass
[JavaClass MyPackage.MyClass]
Accessing standard Java packages and classes is more straightforward than accessing custom packages and classes. For your convenience, there are global objects defined for each of the standard Java packages: com
, edu
, java
, javax
, and org
. They have aliases that correspond to properties of the Packages
object. The following example shows how you can access the java.lang
package and the java.lang.System
class:
jjs> java.lang
[JavaPackage java.lang]
jjs> typeof java.lang
object
jjs> java.lang.System
[JavaClass java.lang.System]
jjs> typeof java.lang.System
function
As you can see from the previous example, Nashorn interprets Java packages as JavaPackage
objects, and Java classes as JavaClass
function objects, which can be used as constructors for the classes. Creating Java Objects describes how to instantiate a class.
The traditional approach for accessing Java packages and classes is intuitive and straightforward, but at the same time, it can be inefficient, limited, and error-prone for the following reasons:
-
Each property access has a cost, so accessing a package or class in a deep hierarchy can be slow.
-
There is no special syntax for creating Java arrays. You must use the
java.lang.reflect.Array
class as a workaround. -
If you misspell a class name, Nashorn assumes that you provided a package name, and interprets it as a
JavaPackage
object instead of aJavaClass
function object. You might not be aware of this until an error is thrown when you attempt to use it as a class. To avoid this, use thetypeof
operator to conditionally test that the construct you are trying to access is interpreted as a function object. The following example shows how this conditional check works:jjs> typeof java.lang.System == "function" true jjs> typeof java.lang.Zyztem == "function" false
To avoid the disadvantages of the approach previously described, Nashorn defines the Java
global object that has several functions for working with Java classes. The Java.type()
function takes a string with the fully qualified Java class name, and returns the corresponding JavaClass
function object. The following example shows how you can access the java.lang.System
class:
jjs> Java.type("java.lang.System")
[JavaClass java.lang.System]
Similar to importing classes in Java, it is a good practice to declare variables of JavaClass
type at the beginning of a script. The following example shows how you can declare the System
variable and give it a value of the java.lang.System
class:
jjs> var System = Java.type("java.lang.System")
jjs> System
[JavaClass java.lang.System]
Creating Java Objects
To instantiate a class, pass the JavaClass
function object to the new
operator. Nashorn invokes the corresponding constructor based on the arguments passed to the function.
The following example shows how you can instantiate the java.util.HashMap
class with the default initial capacity and with the initial capacity set to 100:
jjs> var HashMap = Java.type("java.util.HashMap")
jjs> var mapDef = new HashMap()
jjs> var map100 = new HashMap(100)
Accessing Class and Instance Members
You can use the standard dot notation to access static fields, methods, and inner classes as follows.
jjs> Java.type("java.lang.Math").PI
3.141592653589793
jjs> Java.type("java.lang.System").currentTimeMillis()
1375813353330
jjs> Java.type("java.util.Map").Entry
[JavaClass java.util.Map$Entry]
An inner class can also be accessed using internal representation with the dollar sign ($
) as the separator, or a dot, which is consistent with Java:
jjs> Java.type("java.util.Map$Entry")
[JavaClass java.util.Map$Entry]
jjs> Java.type("java.util.Map.Entry")
[JavaClass java.util.Map$Entry]
To invoke an instance method or access an instance field of an object, use the dot operator, similar to how it is done in Java. The following example shows how you can call the toUpperCase()
method on a String
object:
jjs> var String = Java.type("java.lang.String")
jjs> var str = new String("Hello")
jjs> str
Hello
jjs> var upper = str.toUpperCase()
jjs> upper
HELLO
Nashorn also supports member access using the bracket notation, where you specify the name of the member as a string between brackets ([]
) that immediately follow the class (in case of a static member) or object (in case of an instance member). This method is defined by the ECMAScript as an alternative to the dot notation, and is not intuitive for Java developers. However, it can be used to resolve method overload ambiguity. By default, Nashorn uses the overloaded method that best matches the arguments, and this is not always what you expect. For example, if you want to print a double
value, you must use the java.lang.System.out.println(double)
method overload, as shown in the following example:
jjs> Java.type("java.lang.System").out.println(10)
10
jjs> Java.type("java.lang.System").out["println(double)"](10)
10.0
Using JavaBeans
Nashorn enables you to treat accessor and mutator methods in JavaBeans as equivalent JavaScript properties. The name of the property is the name of the JavaBean method without the get
or set
suffix, and starts with a lowecase letter.
For example you can call the getYear()
and setYear()
methods in a java.util.Date
object using the year
property as follows:
jjs> var Date = Java.type("java.util.Date")
jjs> var date = new Date()
jjs> date.year + 1900
2013
jjs> date.year = 2014 - 1900
114
jjs> date.year + 1900
2014
Working with Java Arrays
To access a Java array class, pass to the Java.type()
function the type of objects that comprise the array followed by a pair of brackets (similar to Java syntax).
The following example shows how you can access a Java array of integers and a Java array of String
objects:
jjs> Java.type("int[]")
[JavaClass [I]
jjs> Java.type("java.lang.String[]")
[JavaClass [Ljava.lang.String;]
After you have the array type object, you can use it to instantiate an array as you do any other class. You can access array entries by their indexes, and use the dot or bracket notation to access members (similar to Java syntax), as shown in the following example:
jjs> var IntArrayType = Java.type("int[]")
jjs> var arr = new IntArrayType(10)
jjs> arr[1] = 123
123
jjs> arr[2] = 321
321
jjs> arr[1] + arr[2]
444
jjs> arr[10]
java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 10
jjs> arr.length
10
If you have an existing JavaScript array, you can convert it to a Java array using the Java.to()
function. The following example shows how you can convert a JavaScript array of strings "a"
, "b"
, and "c"
, to a java.lang.String[]
array with the same values:
jjs> var jsArr = ["a","b","c"]
jjs> var strArrType = Java.type("java.lang.String[]")
jjs> var javaArr = Java.to(jsArr, strArrType)
jjs> javaArr.class
class [Ljava.lang.String;
jjs> javaArr[0]
a
You can iterate through a Java array's indexes and values using the for
and for each
statements as follows:
jjs> for (var i in javaArr) print(i)
0
1
2
jjs> for each (var i in javaArr) print(i)
a
b
c
Working with Java Strings
Nashorn represents strings as java.lang.String
objects. When you concatenate two strings, you get a String
instance.
jjs> var a = "abc"
jjs> a.class
class java.lang.String
jjs> var b = a + "def"
jjs> b.class
class java.lang.String
jjs> var c = String(b)
jjs> c.class
class java.lang.String
Working with Java Numbers
Nashorn interprets numbers as java.lang.Double
, java.lang.Long
, or java.lang.Integer
objects, depending on the computation performed. You can use the Number()
function to force a number to be a Double
object, as shown in the following example.
jjs> var intNum = 10
jjs> intNum.class
class java.lang.Integer
jjs> var dblNum = Number(intNum)
jjs> dblNum.class
class java.lang.Double
Working with Java Lists and Maps
Nashorn interprets Java lists as arrays; iterate over the values of a list with the for each
statement. To iterate over keys and values in a map, use the keySet()
and values()
methods.
You can access list elements by using the index in brackets ([]
) and iterate over the values of a list using the for each
statement, as shown in the following example.
jjs> var ArrayList = Java.type("java.util.ArrayList")
jjs> var alist = new ArrayList()
jjs> alist.add("a")
true
jjs> alist.add("b")
true
jjs> alist.add("c")
true
jjs> alist[1]
b
jjs> for each (var i in alist) print(i)
a
b
c
Note:
You can only access list and map elements by using the index in brackets; in particular, you cannot access queue and set elements by using this syntax.The following example shows how you can create a HashMap
object and iterate over its keys and values with the keySet()
and values()
methods.
jjs> var HashMap = Java.type("java.util.HashMap")
jjs> var hm = new HashMap()
jjs> hm.put("name", "Bob")
jjs> hm.put("age", 40)
jjs> hm.put("weight", 180)
jjs> for each (var i in hm.keySet()) print(i)
weight
age
name
jjs> for each (var i in hm.values()) print(i)
180
40
Bob
Alternatively, you can iterate over the values of a map the same way you iterate over the values of a list, as shown in the following example:
jjs> for each (var i in hm) print(i)
180
40
Bob
Extending Java Classes
You can extend a class using the Java.extend()
function that takes a Java type as the first argument and method implementations (in the form of JavaScript functions) as the other arguments.
The following script extends the java.lang.Runnable
interface and uses it to construct a new java.lang.Thread
object:
var Run = Java.type("java.lang.Runnable");
var MyRun = Java.extend(Run, {
run: function() {
print("Run in separate thread");
}
});
var Thread = Java.type("java.lang.Thread");
var th = new Thread(new MyRun());
Nashorn can automatically extend functional interfaces (see the annotation type FunctionalInterface) if you provide the function for implementing the method as the argument to the constructor. The following script extends the java.lang.Runnable
interface and uses it to construct a new java.lang.Thread
object, but it uses fewer lines of code than in Example 2-1, because the Java.extend()
function is called automatically for a functional interface.
var Thread = Java.type("java.lang.Thread")
var th = new Thread(function() print("Run in a separate thread"))
Extending Java Classes in Java Platform, Standard Edition Java Scripting Programmer's Guide describes the capabilities of the Java.extend()
function.
Example 2-1 Extending a Java Class
var Run = Java.type("java.lang.Runnable");
var MyRun = Java.extend(Run, {
run: function() {
print("Run in separate thread");
}
});
var Thread = Java.type("java.lang.Thread");
var th = new Thread(new MyRun());
Example 2-2 Extending a Functional Interface
var Thread = Java.type("java.lang.Thread")
var th = new Thread(function() print("Run in a separate thread"))
Restricting Script Access to Specified Java Classes
The jdk.nashorn.api.scripting.ClassFilter
interface provides fine-grained control over access to Java classes from JavaScript code by restricting access to specified Java classes from scripts run by a Nashorn script engine.
Applications that embed Nashorn, in particular, server-side JavaScript frameworks, often have to run scripts from untrusted sources and therefore must limit access to Java APIs. These applications can implement the ClassFilter interface to restrict Java class access to a subset of Java classes. To do so, client applications must use Nashorn APIs to instantiate the Nashorn script engine.
Note:
While the ClassFilter interface can prevent access to Java classes, it is not enough to run untrusted scripts securely. The ClassFilter interface is not a replacement for a security manager. Applications should still run with a security manager before evaluating scripts from untrusted sources. Class filtering provides finer control beyond what a security manager provides. For example, an application that embeds Nashorn may prevent the spawning of threads from scripts or other resource-intensive operations that may be allowed by security manager.Using Class Filters
To use a class filter, implement the ClassFilter
interface and define the method boolean exposeToScripts(String)
.
The String
argument is the name of the Java class or package that the Nashorn script engine encounters when it runs a script. Define the method exposeToScripts
such that it returns false
for those classes and packages you want to prevent scripts from accessing. Then, create a Nashron script engine with your class filter with the method NashornScriptEngineFactory.getScriptEngine(ClassFilter)
or NashornScriptEngineFactory.getScriptEngine(String[], ClassLoader, ClassFilter)
.
Restricting Access to Java Reflection APIs
If you are using a security manager, then Nashorn allows a script to use the Java Reflection APIs (for example, the java.lang.reflect
and java.lang.invoke
packages) only if the script has the nashorn.javaReflection
run time permission.
If you are using a class filter, then Nashorn prevents access to Java Reflection APIs even when a security manager is not present. Note that you do not need to use a class filter if the Java Reflection APIs are available because a script can use the Class.forName(String)
to circumvent the class filter.
Example of Using the ClassFilter Interface
This example, MyClassFilterTest.java
, demonstrates the ClassFilter
interface. It restricts scripts’ access to the class java.io.File.
import javax.script.ScriptEngine; import jdk.nashorn.api.scripting.ClassFilter; import jdk.nashorn.api.scripting.NashornScriptEngineFactory; public class MyClassFilterTest { class MyCF implements ClassFilter { @Override public boolean exposeToScripts(String s) { if (s.compareTo("java.io.File") == 0) return false; return true; } } public void testClassFilter() { final String script = "print(java.lang.System.getProperty(\"java.home\"));" + "print(\"Create file variable\");" + "var File = Java.type(\"java.io.File\");"; NashornScriptEngineFactory factory = new NashornScriptEngineFactory(); ScriptEngine engine = factory.getScriptEngine( new MyClassFilterTest.MyCF()); try { engine.eval(script); } catch (Exception e) { System.out.println("Exception caught: " + e.toString()); } } public static void main(String[] args) { MyClassFilterTest myApp = new MyClassFilterTest(); myApp.testClassFilter(); } }
This example prints the following:
C:\Java\jre8 Create file variable Exception caught: java.lang.RuntimeException: java.lang.ClassNotFoundException: java.io.File
The MyClassFilterTest.java
example does the following: