Adding back Nashorn to Apache Sling on Java 15 or newer

Backwards compatiblity is of the utmost importance for Apache Sling. Existing applications should ‘just work’ when running on top of newer versions of the Sling Starter or other Sling-based applications.

An interesting challege comes up when needing to adjust to newer version of the Java runtime. Overall Java does a very good job of staying backwards compatible but there are scenarios where consumers need to adapt.

One such case is the removal of the built-in Nashorn engine in Java 15. This is documented in JEP 372 and the main reason is the effort needed to keep the engine up-to-date with ECMAScript changes. The project is still maintained though as a GitHub project at https://github.com/openjdk/nashorn so it is possible to add it back to a Sling deployment.

This post documents the main steps needed to update an existing bundle to adds back the Nashborn engine to a Sling application running on Java 15 or newer.

Maven dependencies

Assuming that you already have a working OSGi bundle built with Maven, you first need to add the nashorn dependency to your pom.xml:

<dependency>
    <groupId>org.openjdk.nashorn</groupId>
    <artifactId>nashorn-core</artifactId>
    <version>15.6</version>
</dependency>

Make sure to check if there are newer versions availabler in Maven Central.

The Nashorn scripting engine has very few dependencies but id does depend on ASM. If you prefer the bundle to be self-contained you should manually manage the ASM dependency and ensure you use the latest version. When declaring multiple dependcies I prefer defining a property first

<asm.version>9.7</asm.version>

We can now exclude the transitive ASM dependencies and add them manually to our pom.xml. The dependencies block now becomes

<dependency>
    <groupId>org.openjdk.nashorn</groupId>
    <artifactId>nashorn-core</artifactId>
    <version>15.6</version>
    <exclusions>
        <exclusion>
            <groupId>org.ow2.asm</groupId>
            <artifactId>*</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>${asm.version}</version>
</dependency>
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm-commons</artifactId>
    <version>${asm.version}</version>
</dependency>
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm-tree</artifactId>
    <version>${asm.version}</version>
</dependency>
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm-util</artifactId>
    <version>${asm.version}</version>
</dependency>
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm-analysis</artifactId>
    <version>${asm.version}</version>
</dependency>

Embedding Nashorn

We can now use the bnd-maven-plugin to embed all the needed classes to our bundle.

The following configuration, whether added to the bnd.bnd file or to the configuration section of the bnd-maven-plugin, will embed Nashorn and its dependencies:

-includeresource: \
    @nashorn-core-*.jar!/!META-INF/MANIFEST.MF,\
    @asm-*.jar!/!META-INF/MANIFEST.MF

To verify that the embedding is correct, build the project once and ensure that the Nashorn and ASM classes are included in the resulting bundle:

$ mvn package
$ jar tf target/your-bundle.jar | grep nashorn | wc -l
$ jar tf target/your-bundle.jar | grep asm | wc -l

Deploying the bundle

If you have the sling-maven-plugin installed you can build and deploy bundle in one go using mvn package sling:install. Otherwise you can build it and use other mechanisms such as the Apache Felix Web Console.

After deploying you can use the Web Console scripting status, by default available at http://localhost:8080/system/console/slingscripting to verify the available scripting engines. If the bundle is correctly deployed and started you should see a listing similar to

OpenJDK Nashorn 15.6
-------------------------------------
- Language : ECMAScript, ECMA - 262 Edition 5.1
- Extensions : js
- MIME Types : application/javascript, application/ecmascript, text/javascript, text/ecmascript
- Names : nashorn, Nashorn, js, JS, JavaScript, javascript, ECMAScript, ecmascript

Congratulations, you have now deployed the Nashorn scripting engine to your Sling application.

A note on accessing scripting engines in Sling

Java applications normally instantiate the javax.script.ScriptEngineManager directly to access the available scripting engines. This works by virtue of the ServiceLoader mechanism. Unfortunately, that mechanism does not work out-of-the-box in OSGi environments. My colleague David Bosschaert has written a blog post the topic, including solutions for the general problem - java.util.ServiceLoader in OSGi. But for the specific scenario in Sling we do something simpler - obtain a reference to the javax.script.ScriptEngineManager service from the OSGi service registry, as registered by the Apache Sling Scripting Core bundle. You can then use the scripting engine to as usual to evalute inputs.

public class NashornTestServlet extends SlingSafeMethodsServlet {

  @Reference
  private ScriptEngineManager scriptEngineManager;

  @Override
  protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
    throws ServletException, IOException {

    ScriptEngine engine = scriptEngineManager.getEngineByName("nashorn")
    String input = "var result = 'Hello from Nashorn!'; result;";
    Object result = engine.eval(input);

    // do something with the result
  }
}

Sample project

A project demonstrating the above steps is available in the Sling Samples repository at https://github.com/apache/sling-samples/tree/master/nashorn-external.