Choosing a Java scripting language: Round two

If you are considering hooking a scripting interpreter into your Java application, the hardest part is choosing which one to use

Three years ago, I wrote an article for JavaWorld called "Java Scripting Languages: Which Is Right for You?" When I collected the interpreters to compare, I tried to choose ones that seemed a good fit for a demanding commercial application. Ideally, I wanted an interpreter that would ease the extension of the application's user interface, have readable scripting code, be reliable, fast, well-supported, well-documented, and complete. At that time, I narrowed the list down to Jacl, Jython, Rhino, and BeanShell.

A lot has changed over the last three years. Instead of a handful of choices, there are now more than a dozen scripting languages either under active development or already available for use. The list of solid choices is bigger than it was three years ago and now includes Groovy, JudoScript, Pnuts, and JRuby, in addition to Jacl, Jython, Rhino, and BeanShell. We could consider other scripting interpreters beyond this group, but this list is large enough for developers to find what they're looking for.

I wanted to benchmark all of these interpreters to see if the performance for Jacl, Jython, Rhino, and BeanShell had improved since 2002, and to find out how Groovy, JudoScript, JRuby, and Pnuts compare with them. I thought it might be interesting to see what's unique about the different scripting interpreters and if any have particular strengths or weaknesses.

Risky business

In my previous article, I called out some of the well-known benefits of scripting interpreter integration and described the risks you take when integrating with one. Here, I boil that information down to the most important points and update the information based on my experience since writing the last article.

The benefits of Java scripting interpreters are substantial. For one thing, scripting languages can be simpler to code in than Java. Scripts also make it possible to drive and extend your program's application logic and user interface. They can be run directly against your Java application's class interfaces, which is very powerful. This can make it easier to write test drivers against your program much more quickly than if you had to code and compile unit tests against the Java classes themselves. Also, if users take the time to extend your application by writing scripts for it, they're making an investment in your tool—and that can give your application an edge against the competition.

You do open yourself up to a certain amount of risk by integrating a Java scripting interpreter into your application, though. The two biggest risks are that the interpreter will be orphaned or that you will discover a fatal flaw in the interpreter after you ship a product with it.

Most of the interpreters are actively maintained and updated through an open source model, and, in those cases, you can probably rely on experts for help on working around problems you find, patching the interpreter, or getting the bug-fix you need included in a future release. That's a safe bet, but not a guarantee. If you are seriously considering using a specific interpreter, take a look at the activity on the development site to get a feel for how the code is evolving and look at the message boards to see if user questions are getting answered. That will give you a feel for how well supported the code really is.

Another way to protect yourself is to thoroughly test any scripting interpreter you plan to use. The distributions for some interpreters include a set of unit tests. When you test the scripting interpreter integration with your application, those unit tests can serve as part of the larger test suite you'll want to put together. When you test the integration between the interpreter and the application, you have your work cut out for you, because scripting interpreters are so flexible and expose so much functionality to the developer. You're making an investment by spending time on quality assurance early on, instead of when the application is in production or when customers need a critical bug fixed.

The new list of contenders

If you're looking for a scripting interpreter, there are plenty to choose from. Some interpreters have been written to support existing languages like Ruby, Python, JavaScript, Java, and Tcl. Other interpreters, like JudoScript, Groovy, and Pnuts, have chosen their own language syntax that is similar to Java but, in some ways, different. One of the biggest choices you'll have to make when you compare different interpreters is deciding what scripting language syntax is a good fit for you. Technology choices like this one, where developers' personal preferences come into play, can spark heated discussions among software development teams. Perhaps the information in this article can help settle some arguments. I collected the most recent releases of eight different scripting interpreters for comparison. The interpreters and their version numbers are listed below. If you are not familiar with these interpreters, I've also included a thumbnail sketch of the functionality and development activity on each one below.

