1. Overview

Python is an increasingly popular programming language, particularly in the scientific community due to its rich variety of numerical and statistical packages. Therefore, it’s not an uncommon requirement to be able to invoke Python code from our Java applications.

In this tutorial, we’ll take a look at some of the most common ways of calling Python code from Java.

2. A Simple Python Script

Throughout this tutorial, we’ll use a very simple Python script which we’ll define in a dedicated file called hello.py:

print("Hello Baeldung Readers!!")

Assuming we have a working Python installation when we run our script, we should see the message printed:

$ python hello.py 
Hello Baeldung Readers!!

3. Core Java

In this section, we’ll take a look at two different options we can use to invoke our Python script using core Java.

3.1. Using ProcessBuilder

Let’s first take a look at how we can use the ProcessBuilder API to create a native operating system process to launch python and execute our simple script:

@Test
public void givenPythonScript_whenPythonProcessInvoked_thenSuccess() throws Exception {
    ProcessBuilder processBuilder = new ProcessBuilder("python", resolvePythonScriptPath("hello.py"));
    processBuilder.redirectErrorStream(true);

    Process process = processBuilder.start();
    List<String> results = readProcessOutput(process.getInputStream());

    assertThat("Results should not be empty", results, is(not(empty())));
    assertThat("Results should contain output of script: ", results, hasItem(
      containsString("Hello Baeldung Readers!!")));

    int exitCode = process.waitFor();
    assertEquals("No errors should be detected", 0, exitCode);
}

In this first example, we’re running the python command with one argument which is the absolute path to our hello.py script. We can find it in our test/resources folder.

To summarize, we create our ProcessBuilder object by passing the command and argument values to the constructor. It’s also important to mention the call to redirectErrorStream(true). In case of any errors, the error output will be merged with the standard output. 

This is useful as it means we can read any error messages from the corresponding output when we call the getInputStream() method on the Process object. If we don’t set this property to true, then we’ll need to read output from two separate streams, using the getInputStream() and the getErrorStream() methods.

Now, we start the process using the start() method to get a Process object. Then we read the process output and verify the contents is what we expect.

As previously mentioned, we’ve made the assumption that the python command is available via the PATH variable.

3.2. Working With the JSR-223 Scripting Engine

JSR-223, which was first introduced in Java 6, defines a set of scripting APIs that provide basic scripting functionality. These methods provide mechanisms for executing scripts and for sharing values between Java and a scripting language. The main objective of this standard was to try to bring some uniformity to interoperating with different scripting languages from Java.

We can use the pluggable script engine architecture for any dynamic language provided it has a JVM implementation, of course. Jython is the Java platform implementation of Python which runs on the JVM.

Assuming that we have Jython on the CLASSPATH, the framework should automatically discover that we have the possibility of using this scripting engine and enable us to ask for the Python script engine directly.

Since Jython is available from Maven Central, we can just include it in our pom.xml:

<dependency>
    <groupId>org.python</groupId>
    <artifactId>jython-slim</artifactId>
    <version>2.7.3b1</version>
</dependency>

Likewise, it can also be downloaded and installed directly.

Let’s list out all the scripting engines that we have available to us:

ScriptEngineManagerUtils.listEngines();

If we have the possibility of using Jython, we should see the appropriate scripting engine displayed:

...
Engine name: jython
Version: 2.7.2
Language: python
Short Names:
python
jython

Now that we know we can use the Jython scripting engine, let’s go ahead and see how to call our hello.py script:

@Test
public void givenPythonScriptEngineIsAvailable_whenScriptInvoked_thenOutputDisplayed() throws Exception {
    StringWriter writer = new StringWriter();
    ScriptContext context = new SimpleScriptContext();
    context.setWriter(writer);

    ScriptEngineManager manager = new ScriptEngineManager();
    ScriptEngine engine = manager.getEngineByName("python");
    engine.eval(new FileReader(resolvePythonScriptPath("hello.py")), context);
    assertEquals("Should contain script output: ", "Hello Baeldung Readers!!", writer.toString().trim());
}

As we can see, it is pretty simple to work with this API. First, we begin by setting up a ScriptContext which contains a StringWriter. This will be used to store the output from the script we want to invoke.

