In this post I’ll demonstrate how to embed logic provided by a java application into a python PyQt5 UI based application. The motivation for this project arose from the need to be able to port legacy code in the form of an old Eclipse Rich Client Platform application to a contemporary technology with as little effort as possible.
We will develop a simple application to add two integer numbers and display the sum. The logic shall be provided by a java jar, where as the GUI and the application shall be implemented in python. Please refer to this post to see how to design the UI using PyQt5 designer and make some changes in the python application to perform the logic in python. In the following, we’ll extend the sourcecode from the prior post in order to have the logic handled by an external jar.
What do you need?
Since we’re building a python UI-based application, you need the same preparations as described in this post. Then, in order to be able to develop a java JAR containing some logic, you need at least a JDK, a text editor and again some minimal shell knowledge. I’ll describe the steps assuming you’re using some sort of an Eclipse IDE, i.e. the Spring Tool Suite from Pivotal, which can be downloaded here. If you don’t want to fuzz around with Java, you can also just download the files in the following sections or use the procedure described in order to integrate business logic from existing JARs into your own python application.
What’s the Idea behind py4j
The key element which allows us to invoke Java code contained in JAR-files from python is a package called py4j, a great project developed by Barthélémy Dagenais. It consists of a python-Side package which can be obtained by invoking the ‘pip install py4j’ command from your shell, as well as of a Java library, which can be included via pom dependency or downloaded from the maven repository in the most recent version.
There are different approaches to integrate Java based logic into python applications. A simplified overview is given here. However, in my eyes py4j’s approach is the most flexible and best performing one and the way of choice, if you have processing intensive Java code and comparatively few python / Java invocations. Here’s how py4j works:
The functionality you want to acces via python is exposed via an (internal) service. For this reason you’ll either have to include the py4j library in your Java project and provide a wrapping service using some very simple boilerplate code (details can be found here). The second more comfortable way is to use pythons py4j helper methods in order to startup a virtual machine and install a service and accessing and instantiating classes from a jar provided. This is also the way we’ll demonstrate in this tutorial.
The following steps you have to follow in order to be able to delegate your business logic to Java. In our example application, we want the addition of two integer numbers to be done by our Java service developed above, rather than by the python statement ‘c = a + b’.
Of course, one would never want to embed a Java service just to perform a simple addition, but it allows us to demonstrate in an easy manner, how Java business logic can be invoked from python. Now we have the basic background knowledge to start our endeavour:
1) Provide the JAR containing your Business Logic
We first create a simple plain old java project containing just one single class. In eclipse you would choose ‘New…’, then ‘Java-Project’ and generate a JAR from it. We’ll develop a Method, which simply adds two numbers. Since we don’t have any extraordinary dependencies, that’s it. Then create our only class named ‘AdditionService’ in the file ‘AdditionService.java’ containing the following code:
public class AdditionService {
public int add(int a, int b) {
int c = a + b;
System.out.println(a + "+" + b + "=" + c);
return c;
}
}
That’s it. Now, we want to create a jar we can include in our python application. We don’t need a runnable jar, a simple jar containing the compiled class would be enough. In Eclipse, this works as follows:
- Right click on the Java project in the project explorer
- Choose ‘Export’
- Choose ‘JAR file’
- Choose ‘Next’ and provide the output location, where you want the jar to be written to; create a folder named ‘bin’ in your root project folder
Now, we’ve got the Business-Logic ready to be integrated within our python application.
2. Provide the py4j JAR
To enable the python py4j library starting up a Java service and accessing our JARs business logic, we need to provide the py4j Java library in the form of a py4j JAR. You can get the it here. At the time of this writing, the most recent version is 10.9.2 and the library’s name is ‘py4j-0.10.9.2.jar’. Again we place it in the ‘bin’ folder on the top level of our project folder.
3. Provide a Java Runtime
In order to enable our app to launch Java code, we need to provide a Java runtime environment. Since we want our python application to be independent of local installation or configuration settings, we should include a complete JRE in our project. Still today, there are quite some good sources to find free JREs. I usually refer to the ones from adopt open JDK. After selecting the target OS and processor architecture (x86 = 32 Bit or x64 = 64 Bit), make sure you get a JRE, rather than a JDK, since we just want to be able to run Java code and do not want to use it to develop code.
After download extract the JDK and move it into your project folder. In my situation I ended up with a folder named ‘jdk-11.0.11+9-jre’ within my project folder.
Hint: For deployment reasons please keep in mind that your customers might have other system configurations and CPU architectures than you!
4. Embed the Java-Logic into your Python app
Now, we’ll again focus on our python script ‘addition_dialog.py’, which we have created in the former post mentioned above. In this step, we’ll modify the code in order to have java perform our business logic – that is, add the two numbers and return their sum.
First we modify the main-method in order to get an instance of our AdditionService from our JAR developed above:
if __name__ == "__main__":
import sys
# launch py4j gateway to lib-jar and expose it as a service
gateway = JavaGateway.launch_gateway(jarpath='bin/py4j-0.10.9.2.jar', classpath='bin/addition.jar', java_path='jdk-11.0.11+9-jre/bin/java.exe', die_on_exit=True, use_shell=False)
# create instance of the class of interest within the jar
svc = gateway.jvm.AdditionService()
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
# now startup QT-app and pass reference to java-service
ui.setupUi(MainWindow, svc)
MainWindow.show()
sys.exit(app.exec_())
The big magic gets triggered within the JavaGateway.launch_gateway – call. This causes py4j to expose the logic contained in our selfmade jar in a service listening on port 25333. Then on the following line an instance of our AdditionService is created and stored in the svc variable. Since we want to access the addition service from our UI, we have to pass the reference to the AdditionServer in the ui.setupUi() call as shown above.
Now, let’s adapt the ui.setupUi() Method to accept our server’s reference by just extending its signature by the svc argument:
def setupUi(self, MainWindow, svc):
MainWindow.setObjectName("MainWindow")
...
The last necessary change is to replace the addition statement with the call to the AdditionService:
def press_it(self, svc):
a = int(self.varATextBox.text())
b = int(self.varBTextBox.text())
# invoke the Java AdditionService rather than directly adding numbers
c = svc.add(a,b)
self.resultLbl.setText("Laus: " + str(c))
Congrats! That’s it! You’ve just successfully finished your first python application including an embedded Java library to perform some business logic! The complete source can be obtained here.