Running your JS code in Java

tl;dr java-xls 

After the last post on running JS code in python, quite a few people asked me about running JS code in Java.  I guess it’s not too surprising, given that working with Rhino is like pulling teeth.  But here goes:

Note that I use vim, not Eclipse or some other IDE, so some steps may seem unnatural.  However, I felt that it was truer to form to keep it all in a Makefile.

The standard choice for Java is Rhino, from the folks at Mozilla.  I wish they kept good documentation, but MDN is having issues: their API javadoc Reference link points to the main page and the fallback is also broken.  I recommend referencing the source directly.  

The first step is to get the rhino JAR.  This step is fairly straight-forward:

$ git clone
$ cd rhino
$ ant jar
$ cd -
$ cp rhino/build/rhino*/js.jar rhino.jar

All of the relevant rhino classes are in the package org.mozilla.javascript.

The first class T will evaluate 1+1 and print the output:

import org.mozilla.javascript.*;
public class T {
public static void main(String[] args) throws Exception { Context cx = Context.enter();
Scriptable scope = cx.initStandardObjects();
String script = "1+1";
String out = cx.evaluateString(scope, script, "<cmd>", 1, null);

Save this to and compile:

$ javac -cp .:rhino.jar && java -cp .:rhino.jar T

Variables are available in the same fashion:

String script = "var x = 1+1";
cx.evaluateString(scope, script, "<cmd>", 1, null);
script = "Math.pow(x,x)";
String out = cx.evaluateString(scope, script, "<cmd>", 1, null);

The other way to access variables is to use an accessor:

String out = scope.get("x", scope).toString();

Unfortunately, to request something deeper like “”, you can’t just request it by path:

String script = "var foo = {bar: {baz: {qux:1}}}";
String out = scope.get("", scope).toString();
(error) org.mozilla.javascript.UniqueTag@442a15cd: NOT_FOUND

The correct approach is to recursively apply.

String script = "var foo = {bar: {baz: {qux:'dafuq'}}}";
String out = (String)(((NativeObject)((NativeObject)((NativeObject)scope.get("foo", scope)).get("bar",scope)).get("baz",scope)).get("qux",scope));

so I wrote a helper function to do the right thing for “”.

Like with PyV8, rhino lacks console, and the error looks something like:

Exception in thread "main" org.mozilla.javascript.EcmaError: ReferenceError: "console" is not defined. (<cmd>#1)
at org.mozilla.javascript.ScriptRuntime.constructError(
at org.mozilla.javascript.ScriptRuntime.constructError(
at org.mozilla.javascript.ScriptRuntime.notFoundError(
at org.mozilla.javascript.gen._cmd__1._c_script_0(<cmd>:1)
at org.mozilla.javascript.ContextFactory.doTopCall(
at org.mozilla.javascript.ScriptRuntime.doTopCall(
at org.mozilla.javascript.gen._cmd__1.exec(<cmd>)
at org.mozilla.javascript.Context.evaluateString(
at T.main(

The workaround is to call `java.lang.System.out.println` (what a mouthful)

Unfortunately the NativeArray types are difficult to deal with, so a manual translation to a Java array is the easiest approach

String[] out = new String[(int)native_array.getLength()];
int idx;
for(Object o :native_array.getIds()) out[idx = (Integer)o] = native_array.get(idx, native_array).toString();

There are other helpers to convert between Java and JS, none of which are as pleasant as PyV8

In summary, the experience left much to be desired.  3/10, would advise others to re-evaluate decision to use Rhino in the first place, but if you have to do it I recommend just modifying the java-xls template (and the com.sheetjs.JSHelper class has a few helper functions)

  1. sheetjs posted this