Invoke dynamic languages dynamically, Part 1: Introducing the Java scripting API

Use the javax.script API to change a running application

You don't need to compile dynamic languages into Java™ bytecode to use them with a Java application. Dozens of scripting languages can be called at run time from Java code in a simple, unified way using the scripting package added to Java Platform, Standard Edition 6 (Java SE) and backward compatible with Java SE 5. Part 1 of this two-part article introduces the Java scripting API's features. It uses a simple Hello World application to show how Java code can execute scripting code and how scripts can in turn execute Java code. Part 2 dives deeper into the power of the Java scripting API.

Share:

Tom McQueeney (tom.mcqueeney@gmail.com), Lead Technical Consultant, Idea Integration

Tom McQueeneyTom McQueeney is a Java developer and application architect for Idea Integration, a national consulting firm. He enjoys integrating dynamic languages such as Ruby and Groovy into Java projects to make development faster, more efficient, and more fun. He is a past speaker at O'Reilly's OSCON and ApacheCon Europe, and he formerly served as president of the Denver Java Users Group. He and his wife, also a certified Java architect, live in Washington, D.C.



04 September 2007

Also available in Chinese Russian Japanese

Java developers know that the Java language isn't always the best language for every task. This year's releases of 1.0 versions of JRuby and Groovy have heightened interest in adding dynamic languages to Java applications. Groovy, JRuby, Rhino, Jython, and other open source projects make it possible to write code in a so-called scripting language and run it within the JVM (see Resources). Yet integrating these languages with Java code typically has meant learning each interpreter's unique API and features.

The javax.script package added to Java SE 6 makes integrating dynamic languages easier. It provides a single, simple way to invoke dozens of scripting languages using a small set of interfaces and concrete classes. But the Java scripting API is about more than making it easier to script parts of an application; the scripting package lets you read and invoke external scripts at run time, which means you can alter those scripts dynamically to change a running application's behavior.

This article, the first in a two-part series, introduces the features and key classes of the Java scripting API using a Hello World style application. Part 2 presents a more realistic sample application that shows off more of the scripting API's power. That application uses the scripting API to create a dynamic rules engine in which the rules are encoded as external scripts written in Groovy, JavaScript, and Ruby. The rules decide whether applicants for a home loan qualify for particular mortgage products. Externalizing the rules with the Java scripting API allows the rules to change and new mortgage products to be added at run time.

The Java scripting API

Scripting versus dynamic

The term scripting typically refers to languages that run from an interpreter shell without a separate compilation step. The term dynamic typically refers to languages that wait until run time to determine a variable's type or an object's behavior and contain features such as closures and continuations. Several general-purpose programming languages fit both terms. Scripting language is favored here because this article focuses on the Java Scripting API, not because the languages mentioned lack dynamic features.

The scripting package was added to the Java language in December 2006 to provide a unified way to integrate scripting languages into a Java application. For language developers, the package provides a way to write the necessary glue code for letting their languages be called dynamically from a Java application. For Java developers, the scripting package provides a small set of classes and interfaces that let scripts written in any number of languages be invoked using a common API. The scripting package is thus similar to the Java Database Connectivity (JDBC) package in that different languages -- like different databases -- can be integrated into the Java platform using a consistent interface.

Previously, invoking a scripting language dynamically from Java code involved using unique classes provided by each language's distribution or using Apache's Jakarta Bean Scripting Framework (BSF). BSF unifies a handful of scripting languages behind a single API (see Resources). More than two dozen scripting languages, including AppleScript, Groovy, JavaScript, Jelly, PHP, Python, Ruby, and Velocity, can be integrated into Java code using the Java SE 6 scripting API, which is based largely on BSF.

The scripting API provides two-way visibility between Java applications and external scripts. Your Java code can not only invoke external scripts, but it can also give those scripts access to selected Java objects. An external Ruby script, for example, can invoke methods on Java objects and access their properties, allowing those scripts to add behavior to a running application not anticipated at development time.

Invoking external scripts can be used for run-time application enhancement, configuration, monitoring, or other run-time manipulation, such as changing business rules without stopping the application. Possible uses of the scripting package include:

  • Writing business rules in a language simpler than the Java language without resorting to a full-blown rules engine.
  • Creating a plug-in architecture to let users customize an application on the fly.
  • Integrating an existing script into your Java application, such as a script that processes or transforms text files.
  • Externally configuring an application's run-time behavior using a full-blown programming language instead of a properties file.
  • Adding a domain-specific language to a Java application.
  • Using a scripting language while prototyping a Java application.
  • Writing application test code in a scripting language.

