Moving from Jython to GraalVM

Tim Felgentreff
graalvm
Published in
6 min readJun 18, 2020

--

Jython has always strived to be an easy-to-use integration of Python and
Java. Jython was started by Jim Hugunin in 1997 as “JPython”, and has seen continued development since then. However, development of Jython is remains on the Python 2.7 line, a major release of the Python interpreter which, since the beginning of 2020, is no longer being maintained.

GraalVM comes with a Python 3 implementation that is continuously improving and that through the GraalVM API allows easy integration with existing Java applications. Here is an example:

>>> import java.awt as awt >>> win = awt.Frame()>>> win.setSize(200, 200)>>> win.setTitle("Hello from Python!")>>> win.getSize().toString()'java.awt.Dimension[width=200,height=200]'>>> win.show()

This example works exactly the same on both Jython and Python on GraalVM. Being able to interact directly with Java objects like this can be an immense help for productivity, as many Jython users can attest to. With Python on GraalVM the same is possible. While it’s not a drop-in replacement, we believe that Python on GraalVM offers the easiest migration path to move Jython applications to Python 3.

Let’s go over some of the use cases for Jython and how GraalVM can help you with them.

Using Java from Python

Note: To follow along with these examples, you should use the Jython
compatibility mode of GraalVM Python, enabled using the
--python.EmulateJythonoption like below:

$ graalpy --jvm --experimental-options --python.EmulateJython

Importing

We’ve already seen that we can easily import Java classes from Python. Import statements allow you to import Java classes, but (unlike Jython), only packages in the java namespace can be directly imported. So this works:

import java.lang as lang

But this doesn’t:

import javax.swing as swingfrom javax.swing import *

Instead, you’ll have to import one of the classes you’re interested in directly:

import javax.swing.Window as Window

Basic Object Usage

Constructing and working with Java objects and classes is done with natural
Python syntax. You construct classes by calling them as a function, and methods on them are called as you would expect. The methods of Java objects can also be retrieved and passed around as first class objects (bound to their instance) the same as Python methods:

>>> from java.util import Random>>> rg = Random(99)>>> boundNextInt = rg.nextInt>>> rg.nextInt()1491444859>>> boundNextInt()1672896916

Java-to-Python Types: Automatic Conversion

Method overloads are resolved by matching the Python arguments in a best-effort manner to the available parameter types. This is also when data conversion happens. The goal here is to make using Java from Python as smooth as possible. The matching we do here is similar to Jython, but GraalVM Python uses a more dynamic approach to matching — Python types emulating intor float are also converted to the appropriate Java types. This allows, for example, to use Pandas frames as double[][]or NumPy array elements as int when the elements fit into those Java primitive types.

Automatic conversion of Java types to Python

Note: Python 3 is quite different to Python 2 in some respects, and this
manifests also in these automatic conversions: for example,
byte[] no longer maps tostr, but to the byte type and there is no longer a long type.

Special Jython Modules

We do not offer any of the special Jython modules. We believe most of their
functionality can be expressed just as well using Java API of GraalVM Python. For example, the jarray module on Jython allows construction of primitive Java arrays. This can be achieved as follows on GraalVM Python:

>>> import java>>> java.type("int[]")(10)

Given our automatic conversions, code that only needs to pass a Java array can also use Python types. However, implicitly this may entail a copy of the array data, which can be deceiving when using Java arrays as output parameters:

>>> i = java.io.ByteArrayInputStream(b"foobar")>>> buf = [0, 0, 0]>>> i.read(buf) # buf is automatically copied into to a temporary byte[] array3>>> buf[0, 0, 0] # the temporary byte[] array got lost>>> jbuf = java.type("byte[]")(3)>>> i.read(jbuf)3>>> jbuf[98, 97, 122]

Exceptions from Java