Scripting languageVersion numberDescription
Jacl1.3.1The Java implementation of the Tcl interpreter. If you want to use Tk constructs in your scripts to create user interface objects, take a look at the Swank project for a set of Java classes that wrap Java's Swing widgets into Tk. Jacl has been around for quite a while and is still being actively worked on.
Jython2.1The Java implementation of the Python interpreter. One concern I have about this interpreter is that a new release has not been issued in quite a while. However, there are signs on the Jython Website indicating plans to reverse this trend, apparently with funding to back up the work.
Rhino1.6.1The Java implementation of the JavaScript interpreter. It also supports compiling the scripts into classfiles. The most recent Rhino release came out just a few months ago and added support for XML.
JRuby0.8The Java implementation of the Ruby interpreter. JRuby is under active development. I tested version 0.8, which worked well.
BeanShell2.0 beta 2BeanShell is a Java source interpreter. BeanShell continues to evolve and add new features. Version 2.0 provides full support for interpreting ordinary Java source code.
Groovy1.0 beta 9There actually are some groovy things about this new language that add some of the features of Python and Ruby to a Java-like syntax. You can compile the scripts directly into Java classfiles. Groovy plug-ins are available for numerous IDEs, and a JSR (Java Specification Request) committee is working on the language specification for Groovy.
JudoScript0.9JudoScript has a JavaScript-like programming syntax that is easy to learn and use. One of the stated goals mentioned in the JudoScript FAQ is to "support object-level, OS-level, and application-level scripting." I tested version 0.9, which worked well.
Pnuts1.1 beta 2Pnuts has a Java-like programming syntax and seems to be actively developed and released. You can compile the scripts directly into Java classfiles.

The first benchmark: Performance

For the first benchmark, I wrote equivalent scripts for each of the interpreters to do a set of simple tasks and then timed how long it took each interpreter to run the scripts. My benchmark scripts stick to basic operations like looping, comparing integers against lots of other integers, and allocating and initializing large one- and two-dimensional arrays. The benchmarking scripts for each of the languages and the Java programs to run them can be downloaded from Resources.

The most useful information that comes out of the benchmarking tests is an apples-to-apples comparison of how quickly the interpreters complete some extremely simple tasks. If throughput is a high priority for you, then the benchmarking numbers are useful.

I tried to code each test as similarly as possible for each of the scripting languages. The tests were run using Java 1.4.2 on a Toshiba Tecra 8100 laptop with a 700 MHz Pentium III processor and 256 MB of RAM. I used the default heap size when invoking the Java Virtual Machine.

In the interest of giving you some perspective for how fast or slow these numbers are, I also coded the test cases in Java and ran them using Java 1.4.2.

Here are the four performance tests:

  • Count from 1 to 1 million
  • Compare 1 million integers for equality
  • Allocate and initialize a 100,000 element array
  • Allocate and initialize a 500-by-500 element array

Have things improved since 2002?

Before I show you which of the interpreters is the fastest, I want you to look at the bar chart in Figure 1 that lists results for the most time-consuming task: comparing 1 million integers for equality. For the four scripting interpreters covered in the 2002 article, I show the times with the old release of the interpreter running on the 1.3.1 JVM and the interpreter's most current release running on the 1.4.2 JVM. Interestingly, when I tested Jython, I used the same version of the scripting interpreter I tested in my previous article, but the results show about a 25 percent improvement in speed.

Figure 1. Click on thumbnail to view full-sized image.

Since I ran the tests on the exact same hardware I used for my previous tests, I'd say that the 1.4.2 JVM substantially cut the time needed to run these benchmarks. Now look at what happened with Rhino, BeanShell, and Jacl. The newest release of Rhino running on 1.4.2 was 86 percent faster than the old release running on 1.3.1. For BeanShell, the improvement was about 70 percent, and for Jacl, about 76 percent. That's quite an improvement.

Total time for the four tasks

