Enterprise Nashorn
by Adam Bien
Learn how to use the extensive capabilities of the Nashorn JavaScript engine.
Published February 2015
Nashorn is an open source JavaScript engine developed by Oracle that is compliant with ECMAScript-262 Edition 5.1. It comes with Java SE 8 and is the successor to the Mozilla Rhino engine, which was introduced in December 2006 as a default Service Provider Interface (SPI) for JSR 223: Scripting for the Java Platform. Both engines are embeddable and are executed "in-process" by the Java Virtual Machine (JVM).
Nashorn is shipped with Java and can be used without any additional configuration or setup and without any external dependencies. This article demonstrates Nashorn's capabilities, which are particularly interesting in the enterprise space for Java EE projects.
Embedded Nashorn
The Nashorn JavaScript engine can be accessed from Java code using the standard JSR-223 API by calling ScriptEngineManager#getEngineByName
and passing javascript
as the parameter (see Listing 1). Because Nashorn is now the default JavaScript engine, no additional setup or configuration is needed.
import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import static org.junit.Assert.assertTrue; import org.junit.Test; public class NashornTest { @Test public void nashornAvailable() { ScriptEngineManager sem = new ScriptEngineManager(); ScriptEngine engine = sem.getEngineByName("javascript"); String engineName = engine.getClass().getName(); assertTrue(engineName.contains("jdk.nashorn.api.scripting.NashornScriptEngine")); } }
Listing 1: Booting Nashorn from Java
After obtaining the ScriptEngine
instance, you can evaluate scripts by calling the ScriptEngine#eval
method (see Listing 2).
@Test public void executeEval() throws ScriptException { this.engine.eval("print('hello world')"); }
Listing 2: JavaScript as a String
The eval
method throws a checked ScriptException
that provides useful troubleshooting information (see Listing 3).
@Test public void syntaxError() { try { this.engine.eval("-invalid javascript!"); fail("Syntax error was ignored!"); } catch (ScriptException ex) { String fileName = ex.getFileName(); assertThat(fileName, is("<eval>")); int columnNumber = ex.getColumnNumber(); assertTrue(columnNumber > 0); int lineNumber = ex.getLineNumber(); assertThat(lineNumber, is(1)); String message = ex.getMessage(); assertNotNull(message); } }
Listing 3: Code for ScriptException
, which provides useful information
By running the code in Listing 3, ScriptException
can reveal the information shown in Listing 4.
fileName = <eval> columnNumber = 9 lineNumber = 1 message = <eval>:1:9 Expected ; but found javascript -invalid javascript! ^ in <eval> at line number 1 at column number 9 Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.441 sec
Listing 4: Concrete example of a ScriptException
Particularly useful is the interaction with Java objects. The ScriptEngine
exposes a Binding
instance which is a Map<String,Object>
. Any Java object can be passed to the map as a value and referenced from JavaScript using the String as a key for the name (see Listing 5).
@Test public void bindings() throws ScriptException { Bindings bindings = this.engine.createBindings(); assertNotNull(bindings); final String expected = "HELLO"; bindings.put("msg", expected); Object result = this.engine.eval("msg", bindings); assertThat(result, is(expected)); }
Listing 5: Referencing a Java String from JavaScript
The method ScriptEngine#put
is a streamlined version of the code in Listing 5. All parameters are passed directly to the particular engine instance (see Listing 6).
@Test public void javaIntegration() throws ScriptException { this.engine.put("SIX", 6); this.engine.put("SEVEN", 7); Object result = this.engine.eval("SIX*SEVEN"); assertThat(result, is(42d)); }
Listing 6: Passing Java objects directly to the engine
Not only primitive datatypes, but also full-fledged Java objects can be passed to the ScriptEngine
, as shown in Listing 7.
public class Developer { private String name; private String language; public Developer(String name, String language) { this.name = name; this.language = language; } public String getName() { return name; } public String getLanguage() { return language; } public boolean isCool() { return language.contains("java"); } } //--- @Test public void domainObjectAccess() throws ScriptException { Developer javaDev = new Developer("duke", "java"); this.engine.put("dev", javaDev); Object result = this.engine.eval("dev.name + ' develops in ' + dev.language"); String expected = "duke develops in java"; assertThat(result, is(expected)); expected = "duke is cool"; result = this.engine.eval("if(dev.cool){'duke is cool'}"); assertThat(result, is(expected)); }
Listing 7: Accessing Java objects from JavaScript
Flexible Calculations
Java does not come with a "read-eval-print loop" (REPL) tool, nor with the ability to evaluate code at runtime. To perform any dynamic calculations at runtime, you will have to implement your own language or parsers with additional tools, for example, ANTLR (ANother Tool for Language Recognition).
Instead of building parsers, you could "misuse" the Nashorn JavaScript engine for input parsing and evaluation. As discussed in the previous section, the ScriptEngine
has full access to the Java objects passed to instances of Binding
. A calculation can be stored in an ordinary String and fetched from a file or database. A @Stateless
EJB bean is a perfect place to cache the ScriptEngine
and ensure its thread-safe execution. A set of predefined domain objects and calculations can be passed to the bindings and made accessible to the JavaScript engine. The formula passed as a parameter in Listing 8 would usually be fetched from a trusted and tested script store.
@Stateless @Path("calculations") public class CalculationsResource { private static final String ENGINE_NAME = "JavaScript"; private ScriptEngine scriptEngine = null; @PostConstruct public void initScripting() { ScriptEngineManager engineManager = new ScriptEngineManager(); this.scriptEngine = engineManager.getEngineByName(ENGINE_NAME); } @POST @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Produces(MediaType.TEXT_PLAIN) public String calculate(@FormParam("formula") String formula) { Object retVal = null; try { Bindings binding = this.scriptEngine.createBindings(); //domain object integration binding.put("FIVE", 5); binding.put("A1", new BigDecimal(1)); binding.put("A2", new BigDecimal(2)); long start = System.currentTimeMillis(); try { retVal = this.scriptEngine.eval(formula, binding); } catch (Exception e) { throw new IllegalStateException("Exception during evaluating script: " + e, e); } return retVal.toString(); } }
Listing 8: Integration of the ScriptEngine
and an EJB bean
With the "Fluid Logic" pattern, you can easily extract various parts of your code—such as computations, orchestrations, and validations—into a script and keep them outside the deployable archive.
Type Safety
Dealing with dynamically typed JavaScript within statically typed Java might be challenging. Fortunately a Nashorn ScriptEngine
instance implements the javax.script.Invocable
interface. An Invocable
instance can transform a JavaScript function into a Java interface implementation. The dynamic nature of JavaScript can be hidden behind a type-safe and convenient Java interface.
In Listing 9, all "java" developers are identified using a java.util.function.Predicate
instance.
@Test public void interfaceImplementation() { Developer javaDev = new Developer("james", "java"); Developer duke = new Developer("duke", "java"); Developer jsDev = new Developer("brendan", "javascript"); List<Developer> developers = new ArrayList<>(); developers.add(javaDev); developers.add(jsDev); developers.add(duke); Predicate<Developer> filter = getFilter(); List<String> javaDevelopers = developers.stream(). filter(filter). map(d -> d.getName()). collect(Collectors.toList()); assertThat(javaDevelopers.size(), is(2)); assertTrue(javaDevelopers.contains("james")); assertTrue(javaDevelopers.contains("duke")); }
Listing 9: Filtering a collection using a predicate
Predicate
is a functional interface, so you could easily construct it directly in Java using a lambda expression. However, lambdas are static and you would have to recompile and redeploy the source code after any modification.
Nashorn, on the other hand, can implement a Java interface with JavaScript functions. In Listing 10, the corresponding JavaScript function is hardcoded in a String; however, you could equally well fetch the JavaScript function from a database, a file, or any other source.
Predicate getFilter() { Invocable invocable = (Invocable) this.engine; Predicate predicate = v -> true; try { this.engine.eval("function test(dev){return dev.language.equals('java');}"); predicate = invocable.getInterface(Predicate.class); } catch (ScriptException ex) { //... } return predicate; }
Listing 10: Implementing a Java interface with a JavaScript function
Hot Deployable Code and Serialization
Even more compelling is the use of the JavaScript implementation of Java interfaces in a distributed environment for caches and in-memory grids, for example, for JCache JSR 107 implementations. To pass an instance of a Predicate
to remote processing nodes, you will have to serialize the Predicate
instance and send it over the network. A successful deserialization will require that the class file be shared across all nodes as well. Another option is the implementation of a custom ClassLoader or the use of java.rmi.MashalledObject
, where the class is moved along with the payload.
From the Java perspective, a JavaScript function is just an ordinary String and can be easily passed over the network as such. No additional custom class loading would be required to pass a script to a remote node. In fact, an in-memory grid could be used to distribute the script to all remote nodes. JavaScript functions can be easily loaded on-demand and converted in-place into Java interface implementations.
Dynamic Validation Using Bean Validation
Cross-checks on domain objects can be conveniently performed with the Bean Validation API. The validation rules are usually hardcoded either in the ConstraintValidator
instance or within the domain object. With the built-in Nashorn JavaScript engine, such rules could be completely externalized and dynamically loaded on demand.
Let's check the capabilities of a "developer" using Nashorn. For demonstration purposes, the entire validation script in Listing 11 is carried in the method value()
. This would be the worst possible approach in the real world, but it is perfectly suitable for demonstration purposes. If you store a script in a constant, you are combining Java's static behavior with JavaScript's dynamic typing. In the real world, you would rather maintain a key in the annotation and resolve the value at runtime from a database or another external resource.
@Documented @Constraint(validatedBy = BleedingEdgeValidator.class) @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface BleedingEdge { String message() default "Developer is not bleeding edge."; String value(); Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
Listing 11: Cross-attribute validation annotation
The validator implementation in Listing 12 relies on a script returning a boolean value, and it passes the current Developer instance under the name dev
to the ScriptEngine
.
public class BleedingEdgeValidator implements ConstraintValidator<BleedingEdge, Developer> { private ScriptEngine engine; private BleedingEdge annotation; @Override public void initialize(BleedingEdge annotation) { ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); this.engine = scriptEngineManager.getEngineByName("javascript"); this.annotation = annotation; } @Override public boolean isValid(Developer workshop, ConstraintValidatorContext context) { this.engine.put("dev", workshop); try { //fetch the script from an external source, not the annotation final String script = annotation.value(); return (boolean) this.engine.eval(script); } catch (ScriptException ex) { throw new IllegalStateException("Cannot validated workshop", ex); } } }
Listing 12: A validator that uses ScriptEngine
Again, in the real world, you would prefer to load the script dynamically instead of hardcoding it in a constant. Before the deployment of the rules, the syntactical correctness of the script could be checked within regular unit tests. Listing 13 shows an example of injecting a script from an external resource.
public class BleedingEdgeValidator implements ConstraintValidator<BleedingEdge, Developer> { private ScriptEngine engine; private BleedingEdge annotation; @Inject String validationScript;//... }
Listing 13: Injecting a script from an external resource
The value of the injected script could be loaded from a database with a key derived from the InjectionPoint
metadata (see this Java Magazine "Convention over Configuration in Java EE 6" article). The Bean Validation API together with Nashorn allows you to maintain all the rules in a Microsoft Excel spreadsheet and activate the validation scripts on-demand without any redeployment.
Automation Tasks
Nashorn also comes with surprising Inversion of Control (IoC) capabilities. Nashorn can be used as a full-fledged system scripting language and can, therefore, supersede bash scripts for various automation tasks. Because Nashorn is a standard-compliant JavaScript implementation and it comes with JDK 8, it becomes a perfect, portable tool for the implementation of any automation tasks. The jjs
binary comes with the JDK and allows script execution from the command line.
In addition, Nashorn comes with extensions to the regular JavaScript language for tighter integration with the operating system. In the Listing 14, a script iterates over all defined system variables.
#!/usr/bin/jjs -fv for(key in $ENV) print("${key}:${$ENV[key]}");
Listing 14: Iteration over system environment variables using JavaScript
In scripting mode, Nashorn is also able to execute Java code and access Java classes from the classpath. Its convenient integration with Java makes Nashorn the perfect tool for any JDBC maintenance jobs. In Listing 15, Nashorn is used to back up a HyperSQL DataBase (HSQLDB) database via a JDBC connection.
#!/usr/bin/jjs -J-Djava.class.path=[PATH_TO_HSQL]/lib/hsqldb.jar -fv print("Using driver: ${$ENV.HSQL2_HOME}"); var driver= "org.hsqldb.jdbcDriver"; var url = "jdbc:hsqldb:hsql://localhost:9092/NASHORN"; var user = "NAS"; var pwd = "HORN"; var sql = "BACKUP DATABASE TO '/backup/' BLOCKING"; java.lang.Class.forName(driver); var connection = java.sql.DriverManager.getConnection(url,user,pwd); var statement = connection.createStatement(); statement.executeUpdate(sql); print("${sql} executed");
Listing 15: Backing up an HSQLDB database using Nashorn
In addition, performing schema evolution (using tools such as Flyway or Liquibase) or implementing installation scripts can be easily done in a portable manner with JavaScript. In fact, the same script can be executed directly from the command-line interface as well as from a continuous integration (CI) environment using tools such as Jenkins.
I also frequently use Nashorn for the orchestration of build jobs and administrative tasks. In fact, Nashorn scripts are more portable than bash scripts and better supported by popular developer integrated development environments (IDEs).
Monitoring and Transformations
With access to operating system tools, Nashorn becomes a capable HTTP client. Modern services come with REST-based management and monitoring APIs. Tools such as wget
or curl
are native executables, and so they are directly executable from Nashorn.
Docker is a simple documentation generator. In the Listing 16, the output from curl is converted into an array of JSON objects, which are first-class citizen JavaScript objects.
#!/usr/bin/jjs -fv var host = "192.168.0.42" var dockerUri="http://${host}:5555/containers/json"; var command = "curl ${dockerUri}"; $EXEC(command); var containers = JSON.parse($OUT); for each(container in containers){ print("${container.Image} ${container.Names[0]} ${container.Status}"); }
Listing 16: Listing Docker containers using Nashorn
JSON can be conveniently manipulated in JavaScript environments without any friction. With a few additional lines of code, we could easily obtain additional Docker container runtime information, IP addresses, ports, or even logs (see project watchdock).
Also, because Java EE application servers emit JSON via HTTP as a platform-neutral monitoring and management API, using a few Nashorn scripts, you could easily monitor Java EE applications and even deploy applications to the GlassFish application server (see the article "RESTful GlassFish Monitoring and Management").
The Java-based project Enhydrator goes in exactly the opposite direction. Enhydrator is a Java SE 8–based extract, transform, and load (ETL) library that can consume, transform, and emit table-like structures. Enhydrator is implemented entirely in Java and needs the interface implementations shown in Listing 17 to perform any useful work.
@FunctionalInterface public interface ColumnTransformer { Object execute(Object entry); } @FunctionalInterface public interface RowTransformer { Row execute(Row input); }
Listing 17: Functional Java interfaces for row and column transformations
Relying on conventions, Enhydrator searches in predefined directories for scripts and converts them to instances of the interfaces introduced in Listing 17. Nashorn scripts are project-specific and completely separated from the Enhydrator project.
More Nashorn Advantages
Nashorn also shines from the non-functional perspective. Nashorn's performance is far better than its predecessor, Rhino. Also, Nashorn integrates well with Java SE 8 features such as lambdas, and it can even be used in a multithreaded environment.
Best of all, Nashorn comes with JDK 8, so there is no need to download, install, or configure external libraries. This keeps your WAR files lean and enables fast deployment.
See Also
About the Author
Consultant and author Adam Bien is an Expert Group member for the Java EE 6, Java EE 7, EJB 3.X, JAX-RS, and JPA 2.X JSRs. He has worked with Java technology since JDK 1.0 and with Servlets/EJB 1.0, and he is now an architect and developer for Java SE and Java EE projects. He has edited several books about JavaFX, J2EE, and Java EE, and he is the author of Real World Java EE Patterns—Rethinking Best Practices and Real World Java EE Night Hacks—Dissecting the Business Tier. Adam is also a Java Champion, Top Java Ambassador 2012, and a JavaOne 2009, 2011, 2012, and 2013 Rock Star. Adam organizes occasionally Java EE workshops at Munich's Airport and broadcasts a monthly Java Q&A show.
Join the Conversation
Join the Java community conversation on Facebook, Twitter, and the Oracle Java Blog!