Transposit is built on a Java backend uses Nashorn. We wanted to support ES6 syntax, so we investigated alternatives.
Transposit is built on a Java backend and runs customers’ JavaScript on the server-side using Nashorn. After our recent alpha launch, we received numerous customer requests to support ES6 syntax. Nashorn is deprecated in JDK 11 and so is unlikely to ever support full ES6 syntax, so we began investigating alternatives — and GraalVM is where we landed.
GraalVM is Oracle’s recommended replacement for Nashorn, despite the technology being relatively new. We liked the idea of a more performant compiler, and we’re interested in potentially using the GraalVM to allow customers to script in other languages like Python or Ruby besides our currently supported languages (JS, SQL).
We considered introducing NodeJS into our stack, but decided it would take more time and effort than we wanted to invest just to be able to use ES6 syntax. So we chose GraalVM instead because the migration path from Nashorn is clear and fast.
While some things were well documented (such as Nashorn compatibility mode), adopting such a new technology unsurprisingly had areas that lacked documentation and included a few hurdles to jump. We want to share with you all some lessons learned from our adventure.
GraalVM is a collection of many projects, not all of which need to be used at the same time. In the case of migrating from Nashorn to GraalVM, we used:
One of the most popular GraalVM features is compiling JVM programs to native. This feature is great for those who need smaller executables or faster startup times, but passing Java objects to Graal languages isn’t currently supported by native. Fortunately, the performance improvements gained by compiling to native isn’t an immediate need of ours.
In more recent release candidates, the Graal folks have published Truffle and GraalJS jars. This is great news for those not yet on JDK 11: it means we can still use GraalJS, just with the downside that JavaScript will be interpreted instead of optimized via the Graal Compiler. Just include the following maven dependencies to get started:
<!-- https://mvnrepository.com/artifact/org.graalvm.sdk/graal-sdk -->
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>graal-sdk</artifactId>
<version>1.0.0-rc8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.oracle.truffle/truffle-api -->
<dependency>
<groupId>org.graalvm.truffle</groupId>
<artifactId>truffle-api</artifactId>
<version>1.0.0-rc8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.graalvm.js/js -->
<dependency>
<groupId>org.graalvm.js</groupId>
<artifactId>js</artifactId>
<version>1.0.0-rc8</version>
</dependency>
For those on JDK 11, you can run the Graal Compiler using the following flags:
-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler
You can also use the Graal Compiler by downloading it directly. If you go this route, Truffle and GraalJS are included by default, and so you only need to explicitly add the SDK:
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>graal-sdk</artifactId>
<version>1.0.0-rc8</version>
</dependency>
With Nashorn we’d been caching our ScriptObjectMirror objects for parallel runs of the same function. We found that GraalJS disallows reads from the same Context across threads. As a result we’ve stopped caching across threads.
Now that we no longer cache Contexts, we have to remember to close each Context once we’re done with it:
if the context is no longer needed, it is necessary to close it to ensure that all resources are freed. Contexts are also AutoCloseable for use with the Java try-with-resources statement.
The Graal SDK looks for languages to use under META-INF/truffle/language
. The published GraalJS library comes with its own list of languages, but 1.0.0-rc8 and rc9 don’t include the regex language that it’s dependent upon. For the moment, we wrote our own truffle/language
file:
language1.characterMimeType.0=application/tregex
language1.className=com.oracle.truffle.regex.RegexLanguage
language1.id=regex
language1.implementationName=
language1.interactive=true
language1.internal=true
language1.name=REGEX
language1.version=0.1
language2.className=com.oracle.truffle.js.parser.JavaScriptLanguage
language2.dependentLanguage.0=regex
language2.id=js
language2.implementationName=
language2.interactive=true
language2.internal=false
language2.mimeType.0=application/javascript
language2.mimeType.1=text/javascript
language2.name=JavaScript
language2.version=1.0
Most of the examples in the GraalVM documentation use context.getPolyglotBindings()
. However, polyglot bindings require an import statement in the JS, so we use context.getBindings("js")
instead.
Nashorn does not provide the CommonJS require()
syntax and unfortunately neither does GraalJS. There’s a popular solution for Nashorn called nashorn-commonjs-modules. Graal tooling is less evolved, so we created our own commonjs fork for our Graal solution.
While most of the hype around GraalVM has been around compiling JVM projects to native, we found plenty of value in its Polyglot APIs.GraalVM is a compelling and already fully useable alternative to Nashorn, though the migration path is still a little rocky, mostly due to a lack of documentation. Hopefully this post helps others find their way off of Nashorn and on to the holy graal.