Since several of the interpreters closely resembled each other in terms of speed (at least for my benchmarks), I summed up the times for the interpreters on the four benchmark tasks and show the cumulative results in Figure 2.

Figure 2

The checkered flag

For these simple test cases, Rhino, Pnuts, and Jython were consistently the fastest, followed closely by Groovy, then JudoScript, and the others.

Whether these performance numbers matter to you depends on the kind of things you want to do with your scripting language. If you have many hundreds of thousands of iterations to perform in a scripting function and users of your application will be waiting on the result, then you might want to either focus your attention on the interpreters at the fast end of the spectrum, or plan on implementing your most demanding algorithms in Java code instead of in scripting code. If your scripts have few repetitive functions to run, then the relative differences in speeds between these interpreters is a lot less important. A faster computer can also make a big difference in these numbers.

Another thing worth pointing out is that even the fastest of the interpreters takes about 40 times as long to run these simple programs as compiled Java code does. If speed is really at a premium for you, you might decide that it makes more sense to code certain algorithms in Java instead of using scripting code.

Some scripting interpreters support the compilation of scripts directly down to bytecode. I was curious about how much of a performance difference this would make, so I tried another test. I used the script compiler for Rhino to turn the benchmark scripts into bytecode. Then I reran the whole benchmark suite 10 times using scripts and 10 times using scripts converted to bytecode. Surprisingly, compiling the scripts to bytecode only shaved about 10 percent off the time it took to run the four programs in the benchmark suite. I initially thought that the JVM invocation must be taking the lion's share of the time to run the benchmarks, but further examination showed that the invocation of the JVM itself only accounted for about 20 percent of the total time required to run the suite. It seems that compilation of simple scripts makes a positive difference, but isn't necessarily a silver bullet for dramatically improving performance. Perhaps with longer or more compute-intensive scripts, you would see different results.

The second benchmark: Integration difficulty

The integration benchmark covers two tasks. One task shows how much code it takes to instantiate the scripting language interpreter and run a scripting file. The name of the script to run is passed in as a command line argument to the ScriptRunner class. This yields a straightforward but useful program for testing scripts. Most distributions for the interpreters include much nicer console applications for interactive testing of scripts. I wanted to write a simple program from scratch to see if the documentation made using the interpreter easy or hard.

The second task writes a script that instantiates a Java JFrame, populates it with a JTree, and displays the JFrame.

These tasks are simple but have some value since they show how easy it is to get started using the interpreters and also what a script written for the interpreter will look like when you use it to call Java class code. I present these examples as just one way of getting started. They aren't meant to be bulletproof or even particularly complete; they provide just the essentials to get something working in that scripting language. Once you have that going, you can really start investigating the features important to your application.

In the previous article, I showed examples for Jacl, Jython, Rhino, and BeanShell. In the interest of brevity, I refer the reader to that article for those source code examples. If you would like to download the examples for all eight of the different languages, please refer to this article's Resources section.

Groovy

To integrate Groovy into your Java application, you create a Binding and instantiate a GroovyShell on that Binding. Then you ask the interpreter to evaluate the source at the filepath you provided on the command line. Here's what the code looks like:

 

import groovy.lang.GroovyShell; import groovy.lang.Binding; import groovy.lang.Closure; import java.io.File;

public class ScriptRunner { public static void main (String[] args) throws Exception {

GroovyShell interp = new GroovyShell(new Binding()); try { File f = new File(args[0]); interp.evaluate(f); } catch(Exception e) { System.out.println("Exception while sourcing file " + args[0]); e.printStackTrace(); } } }

The Groovy script to create a JTree, put it in a JFrame, and show the JFrame looks like this:

 

import javax.swing.JFrame import javax.swing.JTree import javax.swing.WindowConstants

class SimplestGUI { void buildIt() { frame = new JFrame("Simplest GUI"); frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); tree = new JTree(); frame.getContentPane().add(tree); frame.pack(); frame.show(); } static void main(args) { b = new SimplestGUI() b.buildIt() } }