Hello, scripting world

The HelloScriptingWorld class, which you can download along with all the code for this article (see Download), demonstrates key features of the Java scripting package. It uses hard-coded snippets of JavaScript as the sample scripting language. The class's main() method, shown in Listing 1, creates a JavaScript script engine and then calls five methods (shown in later listings) that highlight features of the scripting package:

Listing 1. HelloScriptingWorld main method
public static void main(String[] args) throws ScriptException, NoSuchMethodException {

ScriptEngineManager scriptEngineMgr = new ScriptEngineManager();
ScriptEngine jsEngine = scriptEngineMgr.getEngineByName("JavaScript");

    if (jsEngine == null) {
        System.err.println("No script engine found for JavaScript");
        System.exit(1);
    }

    System.out.println("Calling invokeHelloScript...");
    invokeHelloScript(jsEngine);

    System.out.println("\nCalling defineScriptFunction...");
    defineScriptFunction(jsEngine);

    System.out.println("\nCalling invokeScriptFunctionFromEngine...");
    invokeScriptFunctionFromEngine(jsEngine);

    System.out.println("\nCalling invokeScriptFunctionFromJava...");
    invokeScriptFunctionFromJava(jsEngine);

    System.out.println("\nCalling invokeJavaFromScriptFunction...");
    invokeJavaFromScriptFunction(jsEngine);
}

The main() method's chief function is to get an instance of a javax.script.ScriptEngine (the first two statements in Listing 1). A script engine loads and executes scripts in a particular language. It is the most frequently used and vital class in the Java scripting package. You obtain a script engine from a javax.script.ScriptEngineManager (first statement). A typical program needs to obtain only one instance of a script engine, unless many scripting languages are used.

ScriptEngineManager class

ScriptEngineManager is probably the only concrete class in the scripting package you'll use regularly; most of the rest are interfaces. And it may be the only class from the scripting package you'll instantiate directly (or indirectly through a dependency-injection mechanism such as the Spring Framework.) A ScriptEngineManager can return a script engine in one of three ways:

  • By engine or language name, as in Listing 1 requesting a JavaScript engine.
  • By a file extension commonly used for scripts in that language, such as .rb for a Ruby script.
  • By a MIME type the script engine has declared it knows how to process.

Why JavaScript in this example?

This Hello World example uses JavaScript partly because the code is easy to understand, but mostly because the Java 6 runtime environments supplied by Sun Microsystems and BEA Systems come bundled with a JavaScript interpreter based on the Mozilla Rhino open source JavaScript implementation. With JavaScript, you don't need to add script-language JAR files to the classpath.

ScriptEngineManagers find and create script engines indirectly. That is, when script-engine managers are instantiated, they use a service-discovery mechanism added in Java 6 to find all registered javax.script.ScriptEngineFactory implementations in the classpath. These factory classes come packaged with Java scripting API implementations; you may never need to deal with these factory classes directly.

Once the ScriptEngineManager has found all the script-engine factory classes, it queries each one to find out whether it can create a script engine of the requested type -- in the case of Listing 1, a JavaScript engine. If a factory says it can create a script engine for the desired language, the manager asks the factory to create one, which it returns to the caller. The manager returns null if it finds no factory for the requested language, which the code in Listing 1 guards against by checking for a null return value.

ScriptEngine interface

As I mentioned, your code uses a ScriptEngine instance to execute your scripts. A script engine acts as a mediator between your scripting code and an underlying language interpreter or compiler that ultimately executes the code. That way, you don't need to know which classes each interpreter uses to execute code. For example, a script engine for JRuby might pass your code to an instance of JRuby's org.jruby.Ruby class first to compile the script into an intermediate form, then call it again to evaluate the script and process return values. The script-engine implementation hides the details, including how the interpreter shares class definitions, application objects, and input/output streams with Java code.

Figure 1 shows the general relationship among your application, the Java scripting API, a ScriptEngine implementation, and a scripting-language interpreter. You can see that your application relies only on the scripting API, which provides the ScriptEngineManager class and the ScriptEngine interface. The ScriptEngine implementation component handles the specifics of using a particular scripting-language interpreter.

Figure 1: Scripting API component relationships
Scripting API component diagram

You might be wondering where you get the necessary JAR files for the script-engine implementation and language interpreters. The best place to look first for a script-engine implementation is the open source Scripting project hosted by java.net (see Resources). There you'll find script-engine implementations for many languages and links to script-engine implementations hosted elsewhere. The Scripting project also provides links to download the interpreters for scripting languages it supports.

