5 things you didn't know about ... the Java Scripting API

An easier way to do scripting on the Java platform

The Java™ language is more than you need for some
projects, but scripting languages are famously lacking on the performance
front. Find out how the Java Scripting API (javax.script)
delivers the best of both worlds, by allowing you to invoke scripts from your
Java programs, and vice versa.

Many Java developers today are interested in using scripting languages on
the Java platform, but using a dynamic language that has been compiled
into Java bytecode isn't always possible. In some cases, it's quicker and
more efficient to simply script parts of a Java application or to
call the particular Java objects you need from within a script.

That's where javax.script comes in. The Java Scripting API,
introduced in Java 6, bridges the gap between handy little scripting
languages and the robust Java ecosystem. With the Java Scripting API, you
can quickly integrate virtually any scripting language with your Java
code, which opens up your options considerably when solving small problems
on someone else's dime.

1. Executing JavaScript with
jrunscript

Each new Java platform release brings with it a new set of command-line
tools buried away inside of the JDK's bin directory. Java 6 was no
exception, and jrunscript is no small addition to the Java
platform utilities.

About this series

So you think you know about Java programming? The fact is, most
developers scratch the surface of the Java platform, learning just
enough to get the job done. In this series, Ted Neward digs beneath the core functionality of the
Java platform to uncover little-known facts that could help you solve
even the stickiest programming challenges.

Consider the basic problem of writing a command-line script for performance
monitoring. The tool will borrow jmap (introduced in the previous article in the series) and run it every 5 seconds
against a Java process, in order to get a feel for how the process is
running. Normally, command-line shell scripts would do the trick, but in
this case the server application is deployed on a variety of different
platforms, including Windows® and Linux®. Sysadmins will testify
that trying to write shell scripts that run on both platforms is a pain.
The usual solution is to write a Windows batch file and a UNIX® shell
script, and just keep the two in sync over time.

But, as any reader of The Pragmatic Programmer knows, this is a
horrendous violation of the DRY (don't repeat yourself) principle and is a
breeding ground for bugs and defects. What we'd really like to do is write
some kind of OS-neutral script that can run across all the platforms.

The Java language is platform neutral, of course, but this really isn't a
case for a "system" language. What we need is a scripting language
— like JavaScript, for instance.

Listing 1 starts with a basic shell of what we want:

Listing 1.
periodic.js

while (true)
{
echo("Hello, world!");
}

Many, if not most, Java developers already know JavaScript (or ECMAScript;
JavaScript is an ECMAScript dialect owned by Netscape) thanks to our
forced interactions with web browsers. The question is, how would a system
administrator run this script?

The solution, of course, is the jrunscript utility that ships
with the JDK, as shown in Listing 2:

Listing 2.
jrunscript

Note that you could also use a for loop to execute the script
a given number of times before quitting. Basically,
jrunscript lets you do almost everything you normally would
do with JavaScript. The only exception is that the environment is not a
browser, so there's no DOM. The top-level functions and objects available
are therefore slightly different.

Because Java 6 ships with the Rhino ECMAScript engine as a part of the JDK,
jrunscript can execute any ECMAScript code that is fed to it,
either from a file (as shown here) or in a more interactive shell
environment called a REPL ("Read-Evaluate-Print-Loop"). Just run
jrunscript by itself to access the REPL shell.

2. Accessing Java objects from a
script

Being able to write JavaScript/ECMAScript code is nice, but we don't want
to have to rebuild everything we use in the Java language from scratch
— that would defeat the purpose. Fortunately, anything using the
Java Scripting API engines has full access to the entire Java ecosystem
because, at heart, everything is still Java bytecode. So, going back to
the earlier problem, we could launch processes from the Java platform
using the traditional Runtime.exec() call, as shown in
Listing 3:

Listing 3. Runtime.exec() launches
jmap

The arguments array is the ECMAScript standard built-in
reference to the arguments passed to this function. In the case of the
top-level script environment, this is the array of arguments passed to the
script itself (the command-line parameters). So, in Listing 3, the script
is expecting an argument containing the VMID of the Java process to
map.

Alternately, we could take advantage of the fact that jmap is
a Java class and just call its main() method, like in Listing
4. This approach eliminates the need to "pipe" the Process
object's in/out/err streams.

3. Calling into scripts from Java
code

Calling Java objects from a script is only half of the story: The Java
scripting environment also provides the ability to invoke scripts from
within Java code. Doing so just requires instantiating a
ScriptEngine, then loading the script in and evaluating it,
as shown in Listing 5:

The eval() method can also operate against a straight
String, so the script needn't come from a file on the
filesystem — it can come from a database, the user, or even be
manufactured within the application based on circumstance and user
action.

4. Binding Java objects into script
space

Just invoking a script isn't enough: Scripts often want to interact with
objects created from within the Java environment. In these cases, the Java
host environment must create objects and bind them so that they're easy
for the script to find and use. This is a task for the
ScriptContext object shown in Listing 6:

Accessing the bound object is straightforward — the name of the
bound object is introduced at the script level as a member of the global
namespace, so using the Person object from Rhino is as easy
as Listing 7:

5. Compiling oft-used
scripts

The drawback of scripting languages has always been performance. The reason
is that in most cases, the scripting language is interpreted "on the fly"
and loses time and CPU cycles having to parse and validate text as it's
executed. Many scripting languages running on the JVM ultimately transform
the incoming code into Java bytecode, at least the first time the script
is parsed and validated; this on-the-fly compilation gets tossed when the
Java program shuts down. Keeping frequently used scripts in bytecode form
would give us a sizable performance boost.

We can do this in a natural and meaningful way with the Java Scripting API.
If the ScriptEngine returned implements the
Compilable interface, then the compile method on that
interface can be used to compile the script (passed in as either a
String or a Reader) into a
CompiledScript instance, which can then be used to
eval() the compiled code over and over again, with different
bindings. This is shown in Listing 8:

In most cases, the CompiledScript instance should be held
somewhere in long-term storage (servlet-context, for example)
in order to avoid recompiling the same script over and over again. Should
the script contents change, however, you must create a new
CompiledScript to reflect the change; once compiled, the
CompiledScript no longer executes the original script file's
contents.

In conclusion

The Java Scripting API is a huge step forward in extending the reach and
functionality of Java programs, and it brings the productivity gains
associated with scripting languages into the Java environment. Coupled
with jrunscript— which is obviously not all that
complex of a program to write —javax.script gives Java
developers the benefits of scripting languages like Ruby (JRuby) and
ECMAScript (Rhino) without having to surrender the ecosystem and
scalability of the Java environment.

"JavaScript EE, Part 3: Use Java scripting API with
JSP" (Andrei Cioroianu, developerWorks, June 2009): Learn more
about combining JavaScript with the Java platform and how to build Ajax
user interfaces that remain functional when JavaScript is disabled in the
web browser.

JDK Tools and Utilities: Learn about the experimental monitoring
and troubleshooting tools discussed in the 5 things focus on
performance monitoring, including jmap.

The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.