JudoScript

To integrate JudoScript into your Java application, you create a JudoEngine and then ask that JudoEngine to evaluate the file at the path specified:

 

import com.judoscript.JudoEngine;

public class ScriptRunner { public static void main (String[] args) throws Exception { JudoEngine je = new JudoEngine(); try { je.runScript(args[0], args, null); } catch(Exception e) { System.out.println("Exception while sourcing file " + args[0]); e.printStackTrace(); } } }

The JudoScript script to create a JTree, put it in a JFrame, and show the JFrame looks like this:

 

const #JFrame = java::javax.swing.JFrame; const #JTree = java::javax.swing.JTree; frame = new java::#JFrame('Simple JudoScript GUI'); tree = new java::#JTree(); frame.getContentPane().add(tree); frame.pack(); frame.setVisible(true);

gui::events { <frame : Window : windowClosing>: exit 0; }

Pnuts

To integrate Pnuts into your Java application, you create a Context, then ask Pnuts to load the file at the path specified into that Context. Here's what the code looks like to make that happen:

 

import pnuts.lang.*; import java.io.*;

public class ScriptRunner { public static void main(String[] args) throws IOException { try{ Context context = new Context(); Pnuts.loadFile(args[0], context); } catch (Exception e) { System.out.println(e); } } }

The Pnuts script to create a JTree, put it in a JFrame, and show the JFrame looks like this:

 

import("javax.swing.JFrame") import("javax.swing.JTree")

frame = new JFrame(); frame.setDefaultCloseOperation(JFrame::EXIT_ON_CLOSE) tree = new JTree(); frame.getContentPane().add(tree); frame.pack(); frame.show();

JRuby

To integrate JRuby into your Java application, you create an instance of Ruby and then ask it to load the file at the path specified:

 

import org.jruby.*; import java.io.*;

public class ScriptRunner { public static void main (String[] args) throws Exception { Ruby runtime = org.jruby.Ruby.getDefaultInstance(); try { File f = new File(args[0]); runtime.loadFile(f, false); } catch(Exception e) { System.out.println("Exception while sourcing file " + args[0]); e.printStackTrace(); } } }

The JRuby script to create a JTree, put it in a JFrame, and show the JFrame looks like this:

 

require 'java' module JavaSwing include_package "javax.swing" include_package "java.awt.event" end

frame = JavaSwing::JFrame.new("Simple Ruby App") tree = JavaSwing::JTree.new() frame.getContentPane().add(tree) frame.setDefaultCloseOperation(JFrame::EXIT_ON_CLOSE) frame.pack() frame.setVisible(true)

Lessons learned

In the trivial example I used, you can see that integration is simple and that the different interpreters complete the tasks similarly. The scripts for the different languages are also similar. I think that you need to look at bigger, more complex examples than we have space for here to see the differences among the interpreters. To pick the best one for your application, you'll have to look deeper at the language syntax and feature set that the scripting interpreters support and decide for yourself if you like what you see.

In the process of doing these integrations, I took notes about what seemed most important, or interesting, or noteworthy about the different interpreters and have listed those notes below. Obviously, these thoughts represent my personal opinion. Software developers are free-thinking people, so I know you'll come to your own conclusions when you do your own due diligence.

Jacl supports the Tcl syntax, which is not difficult to learn. The Tcl programming language is already well known to many software developers, and numerous books and online tutorials are available on Tcl programming. One recommended interactive tutorial on Tcl programming is the TclTutor program, which takes you through the steps of learning to program in Tcl interactively.

JRuby supports the Ruby syntax. The RubyCentral homepage states that Ruby "combines the object-oriented power of the classic OO language Smalltalk with the expressiveness and convenience of a scripting language such as Perl." I wasn't familiar with Ruby, but the developerWorks article "Take a Shine to JRuby" (September 2004) gives you a taste of JRuby's strengths and shows a more elaborate JRuby programming example than I have in this article.