In Listing 1, the main() method passes the ScriptEngine to each method for use in evaluating that method's JavaScript code. The first method is shown in Listing 2. The invokeHelloScript() method calls the script engine's eval method to evaluate and execute the given string of JavaScript code. The ScriptEngine interface defines six overloaded eval() methods that accept a script to evaluate as either a string or a java.io.Reader object, which is commonly used for reading scripts from external sources such as files.

Listing 2. The invokeHelloScript method
private static void invokeHelloScript(ScriptEngine jsEngine) throws ScriptException {
    jsEngine.eval("println('Hello from JavaScript')");
}

Script execution context

Example scripts in the HelloScriptingWorld application output to the console using the JavaScript println() function, but you have full control over input and output streams. Script engines provide the option of changing a script execution's context, which means you can change the streams used for standard input, standard output, and standard error, as well as define which global variables and Java objects are available to the script being executed.

The JavaScript in the invokeHelloScript() method outputs Hello from JavaScript to the standard output stream, which in this case is the console window. (Listing 6 contains the complete output from running the HelloScriptingWorldApplication.)

Note that this and other methods in the class declare that they throw javax.script.ScriptException. This checked exception -- the only one defined by the scripting package -- indicates that the engine failed to parse or execute the given code. All script-engine eval() methods declare they throw a ScriptException, so your code needs to handle it appropriately.

Listing 3 shows two related methods: defineScriptFunction() and invokeScriptFunctionFromEngine(). The defineScriptFunction() method also invokes the script engine's eval() method with a hard-coded snippet of JavaScript. But note that this method does nothing more than define a JavaScript function, sayHello(). No code is being executed. The sayHello() function takes one parameter, which it outputs to the console in the following println() statement. The script engine's JavaScript interpreter adds this function to its global environment, making it available in subsequent eval calls, which occurs (not surprisingly) in the invokeScriptFunctionFromEngine() method.

Listing 3. The defineScriptFunction and invokeScriptFunctionFromEngine methods
private static void defineScriptFunction(ScriptEngine engine) throws ScriptException {
    // Define a function in the script engine
    engine.eval(
        "function sayHello(name) {" +
        "    println('Hello, ' + name)" +
        "}"
    );
}

private static void invokeScriptFunctionFromEngine(ScriptEngine engine)
  throws ScriptException
{
    engine.eval("sayHello('World!')");
}

This pair of methods demonstrates that script engines can maintain a state of application components and make that state available during subsequent calls to the engine's eval() method. The invokeScriptFunctionFromEngine() method takes advantage of the maintained state by invoking the sayHello() JavaScript function defined in the previous call to eval().

Many script engines maintain state of global variables and functions between calls to eval(). However, it is important to note that the Java scripting API does not require script engines to supply this feature. The JavaScript, Groovy, and JRuby script engines used in this article do maintain state between calls to eval().

Listing 4 is a variation of the preceding example. The invokeScriptFunctionFromJava() method differs in that it calls the sayHello() JavaScript function without using ScriptEngine's eval() method or JavaScript code. Instead, it uses the Java scripting API's javax.script.Invocable interface to call a function maintained by the script engine. The invokeScriptFunctionFromJava() method casts the script-engine object to the Invocable interface and then calls the invokeFunction() method on that interface to call the sayHello() JavaScript function with the given parameter. If the called function returns a value, the invokeFunction() method returns it wrapped as a Java Object type.

Listing 4. The invokeScriptFunctionFromJava method
private static void invokeScriptFunctionFromJava(ScriptEngine engine)
  throws ScriptException, NoSuchMethodException
{
Invocable invocableEngine = (Invocable) engine;
    invocableEngine.invokeFunction("sayHello", "from Java");
}

Advanced script invocation using proxies

An advanced use of Invocable is available when a script function or method implements a Java interface. The Invocable interface defines a getInterface() method that takes an interface as a parameter and returns a Java proxy object that implements the supplied interface. Once you obtain the proxy object from the script engine, you can treat it as a regular Java object. Methods invoked on the proxy are delegated to the script engine for execution by the scripting language.

Notice that Listing 4 contains no JavaScript. The Invocable interface allows Java code to invoke a script function without knowing its implementation language. The invokeFunction() method throws a java.lang.NoSuchMethodException if the script engine can't find a function with the given name or parameter types.

The Java scripting API doesn't require script engines to implement the Invocable interface. Realistically, the code in Listing 4 should have used the instanceof operator to ensure the script engine implements the Invocable interface before making the cast.

Invoking Java methods from scripting code

