As the author of Puffinplot, a Java
program whose users are probably, in general, more comfortable with
Python,
I have a strong interest in convenient ways to connect the two languages.
PuffinPlot currently embeds Jython as a scripting language, but it’s not
an ideal solution, since it’s restricted to Python 2 and doesn’t provide
access to the enormous CPython ecosystem. I’ve therefore been evaluating
other options for connecting the two languages. For my current use case,
the main requirement is to be able to make use of classes in a jar from
Python – preferably without any special tooling on the Java side and with
full support for CPython packages on the Python side – but I’ve listed all
the software that I could find that facilitates any kind of integration
between the two languages.
This article includes brief code snippets to demonstrate the syntax used
for each of the bridge packages. Full, runnable Java and Python demos are
available in this GitHub repository.
GraalVM
Initial release: 2019
Latest release as of 2020-01-09: 2019-12-24
GraalVM is a JVM and JDK which supports various new and exciting features,
including multi-language interoperability within the VM – thus, like
Jython, it offers a reimplementation of Python rather than a bridge to an
existing CPython runtime. In the longer term, it seems a very promising
prospect for Java-Python interoperability. However, as of 2019, GraalVM is
not yet always a viable drop-in replacement for the standard JVM (for
instance, Windows support is currently described as ‘experimental’), and
GraalVM Python is definitely not yet a drop-in replacement for CPython.
The reference manual
states:
GraalVM provides an early-stage implementation of Python 3.7. Warning:
The support for Python is experimental. Experimental features might
never be included in a production version, or might change
significantly before being considered production-ready.
A primary goal is to support SciPy and its constituent libraries. At
this point, the Python implementation is made available for
experimentation and curious end-users.
Javabridge
Initial release: 2014
Latest release as of 2020-01-09: 2018-09-21
A Python wrapper for the Java Native Interface. The JavaBridge website
describes it like this:
The javabridge Python package makes it easy to start a Java virtual
machine (JVM) from Python and interact with it. Python code can
interact with the JVM using a low-level API or a more convenient
high-level API.
The documentation seems a little sparse, but for calling jarred Java code
from Python it seems to work smoothly. A comment on this issue mentions
that the ‘two most useful classes’ are JWrapper and JClassWrapper.
Of the two, JClassWrapper seems more convenient for instantiating a
Java class from Python.
Installation: pip3 install javabridge.
Example of use:
javabridge.start_vm(run_headless=True, class_path=javabridge.JARS +
[jar_path])
try:
# Bind a Java variable and run a script that uses it.
print(javabridge.run_script(
'java.lang.String.format("Hello, %s!", greetee);',
dict(greetee="world")))
# Wrap a Java object and call some of its methods.
array_list = javabridge.JWrapper(javabridge.make_instance(
"java/util/ArrayList", "()V"))
array_list.add("ArrayList item 1")
array_list.add("ArrayList item 2")
print("ArrayList size:", array_list.size())
print("First ArrayList item:", array_list.get(0))
# Wrap a Java object from our jar and call a method.
main1 = javabridge.JWrapper(javabridge.make_instance(
"net/talvi/pythonjavabridgedemos/Main",
"(Ljava/lang/String;)V", "Alice"))
print(main1.greet("Hello"))
# Wrap a Java object using JClassWrapper (no signature required)
main2 = javabridge.JClassWrapper(
"net.talvi.pythonjavabridgedemos.Main")("Bob")
print(main2.greet("Hi there"))
print("2 + 2 =", main2.add(2, 2))
finally:
javabridge.kill_vm()
jep
Initial release: 2015
Latest release as of 2020-01-09: 2019-08-30
Jep uses JNI to embed a CPython interpreter in the JVM (thus it cannot be
used from a standalone Python runtime). CPython extensions and Cython
modules are supported, but not guaranteed to work in all cases.
Python installation: Make sure JAVA_HOME is set, then pip3 install
jep. The jep executable assumes that the Jep jar is installed
globally, so it will not work if the Jep package is installed using pip’s
user scheme (pip3 install --user jep).
Maven configuration for Java installation:
<dependency>
<groupId>black.ninia</groupId>
<artifactId>jep</artifactId>
<version>3.9.0</version>
</dependency>
Classes can be imported and used directly from the Java namespace. The
Python Jep package must be installed, but does not need to be explicitly
invoked in the Python script. The jep executable can be used to run a
Python script directly, or the script can be invoked from Java. When
running via the jep executable, third-party jars can be used by
setting the CLASSPATH variable, but they cannot be specified within
the Python script itself. Here is an example of usage from the Python
side:
#!/usr/bin/env jep
from java.util import *
cal = GregorianCalendar(2008, Calendar.DECEMBER, 3)
weekday = cal.getDisplayName(Calendar.DAY_OF_WEEK,
Calendar.LONG_FORMAT,
Locale.ENGLISH)
print("Python 3 was released on a {}.".format(weekday))
Java code to call a Python script in Jep:
try (Interpreter interp = new SharedInterpreter()) {
interp.set("variable_to_pass", someJavaVariable);
interp.runScript(script_filename);
} catch (JepException ex) {
System.err.println("Jep exception:\n"
+ ex.getMessage());
System.exit(1);
}
jpy
Initial release: 2014
Latest release as of 2020-01-09: 2018-02-08 on GitHub, 2014-07-24 on PyPI
A bidirectional bridge with a focus on ‘maximum data transfer speed
between the two languages’, written in support of the ESA’s SNAP toolbox. jpy is functional but seems a
little unpolished (installation is by building from source, and
documentation is somewhat sparse).
Installation: clone the repository, then
export JDK_HOME=<your-jdk-dir>
export JAVA_HOME=$JDK_HOME
python3 setup.py build maven bdist_wheel
This produces a .whl archive in the dist subdirectory, which can
be installed with pip3. The build process also produces a jar file,
which is placed in a subdirectory of the build directory. The project
publishes pre-built jars to SNAP’s repository at
http://nexus.senbox.net/, but it’s unclear whether this repository
will remain available indefinitely.
Use from Python:
jpy.create_jvm(["-Djava.class.path=" + jar_path])
StringBuilder = jpy.get_type("java.lang.StringBuilder")
sb = StringBuilder()
sb.append("Demonstration of ")
sb.append("StringBuilder")
print(sb.toString())
Main = jpy.get_type("net.talvi.pythonjavabridgedemos.Main")
main = Main("Bob")
print(main.greet("Wotcha"))
For calling Python from Java, the documentation suggests that something
like the following ought to work.
PyLib.startPython();
PyObject.executeCode("print('Hello!')", PyInputMode.STATEMENT);
PyLib.stopPython();
In practice, quite a lot of set-up is needed to invoke Python from Java
via jpy. The jpy jar must of course be available at compile and run time.
In addition, at runtime, the paths to jpy’s shared libraries must be
specified. The Javadoc says that they should be passed via a configuration
file in the user’s home directory; in practice, this doesn’t seem to work,
but examination of the source code reveals that they can be passed via
system properties. Doing this allows jpy to find the library, but it
crashes at once with a java.lang.UnsatisfiedLinkError due to an
undefined symbol PyFloat_Type.
JPype
Initial release: 2005
Latest release as of 2020-01-09: 2019-12-17
Summary, from the project page:
JPype is a Python module to provide full access to Java from within
Python. It allows Python to make use of Java only libraries…
This is achieved not through re-implementing Python, as Jython has
done, but rather through interfacing at the native level in both
virtual machines. This shared memory based approach achieves decent
computing performance, while providing the access to the entirety of
CPython and Java libraries.
JPype has a long and slightly convoluted history. First conceived in (as
far as I can tell) 2004, it had a few releases up to 2007, then went
rather quiet for a few years. The old project site at SourceForge appears
entirely dead, but a continuation on GitHub, dating from 2012, seems to be
in good health these days.
Installation: pip3 install JPype1
Example Python script:
jpype.startJVM(jpype.getDefaultJVMPath(),
"-ea",
"-Djava.class.path=" + jar_path,
convertStrings=False)
jpype.java.lang.System.out.println("This was printed via System.out.println.")
main = jpype.JPackage("net").talvi.pythonjavabridgedemos.Main("Jeff")
print(main.greet("Morning"))
jpype.shutdownJVM()
Jython
Initial release: 1997 (as JPython)
Latest release as of 2020-01-09: 2017-07-01
Not a ‘bridge’ as such, but an implementation of Python on the JVM. This
has the clear advantage of allowing Python support without any non-Java
dependencies: a Java application can bundle Jython in order to support
Python out of the box.
The main disadvantage is equally clear: the CPython ecosystem is one of
Python’s main attractions, and it is not available from Jython. In recent
years, Jython has also suffered from a slow pace of development: the last
release, version 2.7.1, was in 2017, and there is no clear timeline for
Python 3 support. This will become increasingly awkward now that CPython
2.7 has reached its official end-of-life on 1 January 2020. The JyNI project aims to allow the use of CPython extensions
from Jython, but its development appears to have stalled: the last
release, v2.7-alpha.5, was in 2017 and the last commit was in 2018.
Jython can be used from within a Java project via the Java scripting API,
or as a standalone script interpreter using the jython launch script
or by running the jar.
Interactive installation: download the installer jar and exectute it: java -jar
jython-installer-2.7.1.jar
Use without installation: download the standalone jar.
Maven dependency:
<dependency>
<groupId>org.python</groupId>
<artifactId>jython-standalone</artifactId>
<version>2.7.1</version>
</dependency>
Example of Jython use from Java:
try(PythonInterpreter interpreter = new PythonInterpreter()) {
interpreter.exec("print('Hello world!')");
}
final ScriptEngine scriptEngine =
new ScriptEngineManager().getEngineByName("python");
scriptEngine.put("variable_to_pass", someJavaVariable);
try (Reader reader = new FileReader(script_filename)) {
scriptEngine.eval(reader);
}
Example of Java use from a standalone Jython script:
# Demonstrate use of classes from the Java runtime.
from java.util import ArrayList
al = ArrayList()
al.add(2)
al.add(2)
print "2 + 2 =", al.stream().mapToInt(lambda x: x).sum()
# Jython lets us add jar files to sys.path.
sys.path.append(jar_path)
# Import a Java class from the jar file added above.
from net.talvi.pythonjavabridgedemos import Main
# Demonstrate instantiation and use of a class from a third-party jar.
my_main = Main("Medea")
print my_main.greet("Good health")
The script can be run with the jython executable (which can also be
specified on a shebang line), or by executing the Jython jar explicitly:
java -jar jython-standalone-2.7.1.jar script_name.py.
Py4j
Initial release: 2009
Latest release as of 2020-01-09: 2018-10-21
Project description:
Py4J enables Python programs running in a Python interpreter to
dynamically access Java objects in a Java Virtual Machine. Methods
are called as if the Java objects resided in the Python interpreter
and Java collections can be accessed through standard Python
collection methods. Py4J also enables Java programs to call back
Python objects. Py4J is distributed under the BSD license.
One caveat is that using it requires explicit support on the Java side
(see https://www.py4j.org/getting_started.html), so you can't just
drop it in as a shim to use any old JAR from a Python script: the Java
process has to start a Py4J server before any communication can take
place.
Here’s an example of starting the server on the Java side. Any object can
be passed to the GatewayServer constructor as an entry point.
final Main main = new Main("Heliogabalus");
final GatewayServer server = new GatewayServer(main);
server.start(false);
With the server running, a Python script can communicate with the JVM
and with the supplied entry point:
# Demonstrate use of standard Java classes.
cal = gateway.jvm.java.util.\
GregorianCalendar(2008, gateway.jvm.java.util.Calendar.DECEMBER, 3)
weekday = cal.getDisplayName(gateway.jvm.java.util.Calendar.DAY_OF_WEEK,
gateway.jvm.java.util.Calendar.LONG_FORMAT,
gateway.jvm.java.util.Locale.ENGLISH)
print("Python 3 was released on a {}.".format(weekday))
# Demonstrate use of the class provided as an entry point.
print(gateway.entry_point.greet("Salutations"))
# Demonstrate instantiation and use of a third-party class.
main = gateway.jvm.net.talvi.pythonjavabridgedemos.Main("Trismestigus")
print(main.greet("Felicitations"))
It’s also possible to initiate communication from the Java side rather
than the Python side, albeit a little more complicated.
PyJNIus
Initial release: 2012
Latest release as of 2020-01-09: 2019-12-05
PyJNIus uses JNI to allow access to Java classes from Python code.
Installation: pip3 install Cython, followed by pip3 install pyjnius.
Example script:
jnius_config.set_classpath(jar_path)
from jnius import autoclass
Main = autoclass("net.talvi.pythonjavabridgedemos.Main")
main = Main("John")
print(main.greet("G'day"))
print("55 + 89 =", main.add(55, 89))