BeanShell 2.0 release notes state that BeanShell is now capable of interpreting ordinary Java source files, which is impressive. I tested this functionality and found it to work fine for the simple programs I asked it to load and run. If you want to learn more about BeanShell programming, check out the BeanShell tutorial on the BeanShell Website.

Jython supports the Python syntax. If you are unfamiliar with Python and don't have one of the Jython books handy, one place to start learning about Python is with Guido van Rossum's Python tutorial. One feature of the Python syntax is that it doesn't use braces to group statements together, it uses a combination of the colon character (:) and space indentation. This might seem like it could lead to confusion in the code, but consistent usage of spaces instead of tabs is all that's required to keep things clear. If you are looking for in-depth programming advice on Jython, several good books are available.

Rhino supports the JavaScript syntax, which is straightforward and very readable. This language has been well documented and is already known to most Web developers. That might be all the reason you need to make this the obvious choice for you. The documentation is well done. The presence of a debugger is a strong selling point for those writing complex or lengthy scripts. The Mozilla Website has a tutorial on embedding Rhino.

JudoScript syntax is easy to pick up from the documentation and seems to include everything you need to get a complicated programming job done: threads, function pointers, and exception-handling are all supported and well-explained. To get a sense of JudoScript's strengths, read the JudoScript White Paper. For more information, the JudoScript Website offers tutorials on the JudoScript language and on how to embed JudoScript in a Java application.