The examples in Listing 3 and Listing 4 show how Java code can invoke functions or methods defined in a scripting language. You're probably wondering whether code written in a scripting language can in turn invoke methods on Java objects. It can. The invokeJavaFromScriptFunction() method in Listing 5 shows how to give a script engine access to Java objects and how scripting code can invoke methods on that Java object. Specifically, the invokeJavaFromScriptFunction() method uses the script engine's put() method to supply an instance of the HelloScriptingWorld class itself to the engine. Once the engine has access to the Java object using the name provided in the call to put(), scripting code in the call to the eval() method uses it.

Listing 5. The invokeJavaFromScriptFunction and getHelloReply methods
private static void invokeJavaFromScriptFunction(ScriptEngine engine)
  throws ScriptException
{
    engine.put("helloScriptingWorld", new HelloScriptingWorld());
    engine.eval(
        "println('Invoking getHelloReply method from JavaScript...');" +
        "var msg = helloScriptingWorld.getHelloReply(vJavaScript');" +
        "println('Java returned: ' + msg)"
    );
}

/** Method invoked from the above script to return a string. */
public String getHelloReply(String name) {
    return "Java method getHelloReply says, 'Hello, " + name + "'";
}

The JavaScript code contained in Listing 5's call to the eval() method uses the HelloScriptingWorld Java object by accessing it with the variable name helloScriptingWorld provided in the call to the script engine's put() method. The second line of JavaScript code invokes the getHelloReply() public Java method, also shown in Listing 5. The getHelloReply() method returns the Java method getHelloReply says, 'Hello, <parameter>' string. The JavaScript code in the eval() method assigns the Java return value to the msg variable, then prints the value to the console.

Java object translation

When a scripting engine makes a Java object available to scripts running in the engine's environment, the engine needs to wrap it in an object type applicable to the scripting language. The wrapping might involve appropriate object-value conversions, such as allowing a Java Integer object to be used directly in scripting-language mathematical expressions. An exploration of how Java objects are translated to scripting objects is specific to each scripting language engine and beyond this article's scope. You should be aware the translation is occurring, however, so you can test to ensure the scripting language you use performs conversions in ways you expect.

ScriptEngine.put and its associated get() method are the primary ways of sharing objects and data between Java code and scripts running within the script engine. (See Script-execution scope later in this article for an expanded discussion of this topic.) When you call the engine's put() method, the script engine associates the second parameter (any Java object) with the given string key. Most script engines make those Java objects accessible to scripts under the given variable name. Script engines are free to take liberties with the names you pass to the put() method. For instance, the JRuby script engine makes helloScriptingWorld available to Ruby code under the global $helloScriptingWorld variable to fit Ruby syntax for global variables.

The script engine's get() method retrieves values available within the scripting environment. Generally, every global variable and function in the script environment is accessible from Java code through the get() method. But only those Java objects explicitly shared with the script engine using put() are accessible to scripts.

This ability for external scripts to access and manipulate Java objects in a running application is a powerful technique for extending your Java programs' functionality. (The example in Part 2 exploits this technique.)


Running the HelloScriptingWorld application

You can run the HelloScriptingWorld application by downloading and building the source code. The .zip file contains both an Ant script and a Maven build file to help compile and run the sample application. Follow these steps:

  1. Download the .zip file.
  2. Create a new directory, such as java-scripting, and unzip the file you downloaded in Step 1 into that directory.
  3. Open a command-line shell and change to that directory.
  4. Run ant run-hello.

You should see console output from Ant similar to that shown in Listing 6. Notice that the defineScriptFunction() method produces no output because it defines but doesn't call the JavaScript function.

Listing 6. Output from running HelloScriptingWorld
Calling invokeHelloScript...
Hello from JavaScript

Calling defineScriptFunction...

Calling invokeScriptFunctionFromEngine...
Hello, World!

Calling invokeScriptFunctionFromJava...
Hello, from Java

Calling invokeJavaFromScriptFunction...
Invoking getHelloReply method from JavaScript...
Java returned: Java method getHelloReply says, 'Hello, JavaScript'

Java 5 compatibility

Java SE 6 introduced the Java scripting API, but you can also run the API with Java SE 5. You just need to supply an implementation of the missing javax.script package classes. Fortunately, an implementation is available from the Java Specification Request 223 reference implementation (see Resources for a download link). JSR 223 defines the Java scripting API.

If you download the JSR 223 reference implementation, unzip the file and place the script-api.jar, script-js.jar, and js.jar files in your classpath. These files supply the script API, the JavaScript script-engine interface, and the JavaScript script engine that come bundled with Java SE 6.


Script-execution scope

