Skip to Main Content

Java EE (Java Enterprise Edition) General Discussion

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

Interested in getting your voice heard by members of the Developer Marketing team at Oracle? Check out this post for AppDev or this post for AI focus group information.

Enterprise Nashorn

Yolande Poirier-OracleFeb 25 2015 — edited Aug 8 2016

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!

Comments

Post Details

Added on Feb 25 2015
2 comments
29,244 views