Py4J: Python to Java Bridge
Py4J enables Python programs to dynamically access Java objects in a Java Virtual Machine (JVM). It facilitates method calls and allows access to Java collections as if they were native Python objects. Py4J also supports callbacks, enabling Java programs to call Python objects. The current version is 0.10.9.9, and it maintains a consistent release cadence with a strong focus on backward compatibility.
Warnings
- gotcha Py4J does not automatically convert pure Python `list` objects to Java `List` objects when passed as arguments to Java methods. This will result in an `InvalidGatewayException` or similar error if not handled.
- gotcha Attempting to connect to the Java Gateway Server before it has started, or if it is not running on the expected host/port, will result in a `socket.error: [Errno 111] Connection refused`.
- breaking Starting with Py4J 0.10.9.9, Python's magic members (e.g., `__name__`, `__class__`) are no longer sent to the Java side, raising an `AttributeError` instead. This prevents unintended exposure and potential issues on the Java side.
- gotcha For Java versions 11 and later, changes related to the Java Platform Module System (JPMS) and reflective access (e.g., `setAccessible`) might require specific JVM arguments (`--add-opens`) or adjustments to how the Py4J Java Gateway Server is launched, particularly for complex applications.
- breaking Prior to Py4J 0.7, Java `byte[]` arrays passed to Python were treated as references, requiring individual calls for each byte access. Since 0.7, `byte[]` are passed by value and converted to Python `bytearray` (Python 2.x) or `bytes` (Python 3.x), and vice versa.
Install
-
pip install py4j
Imports
- JavaGateway
from py4j.java_gateway import JavaGateway
- GatewayParameters
from py4j.java_gateway import GatewayParameters
Quickstart
import os
from py4j.java_gateway import JavaGateway, GatewayParameters
import subprocess
import time
# --- Java Gateway Server Setup (for demonstration) ---
# In a real application, the Java Gateway Server would be started separately.
# For this example, we'll try to start a minimal one programmatically.
# This assumes 'py4j<version>.jar' is in the classpath. For simplicity,
# we'll use a placeholder for py4j.jar path. Users need to replace it.
# Usually, py4j.jar is found in your Python environment's site-packages/py4j/ directory.
# NOTE: Replace '/path/to/py4j<version>.jar' with the actual path to your Py4J JAR file.
# You can find it by running: `python -c "import py4j; import os; print(os.path.join(os.path.dirname(py4j.__file__), 'py4j0.10.9.9.jar'))"`
PY4J_JAR_PATH = os.environ.get('PY4J_JAR_PATH', '/path/to/py4j0.10.9.9.jar') # Update this path!
java_code = """
import py4j.GatewayServer;
public class AdditionApplication {
public int addition(int first, int second) {
return first + second;
}
public static void main(String[] args) {
AdditionApplication app = new AdditionApplication();
GatewayServer server = new GatewayServer(app, 25333);
server.start();
System.out.println("Gateway Server Started on port 25333");
}
}
"""
# Attempt to compile and run the Java code for quickstart demo
# This is a simplification and might require manual setup for complex environments.
# In a production setting, the Java server would be a separate, long-running process.
# Create temporary files for Java code and compiled class
java_file_name = "AdditionApplication.java"
class_file_name = "AdditionApplication.class"
with open(java_file_name, "w") as f:
f.write(java_code)
try:
# Compile Java code (requires javac in PATH)
compile_process = subprocess.run(
["javac", "-cp", PY4J_JAR_PATH, java_file_name],
capture_output=True, text=True
)
if compile_process.returncode != 0:
print("Java compilation failed:", compile_process.stderr)
print("Please ensure javac is in your PATH and PY4J_JAR_PATH is correct.")
# Fallback for systems without javac or if compilation fails: manual start instruction
print("Alternatively, you can manually compile and run the Java server:")
print(f"1. Save the Java code above as {java_file_name}")
print(f"2. Compile: javac -cp {PY4J_JAR_PATH} {java_file_name}")
print(f"3. Run: java -cp {PY4J_JAR_PATH}:. AdditionApplication")
# If on Windows, use ';' instead of ':' for classpath separator in commands
raise RuntimeError("Java compilation failed, cannot run quickstart.")
# Run Java Gateway Server in a separate process
# Using 'nohup' and '&' for background on *NIX, or 'start' on Windows
if os.name == 'nt': # Windows
java_server_command = ["java", "-cp", f".;{PY4J_JAR_PATH}", "AdditionApplication"]
java_process = subprocess.Popen(java_server_command, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
else: # Unix-like
java_server_command = ["java", "-cp", f":{PY4J_JAR_PATH}", "AdditionApplication"]
java_process = subprocess.Popen(java_server_command, preexec_fn=os.setsid)
print("Starting Java Gateway Server...")
time.sleep(5) # Give Java server time to start
# --- Python Client ---
gateway = JavaGateway(gateway_parameters=GatewayParameters(port=25333))
# Access the Java entry point (AdditionApplication instance)
addition_app = gateway.entry_point
# Call a Java method
number1 = 5
number2 = 3
result = addition_app.addition(number1, number2)
print(f"Python calling Java: {number1} + {number2} = {result}")
# Access JVM directly for standard Java objects
java_list = gateway.jvm.java.util.ArrayList()
java_list.add("Hello from Python!")
print(f"Java list created and modified from Python: {java_list}")
except Exception as e:
print(f"An error occurred during quickstart: {e}")
finally:
# Clean up Java process if it was started
if 'java_process' in locals() and java_process.poll() is None:
print("Shutting down Java Gateway Server...")
if os.name == 'nt':
subprocess.run(["taskkill", "/F", "/T", "/PID", str(java_process.pid)], capture_output=True)
else:
os.killpg(os.getpgid(java_process.pid), 15) # Send SIGTERM
java_process.wait(timeout=5)
# Clean up temporary files
os.remove(java_file_name) if os.path.exists(java_file_name) else None
os.remove(class_file_name) if os.path.exists(class_file_name) else None
# Also remove AdditionApplication$*.class files if they exist
for f in os.listdir('.'):
if f.startswith('AdditionApplication$') and f.endswith('.class'):
os.remove(f)