Calling Python in Java?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
There are many scenarios where you need to call Python code from a Java application: leveraging a Python machine learning library, reusing an existing Python script, or integrating with a Python-based microservice. Java does not natively execute Python, so you need a bridge between the two runtimes. This article covers four practical approaches, each suited to different requirements around performance, compatibility, and complexity.
Using ProcessBuilder and Runtime.exec
The simplest way to run Python from Java is to spawn a separate Python process using ProcessBuilder or Runtime.exec(). This treats Python as an external command, just like running it from a terminal.
The Python script receives arguments through sys.argv and communicates results back through standard output. You can also write to the process's stdin for bidirectional communication.
This approach has zero dependencies and works with any Python version installed on the system. The downside is overhead: each invocation starts a new OS process, and data exchange is limited to text over stdin/stdout. It is best suited for infrequent calls or batch-style workloads where startup cost does not matter.
Using Jython
Jython is an implementation of Python 2.7 that runs directly on the JVM. It compiles Python source code into Java bytecode, which means Python objects and Java objects can interact in the same process without serialization.
Add Jython to your project via Maven:
The major limitation of Jython is that it only supports Python 2.7. It cannot run code that uses Python 3 syntax or CPython C extensions like NumPy, pandas, or TensorFlow. If your Python code is pure Python 2 and does not depend on C-based libraries, Jython provides the tightest integration with zero inter-process overhead.
Using GraalPython on GraalVM
GraalVM is a polyglot virtual machine that can run Java, JavaScript, Python, Ruby, and other languages in the same runtime. GraalPython is its Python 3 implementation, and it supports a growing subset of the CPython ecosystem.
GraalPython runs inside the same JVM process and can achieve good performance through GraalVM's JIT compiler. It supports Python 3 syntax and is steadily adding compatibility with popular packages. However, it does not yet support every CPython C extension, so you should verify that your specific dependencies work before committing to this approach.
Using Py4J and JPype
Py4J and JPype take opposite approaches to the same problem. Py4J lets Python call Java (and Java call back into Python) over a socket connection between two separate processes. JPype embeds CPython directly into the JVM process using JNI.
Py4J example (Java side starts a gateway server):
Python side connects to the gateway:
JPype example (Python embeds JVM):
Py4J is well-known in the data engineering world because Apache Spark uses it internally for PySpark. It works with any CPython installation and all C extensions, but the socket-based communication adds latency. JPype avoids that overhead by embedding CPython in-process, giving you direct access to the full CPython ecosystem including NumPy and pandas. The trade-off is more complex setup and potential issues with native library conflicts.
Common Pitfalls
- Hardcoding the Python executable path. Using
"python"instead of"python3"in ProcessBuilder can invoke Python 2 on systems where both are installed. Always specify the full path or use"python3"explicitly. - Ignoring the error stream in ProcessBuilder. If you only read stdout, Python exceptions and tracebacks are silently discarded. Use
redirectErrorStream(true)or read stderr separately. - Choosing Jython for Python 3 code. Jython only supports Python 2.7. Attempting to run Python 3 syntax like f-strings or
print()as a function with keyword arguments will fail. - Not managing the GraalVM context lifecycle. Forgetting to close the
Contextobject leads to resource leaks. Always use try-with-resources as shown in the example. - Assuming Py4J calls are low-latency. Each method call crosses a socket boundary. If you need to call Python thousands of times in a tight loop, batch the work into a single Python function call instead.
Summary
- ProcessBuilder/Runtime.exec is the simplest approach with no dependencies, best for infrequent or batch calls to any Python version.
- Jython provides tight in-process integration but is limited to Python 2.7 and pure-Python libraries.
- GraalPython supports Python 3 on the JVM with growing ecosystem compatibility and strong JIT performance.
- Py4J bridges Java and CPython over sockets, supports all C extensions, and is battle-tested in Apache Spark.
- JPype embeds CPython directly in the JVM for low-latency access to the full Python ecosystem, at the cost of more complex setup.

