Dynamic languages support
This article is part of our Academy Course titled Advanced Java.
This course is designed to help you make the most effective use of Java. It discusses advanced topics, including object creation, concurrency, serialization, reflection and many more. It will guide you through your journey to Java mastery! Check it out here!
Table Of Contents
1. Introduction
In this part of the tutorial our attention will be fully concentrated on the scripting and dynamic languages support in Java. Since Java 7, the JVM has a direct support of modern dynamic (also often called scripting) languages and the Java 8 release delivered even more enhancements into this space.
One of the strength of the dynamic languages is that the behavior of the program is defined at runtime, rather than at compile time. Among those languages, Ruby (https://www.ruby-lang.org/en/), Python (https://www.python.org/) and JavaScript (http://en.wikipedia.org/wiki/JavaScript) have gained a lot of popularity and are the most widely used ones at the moment. We are going to take a look on how Java scripting API opens a way to integrate those languages into existing Java applications.
2. Dynamic Languages Support
As we already know very well, Java is a statically typed language. This means that all typed information for class, its members, method parameters and return values is available at compile time. Using all this details, the Java compiler emits strongly typed byte code which can then be efficiently interpreted by the JVM at runtime.
However, dynamic languages perform type checking at runtime, rather than compile time. The challenge of dealing with dynamically languages is how to implement a runtime system that can choose the most appropriate implementation of a method to call after the program has been compiled.
For a long time, the JVM had had no special support for dynamically typed languages. But Java 7 release introduced the new invokedynamic instruction that enabled the runtime system (JVM) to customize the linkage between a call site (the place where method is being called) and a method implementation. It really opened a door for effective dynamic languages support and implementations on JVM platform.
3. Scripting API
As part of the Java 6 release back in 2006, the new scripting API has been introduced under the javax.script
package. This extensible API was designed to plug in mostly any scripting language (which provides the script engine implementation) and run it on JVM platform.
Under the hood, the Java scripting API is really small and quite simple. The initial step to begin working with scripting API is to create new instance of the ScriptEngineManager
class. ScriptEngineManager provides the capability to discover and retrieve available scripting engines by their names from the running application classpath.
Each scripting engine is represented using a respective ScriptEngine
implementation and essentially provides the ability to execute the scripts using eval()
functions family (which has multiple overloaded versions). Quite a number of popular scripting (dynamic) languages already provide support of the Java scripting API and in the next sections of this tutorial we will see how nice this API works in practice by playing with JavaScript, Groovy, Ruby/JRuby and Python/Jython.
4. JavaScript on JVM
It is not by accident that we are going to start our journey with the JavaScript language as it was the one of the first scripting languages supported by the Java standard library scripting API. And also because, by and large, it is the single programming language every web browser understands.
In its simplest form, the eval()
function executes the script, passed to it as a plain Java string. The script has no state shared with the evaluator (or caller) and is self-contained piece of code. However, in typical real-world applications it is quite rare and more often than not some variables or properties are required to be provided to the script in order to perform some meaningful calculations or actions. With that being said, let us take a look on a quick example evaluating real JavaScript function call using simple variable bindings:
final ScriptEngineManager factory = new ScriptEngineManager(); final ScriptEngine engine = factory.getEngineByName( "JavaScript" ); final Bindings bindings = engine.createBindings(); bindings.put( "str", "Calling JavaScript" ); bindings.put( "engine", engine ); engine.eval( "print(str + ' from ' + engine.getClass().getSimpleName() )", bindings );
Once executed, the following output will be printed on the console:
Calling JavaScript from RhinoScriptEngine
For quite a while, Rhino used to be the single JavaScript scripting engine, available on JVM. But the Java 8 release brought a brand-new implementation of JavaScript scripting engine called Nashorn (http://www.oracle.com/technetwork/articles/java/jf14-nashorn-2126515.html).
From the API standpoint, there are not too many differences however the internal implementation differs significantly, promising much better performance. Here is the same example rewritten to use Nashorn JavaScript engine:
final ScriptEngineManager factory = new ScriptEngineManager(); final ScriptEngine engine = factory.getEngineByName( "Nashorn" ); final Bindings bindings = engine.createBindings(); bindings.put( "engine", engine ); engine.eval( "print(str + ' from ' + engine.getClass().getSimpleName() )", bindings );
The following output will be printed on the console (please notice a different script engine implementation this time):
Calling JavaScript from NashornScriptEngine
Nonetheless, the examples of JavaScript code snippets we have looked at are quite trivial. You could actually evaluate whole JavaScript files using overloaded eval() function call and implement quite sophisticated algorithms, purely in JavaScript. In the next sections we are going to see such examples while exploring other scripting languages.
5. Groovy on JVM
Groovy (http://groovy.codehaus.org) is one of the most successful dynamic languages for the JVM platform. It is often used side by side with Java, however it also provides the Java scripting API engine implementation and could be used in a similar way as a JavaScript one.
Let us make this Groovy example a bit more meaningful and interesting by developing a small standalone script which prints out on the console some details about every book from the collection shared with it by calling Java application.
The Book class is quite simple and has only two properties, author and title:
public class Book { private final String author; private final String title; public Book(final String author, final String title) { this.author = author; this.title = title; } public String getAuthor() { return author; } public String getTitle() { return title; } }
The Groovy script (named just script.groovy) uses some nifty language features like closures and string interpolation to output the book properties to the console:
books.each { println "Book '$it.title' is written by $it.author" } println "Executed by ${engine.getClass().simpleName}" println "Free memory (bytes): " + Runtime.getRuntime().freeMemory()
Now let us execute this Groovy script using Java scripting API and predefined collection of books (surely, all about Groovy):
final ScriptEngineManager factory = new ScriptEngineManager(); final ScriptEngine engine = factory.getEngineByName( "Groovy" ); final Collection< Book > books = Arrays.asList( new Book( "Venkat Subramaniam", "Programming Groovy 2" ), new Book( "Ken Kousen", "Making Java Groovy" ) ); final Bindings bindings = engine.createBindings(); bindings.put( "books", books ); bindings.put( "engine", engine ); try( final Reader reader = new InputStreamReader( Book.class.getResourceAsStream("/script.groovy" ) ) ) { engine.eval( reader, bindings ); }
Please notice that the Groovy scripting engine has a full access to Java standard library and does not require any addition bindings. To confirm that, the last line from the Groovy script above accesses current runtime environment by calling the Runtime.getRuntime()
static method and prints out the amount of free heap available to running JVM (in bytes). The following sample output is going to appear on the console:
Book 'Programming Groovy 2' is written by Venkat Subramaniam Book 'Making Java Groovy' is written by Ken Kousen Executed by GroovyScriptEngineImpl Free memory (bytes): 153427528
It has been 10 years since Groovy was introduced. It quickly became very popular because of the innovative language features, similar to Java syntax and great interoperability with existing Java code. It may look like introduction of lambdas and Stream API in Java 8 has made Groovy a bit less appealing choice, however it is still widely used by Java developers.
6. Ruby on JVM
Couple of years ago Ruby (https://www.ruby-lang.org/en/) was the most popular dynamic language used for web application development. Even though its popularity has somewhat shaded away nowadays, Ruby and its ecosystem brought a lot of innovations into modern web applications development, inspiring the creation and evolution of many other programming languages and frameworks.
JRuby (http://jruby.org/) is an implementation of the Ruby programming language for JVM platform. Similarly to Groovy, it also provides great interoperability with existing Java code preserving the beauty of the Ruby language syntax.
Let us rewrite the Groovy script from the Groovy on JVM section in Ruby language (with name script.jruby) and evaluate it using the Java scripting API.
$books.each do |it| java.lang.System.out.println( "Book '" + it.title + "' is written by " + it.author ) end java.lang.System.out.println( "Executed by " + $engine.getClass().simpleName ) java.lang.System.out.println( "Free memory (bytes): " + java.lang.Runtime.getRuntime().freeMemory().to_s )
The script evaluation codes stays mostly the same, except different scripting engine and the sample books collection, which is now all about Ruby.
final ScriptEngineManager factory = new ScriptEngineManager(); final ScriptEngine engine = factory.getEngineByName( "jruby" ); final Collection< Book > books = Arrays.asList( new Book( "Sandi Metz", "Practical Object-Oriented Design in Ruby" ), new Book( "Paolo Perrotta", "Metaprogramming Ruby 2" ) ); final Bindings bindings = engine.createBindings(); bindings.put( "books", books ); bindings.put( "engine", engine ); try( final Reader reader = new InputStreamReader( Book.class.getResourceAsStream("/script.jruby" ) ) ) { engine.eval( reader, bindings ); }
The following sample output is going to appear on the console:
Book 'Practical Object-Oriented Design in Ruby' is written by Sandi Metz Book 'Metaprogramming Ruby 2' is written by Paolo Perrotta Executed by JRubyEngine Free memory (bytes): 142717584
As we can figure out from the JRuby code snippet above, using the classes from standard Java library is a bit verbose and have to be prefixed by package name (there are some tricks to get rid of that but we are not going in such specific details).
7. Python on JVM
Our last but not least example is going to showcase the Python (https://www.python.org/) language implementation on JVM platform, which is called Jython (http://www.jython.org/).
The Python language has gained a lot of traction recently and its popularity is growing every day. It is widely used by the scientific community and has a large set of libraries and frameworks, ranging from web development to natural language processing.
Following the same path as with Ruby, we are going to rewrite the example script from Groovy on JVM section using Python language (with name script.py) and evaluate it using the Java scripting API.
from java.lang import Runtime for it in books: print "Book '%s' is written by %s" % (it.title, it.author) print "Executed by " + engine.getClass().simpleName print "Free memory (bytes): " + str( Runtime.getRuntime().freeMemory() )
Let us instantiate the Jython scripting engine and execute the Python script above using already familiar Java scripting API.
final ScriptEngineManager factory = new ScriptEngineManager(); final ScriptEngine engine = factory.getEngineByName( "jython" ); final Collection< Book > books = Arrays.asList( new Book( "Mark Lutz", "Learning Python" ), new Book( "Jamie Chan", "Learn Python in One Day and Learn It Well" ) ); final Bindings bindings = engine.createBindings(); bindings.put( "books", books ); bindings.put( "engine", engine ); try( final Reader reader = new InputStreamReader( Book.class.getResourceAsStream("/script.py" ) ) ) { engine.eval( reader, bindings ); }
The following sample output will be printed out on the console:
Book 'Learning Python' is written by Mark Lutz Book 'Learn Python in One Day and Learn It Well' is written by Jamie Chan Executed by PyScriptEngine Free memory (bytes): 132743352
The power of Python as a programming language is in its simplicity and steep learning curve. With an army of Python developers out there, the ability to integrate the Python scripting language into your Java applications as some kind of extensibility mechanism may sound like an interesting idea.
8. Using Scripting API
The Java scripting API is a great way to enrich your Java applications with extensible scripting support, just pick your language. It is also the simplest way to plug in domain-specific languages (DSLs) and allows the business experts to express their intentions in the most convenient manner.
The latest changes in the JVM itself (see please Dynamic Languages Support section) made it much friendlier runtime platform for different dynamic (scripting) languages implementations. No doubts, more and more scripting language engines will be available in the future, opening the door to seamless integration with new and existing Java applications.
9. What’s next
Beginning from this part we are really starting the discussions about advanced concepts of Java as a language and JVM as excellent runtime execution platform. In the next part of the tutorial we are going to look at the Java Compiler API and the Java Compiler Tree API to learn how to manipulate Java sources at runtime.
9. Download Code
This was a lesson of Dynamic Language Support, part 12 of Advanced Java course. You may download the source code here: advanced-java-part-12