Jython performs automatic mapping from Java exceptions to Python for use in except clauses. This comes with a performance penalty, though, so on
GraalVM Python it is only enabled with the --python.EmulateJython flag mentioned before.
With that flag it works as it does on Jython (only with Python 3 syntax for the
except clause):

>>> import java>>> v = java.util.Vector()>>> try:...    x = v.elementAt(7)... except java.lang.ArrayIndexOutOfBoundsException as e:...    print(e.getMessage())...7 >= 0

Java Collections

Unlike Jython we do not automatically map the Python syntax for accessing
dictionary elements to the java.util mapping and list classes’ get, set,
or put methods. This means that to use these mapping and list clases, you must call the Java methods:

>>> ht = java.util.Hashtable()>>> ht.put("foo", "bar")>>> ht.get("foo")'bar'

Similarly, we also do not (yet) support Python-style iteration of Java java.util.Enumerable, java.util.Iterator, or java.util.Iterable. For
these, you’ll have to use a while loop and use the hasNext() and next()
methods. We are working to remove these inconveniences.

(Not) Inheriting from Java

Inheritance is widely used in Java frameworks, and Jython supports this use-case by allowing Python classes to inherit from Java classes. GraalVM Python, however, does not. One reason for this is GraalVM Native Image. We want to be able to do a closed-world analysis of the all the bytecode that will be executed at run-time, and dynamically creating bytecode and loading it to generate subclasses the way Jython does would prevent us from doing this.

A workaround can be to create a flexible subclass in Java, compile it, and use
delegation instead. Take this example:

package my.python.logging;import java.util.logging.Handler;public class PythonHandler extends Handler {    private final Value pythonDelegate;    public PythonHandler(Value pythonDelegate) {        this.pythonDelegate = pythonDelegate;    }    public void publish(LogRecord record) {        pythonDelegate.invokeMember("publish", record);    }    public void flush() {        pythonDelegate.invokeMember("flush");    }    public void close() {        pythonDelegate.invokeMember("close");    }}

Then you can use it like this in Python:

from java.util.logging import LogManager, Loggerfrom my.python.logging import PythonHandlerclass MyHandler():    def publish(self, logRecord):
print("[python]", logRecord.toString())
def flush(): pass def close(): passLogManager.getLogManager().addLogger(Logger('my.python.logger', None, PythonHandler(MyHandler())))

Embedding Python into Java

The other way to use Jython is to embed it into Java applications. Where above, GraalVM Python offered some measure of compatibility with existing Jython code, we do not offer any in this case. Existing code using Jython depends directly on the Jython package (for example, in the Maven configuration), because the Java code has references to Jython internal classes such as PythonInterpreter. For GraalVM Python, no dependency other than on the GraalVM SDK is required. There are no APIs particular to Python that are exposed, and everything is done through the GraalVM API. It’s important to know that as long as your application is executed on GraalVM with the Python language installed, you can embed Python in your programs. Please refer to our embedding documentation for more details.

Conclusion

Moving from Jython to GraalVM might require some attention to details and understanding of the differences between the two. The move from Python 2 to Python 3 alone is not always trivial in itself and it’s not always possible to offer full compatibility with Jython.

However, making Python in GraalVM stay true to Python 3 will benefit applications (and the community) in the long run. We try to offer the easiest migration path from Jython using the compatibility mode enabled with the —-python.EmulateJython command line option, which should make adapting existing Jython applications for Python on GraalVM as easy as possible.

Despite not being drop-in compatible, the newer Python version, the benefits of the faster GraalVM runtime, the support for native extensions that Jython never supported, as well as the possibility of creating binaries using GraalVM Native Image, GraalVM Python offers a compelling path forward for Jython applications. We have looked at some internal users of Jython and now we’re interested to hear from you about your use-cases! What else would you need to move from Jython to GraalVM?

--

--

Tim Felgentreff
graalvm
Editor for

I am the language lead for the Python implementation on GraalVM. Besides, I collaborate with students and researchers at the HPI Potsdam and the UCT.