How you expose Java objects to scripts running inside a script engine is more configurable than just calling the engine's get() and put() methods. When you call get() or put() on a script engine, the engine retrieves or stores the requested key in a default instance of the javax.script.Bindings interface. (The Bindings interface is just a Map interface that enforces keys to be strings.)

When your code calls a script engine's eval() method, the engine's default bindings of keys and values are used. You can, however, supply your own Bindings object on an eval() call to restrict which variables and objects are visible to that specific script. The call would look like eval(String, Bindings) or eval(Reader, Bindings). To help you create customized Bindings, script engines supply a createBindings() method that returns an empty Bindings object. Calling eval with a Bindings object temporarily hides Java objects previously stored in the engine's default bindings.

To add to the story, script engines contain two default bindings: the "engine scope" bindings used by calls to get() and put() and "global scope" bindings the engine can use to look up objects if they're not found in the "engine scope" bindings. The word can is operative. Script engines are not required to make the global bindings accessible to scripts. Most script engines do.

The design purpose behind the "global scope" bindings is to share objects among different script engines. Every script engine returned by a ScriptEngineManager instance is seeded with the same "global scope" bindings object. You can retrieve an engine's global bindings using the getBindings(ScriptContext.GLOBAL_SCOPE) method and set the global bindings for an engine using setBindings(Bindings, ScriptContext.GLOBAL_SCOPE).

ScriptContext is an interface that defines and controls a script engine's run-time context. A script engine's ScriptContext contains the "engine" and "global" scope bindings, as well as the input and output streams the engine uses for standard input and output operations. You can get and manipulate a script engine's context using the engine's getContext() method.

Scripting API concepts such as scope, bindings, and context can be confusing at first because of their overlapping meanings. The source-code download file for this article contains a JUnit test file called ScriptApiRhinoTest in the src/test/java directory to help explain these concepts through Java code.

What's next?

Now that you have a basic grounding in the Java scripting API, Part 2 of this article refines and expand upon that knowledge using a more realistic sample application. That application uses external script files written in a combination of Groovy, Ruby, and JavaScript to define business logic that can be changed at run time. As you'll see, defining the business rules in a scripting language makes the rules easier to write, and probably easier for a nonprogrammer, such as a business analyst or rule writer, to read.


Download

DescriptionNameSize
Source code and JAR filej-javascripting1.zip116KB

Resources

Learn

  • "Invoke dynamic languages dynamically, Part 2": The second half of this series demonstrates how external scripts written in Ruby, Groovy, and JavaScript can be executed and altered at run time to change business logic without stopping and restarting the application.
  • JSR-223: Scripting for the Java Platform: This Java specification request defines the Java scripting API added to Java SE 6.
  • Java Scripting Programmer's Guide: Sun's JDK 6 documentation includes a programmer's guide to the Java scripting API.
  • Jakarta Bean Scripting Framework: The BSF project provides the foundation for the Java scripting API.
  • "Making Scripting Languages JSR-223-Aware" (Thomas Künneth, java.net, September 2006): This article shows how to make a scripting language available to the Java scripting API when no existing script engine is available.
  • Groovy: Learn more about Groovy, an agile dynamic language for the Java platform, from the project Web site.
  • Practically Groovy: Dig deeper into Groovy with this developerWorks series.
  • Mozilla Rhino: Documentation and other resources for learning more about the JavaScript engine that comes bundled with Java runtimes available from Sun Microsystems and BEA Systems.
  • JRuby: JRuby is a pure-Java implementation of the Ruby programming language. The project Web site features the latest project news and other resources for using JRuby.
  • Browse the technology bookstore for books on these and other technical topics.
  • developerWorks Java technology zone: Hundreds of articles about every aspect of Java programming.

Get products and technologies

  • Groovy: Download the latest Groovy release.
  • Java SE 6 and BEA JRockit: Development kits and runtime environments that natively support the Java scripting API and include a slimmed-down version of the Mozilla Rhino JavaScript engine.
  • Scripting project: The open source Scripting project at java.net provides script-engine interfaces for about two dozen languages and links to other known Java scripting engines. To use one of these scripting languages, install the script-engine implementation JAR file from this project along with the scripting-language interpreter JAR file itself.
  • Scripting for the Java Platform 1.0 Reference Implementation: The JSR-223 reference implementation provides three JAR files that allow the Java scripting API to run with Java SE 5. Download and unzip the sjp-1_0-fr-ri.zip file and place the js.jar, script-api.jar, and script-js.jar files in your classpath.

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


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.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=253190
ArticleTitle=Invoke dynamic languages dynamically, Part 1: Introducing the Java scripting API
publish-date=09042007