We then use the getEngineByName method of the ScriptEngineManager class to look up and create a ScriptEngine for a given short name. In our case, we can pass python or jython which are the two short names associated with this engine.

As before, the final step is to get the output from our script and check it matches what we were expecting.

4. Jython

Continuing with Jython, we also have the possibility of embedding Python code directly into our Java code. We can do this using the PythonInterpretor class:

@Test
public void givenPythonInterpreter_whenPrintExecuted_thenOutputDisplayed() {
    try (PythonInterpreter pyInterp = new PythonInterpreter()) {
        StringWriter output = new StringWriter();
        pyInterp.setOut(output);

        pyInterp.exec("print('Hello Baeldung Readers!!')");
        assertEquals("Should contain script output: ", "Hello Baeldung Readers!!", output.toString()
          .trim());
    }
}

Using the PythonInterpreter class allows us to execute a string of Python source code via the exec method. As before we use a StringWriter to capture the output from this execution.

Now let’s see an example where we add two numbers together:

@Test
public void givenPythonInterpreter_whenNumbersAdded_thenOutputDisplayed() {
    try (PythonInterpreter pyInterp = new PythonInterpreter()) {
        pyInterp.exec("x = 10+10");
        PyObject x = pyInterp.get("x");
        assertEquals("x: ", 20, x.asInt());
    }
}

In this example we see how we can use the get method, to access the value of a variable.

In our final Jython example, we’ll see what happens when an error occurs:

try (PythonInterpreter pyInterp = new PythonInterpreter()) {
    pyInterp.exec("import syds");
}

When we run this code a PyException is thrown and we’ll see the same error as if we were working with native Python:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
ImportError: No module named syds

A few points we should note:

  • As PythonIntepreter implements AutoCloseable, it’s good practice to use try-with-resources when working with this class
  • The PythonInterpreter class name does not imply that our Python code is interpreted. Python programs in Jython are run by the JVM and therefore compiled to Java bytecode before execution
  • Although Jython is the Python implementation for Java, it may not contain all the same sub-packages as native Python

5. Apache Commons Exec

Another third-party library that we could consider using is Apache Common Exec which attempts to overcome some of the shortcomings of the Java Process API.

The commons-exec artifact is available from Maven Central:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-exec</artifactId>
    <version>1.3</version>
</dependency>

Now let’s how we can use this library:

@Test
public void givenPythonScript_whenPythonProcessExecuted_thenSuccess() 
  throws ExecuteException, IOException {
    String line = "python " + resolvePythonScriptPath("hello.py");
    CommandLine cmdLine = CommandLine.parse(line);
        
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream);
        
    DefaultExecutor executor = new DefaultExecutor();
    executor.setStreamHandler(streamHandler);

    int exitCode = executor.execute(cmdLine);
    assertEquals("No errors should be detected", 0, exitCode);
    assertEquals("Should contain script output: ", "Hello Baeldung Readers!!", outputStream.toString()
      .trim());
}

This example is not too dissimilar to our first example using ProcessBuilder. We create a CommandLine object for our given command. Next, we set up a stream handler to use for capturing the output from our process before executing our command.

To summarize, the main philosophy behind this library is to offer a process execution package aimed at supporting a wide range of operating systems through a consistent API.

6. Utilizing HTTP for Interoperability

Let’s take a step back for a moment and instead of trying to invoke Python directly consider using a well-established protocol like HTTP as an abstraction layer between the two different languages.

In actual fact Python ships with a simple built-in HTTP server which we can use for sharing content or files over HTTP:

python -m http.server 9000

If we now go to http://localhost:9000, we’ll see the contents listed for the directory where we launched the previous command.

Some other popular frameworks we could consider using for creating more robust Python-based web services or applications are Flask and Django.

Once we have an endpoint we can access, we can use any one of several Java HTTP libraries to invoke our Python web service/application implementation.

7. Conclusion

In this tutorial, we’ve learned about some of the most popular technologies for calling Python code from Java.

As always, the full source code of the article is available over on GitHub.

Course – LS (cat=Java)

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.