Groovy syntax is Java-like and readable. One interesting feature of Groovy is that it supports closures, which let you define a piece of programming code without declaring a class or a method. You can assign that piece of programming code (the closure) to a variable if you want, pass that variable through other functions, and call that closure whenever you need by just executing the call() function against it. As I mentioned earlier, there is a JSR committee working on the language specification for Groovy, which is good. One caveat is that the Groovy syntax is still evolving, so there is a chance that Groovy scripts written today might need to be rewritten when the committee nails down the final syntax. If you are interested in reading more about the language, several good articles cover the topic, including "Groovy, Java's New Scripting Language" (O'Reilly, September 2004) and "Groovy, Scripting for Java" (Object Computing, 2003).

The only hiccup I found with the Groovy interpreter occurred when I broke an assignment statement between two lines as shown below. In this case, the interpreter seemed to ignore the part of the expression that incremented the variable by 1, like this:

    i = i
       + 1;

But doing things the following way worked just fine:

    i = i +
    1;

I was running a beta version of 1.0, which means the parser in that build isn't totally bulletproof. It's beta, right? Groovy is getting a lot of development attention and no doubt this glitch will be addressed in a future build.

Pnuts syntax also looks similar to Java. One interesting and useful feature of the Pnuts syntax is the module concept. Reusable scripts in Pnuts can be divided into modules, which are similar to Java packages. Those modules, once loaded, can have naming conflicts or collisions resolved in a script by adding the use(module name) call, which tells the interpreter which module takes precedence over other modules for finding a binding for a variable or function name. The Pnuts language syntax seems easy to pick up from the documentation, and the debugger is useful.

If you are looking for more information comparing the programming interfaces of the interpreters, you might look at "Embedding APIs of Java-Based Scripting Engines" which compares the Jacl, BeanShell, Jython, Rhino, Groovy, and Pnuts interpreters performing common tasks such as evaluating an expression from a string, catching an exception thrown by a script, or setting or getting the value of a scripting variable.

The third benchmark: Licensing

Although these interpreters are easy to download and play with, what do their licensing rules say about you embedding it in a commercial application that you sell to customers?

The answer is that licensing is not a problem for any of these libraries. The way I read the license agreements, in each case, the user must abide by the GNU Lesser General Public license or an equivalent. This means that you can ship the libraries with your application even if your application is not free. However, you cannot strip the copyright headers out of their source files or script files and may have to clarify to users that the rights to the scripting interpreter bundled with your application belong to someone else. Of course, if you're embedding the interpreter in a commercial application, the smart thing is for you and your company attorney to carefully look at the licensing agreement.

Final thoughts

If you need to integrate scripting support code into your Java application, my advice is to pick a single scripting interpreter and standardize on it. Costs are associated with each scripting language you support in your product, so don't make more work for yourself by trying to hook more than one scripting interpreter into your application. When adding scripting support, you can further simplify things by using an interpreter written in Java instead of a native interpreter such as Python or Tcl. That will make your solution more portable and simplify the integration task between your Java program and the interpreter.

If your developers or customers are already familiar with a particular scripting language like Tcl, Python, Ruby, or JavaScript, obviously you'll want to look seriously at the interpreter that supports that language (Jacl, Jython, JRuby, or Rhino, respectively). If you don't have that constraint, you will have a harder choice. In some ways, it's a bit like going to a new car lot. All of the choices will work, so you are left with balancing the differences between the alternatives, such as performance or options.

Some of these interpreters perform simple tasks faster than others. Some are updated and released more often, or have better documentation or debugging facilities than others. Some support compilation of scripts to bytecode. Some have language syntaxes that will either appeal to a developer or not, depending on preference, programming background, and the specific task at hand. As with most engineering tasks, you have to define your requirements and then investigate some to come up with the right answer.

If I had to distill what I learned from working with the different interpreters down to a bare minimum, here's what I'd say:

Jacl eases your entrance into scripting. Integration is simple, and if you need your scripts to be written in Tcl, it works well. If speed is your top priority, you may want to consider other choices.

Jython is one of the fastest scripting interpreters. From looking at the Website, it seems that Jython development is about to renew, which is good news. There are several good books on Jython. If you like the Python language, Jython is a solid choice.

BeanShell is not as fast as the quickest of the interpreters, but the 2.0 release supports loading of ordinary Java source, which is a strong selling point. I tried loading and running several Java source programs as scripts and found BeanShell to work fine, which is impressive. The libraries are well-organized and make integration simple. If performance is not the single most important criteria for your scripting interpreter and you want to write Java scripts, look at BeanShell.

Rhino is the winner of the performance benchmarking test and also supports Java-like syntax in its scripting. There are plenty of books on JavaScript available. Rhino appears to be well supported, and the distribution includes a useful debugger.

Pnuts is one of the fastest scripting interpreters. I am impressed with the completeness of the documentation, the simple usability of the debugger, and how straightforward it is to get things working with Pnuts. If the Pnuts syntax is a good fit for your needs, this interpreter deserves a good look.

JudoScript is in the middle of the pack for the performance benchmark, but supports a JavaScript-like syntax that is easy to learn. The documentation seems to be thorough and well organized, and the distribution includes lots of script examples. I ran version 0.9, which seemed to work well.

JRuby brings the feature set of Ruby to the table. It isn't the fastest of the interpreters, but if Ruby syntax and functionality is important to you, take a look at this interpreter. I ran version 0.8, which seemed to work just fine for my simple tests.

Groovy has attracted quite a bit of attention and development effort in the Java community. It is one of the fastest interpreters on the benchmarking tests, even without compiling the scripts down to classfiles. The syntax is Java-like and supports some powerful features that Java doesn't. This is an interesting addition to the programmer's toolkit that has a lot of potential.

David Kearns is a Sun Certified Programmer and Developer for Java 2. He has 15 years of experience designing and developing electronic design automation (EDA) tools and object-oriented application frameworks in C++ and Java, including 5 years of experience building commercial Java applications for configuration management, process control, tool scripting, and design simulation waveform viewing. Kearns works at Mentor Graphics Corporation in Wilsonville, Oregon as a staff engineer. His current project at Mentor Graphics is developing a high-performance schematic entry tool.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies