Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Java cannot import a CPython module as if it were a Java class. To use Python from a Java application, launch Python as a separate process, embed a compatible Python runtime such as GraalPy, or call a Python service. For most first integrations, start with Java’s ProcessBuilder: it preserves your existing Python environment and keeps the two runtimes isolated.

Choose an embedded runtime when repeated in-process calls matter and your Python dependencies work with it. Choose a separate service or long-running worker when you need stronger isolation, independent deployment, or broad CPython package compatibility.

Choose the integration that fits

Need Good starting point Why
Run a script occasionally ProcessBuilder Simple, isolated, and easy to operate with an existing Python installation.
Use an existing CPython virtual environment or native packages ProcessBuilder or a Python service Keeps the interpreter and its installed dependencies together.
Make frequent calls without starting a process each time Persistent worker or embedded GraalPy A worker avoids per-request startup; embedding avoids the process boundary but needs compatibility testing.
Scale or deploy the Python side independently HTTP, gRPC, or messaging service Defines a clear boundary and separates failure and deployment lifecycles.
Python needs to call Java libraries or objects Consider Py4J or JPype These are commonly used with Python as the host side, which is the reverse of Java directly invoking Python.
Maintain a legacy Python 2/Jython application Evaluate Jython for that legacy system Do not assume it is a general Python 3 integration choice.

The right choice depends on package compatibility, call frequency, security boundaries, and deployment constraints—not just which API has the shortest example.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Call a packaged Python module with ProcessBuilder

Use Python’s -m option when the code is packaged as a module. This runs it through Python’s module import system, rather than relying on a fragile relative script path. For example, arrange a package so that mypackage.worker is importable and can act as a command-line entry point:

python -m mypackage.worker 2 3

A small worker can expose a function internally and print a result for its command-line caller:

# mypackage/worker.py
import json
import sys

def add(a, b):
    return a + b

if __name__ == "__main__":
    a = int(sys.argv[1])
    b = int(sys.argv[2])
    print(json.dumps({"result": add(a, b)}))

In Java, pass the executable and each argument as a separate list element. Configure the Python interpreter explicitly instead of assuming that a command named python exists or points to the intended installation:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.List;

public class CallPython {
    public static void main(String[] args) throws IOException, InterruptedException {
        String python = System.getenv("PYTHON_EXECUTABLE");
        if (python == null || python.isBlank()) {
            throw new IllegalStateException("PYTHON_EXECUTABLE is not configured");
        }

        List<String> command = List.of(
                python, "-m", "mypackage.worker", "2", "3");
        ProcessBuilder builder = new ProcessBuilder(command);
        builder.redirectErrorStream(true);

        Process process = builder.start();
        StringBuilder output = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
            String line;
            while ((line = reader.readLine()) != null) {
                output.append(line).append(System.lineSeparator());
            }
        }

        int exitCode = process.waitFor();
        if (exitCode != 0) {
            throw new RuntimeException("Python failed with exit code "
                    + exitCode + ":n" + output);
        }

        System.out.println(output);
    }
}

On Linux or macOS, the configured executable might be /opt/venv/bin/python. On Windows, it might be an absolute path such as C:UsersmeAppDataLocalProgramsPythonPython314python.exe. The right path depends on how Python is installed; do not assume python3 is universal either.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

This example merges stderr into stdout for simplicity, so diagnostics may appear alongside the result. If Java must parse the output as JSON, keep stdout as a data-only protocol and read stderr separately. Java’s ProcessBuilder documentation describes process creation, arguments, environment, directory, and stream redirection. Python’s subprocess documentation covers process execution and the -m convention.

Pass data and return a result

Command-line arguments are suitable for a few small scalar values. Avoid cramming complex objects, large payloads, or user-controlled strings into a command. For structured requests, JSON over standard input and standard output is a practical default.

Python worker:

import json
import sys

request = json.load(sys.stdin)
response = {"sum": request["a"] + request["b"], "ok": True}
json.dump(response, sys.stdout)
sys.stdout.write("n")
sys.stdout.flush()

Java can write one request and read one response using UTF-8 streams:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;

Process process = new ProcessBuilder(python, "-m", "mypackage.worker").start();

try (OutputStreamWriter writer = new OutputStreamWriter(
        process.getOutputStream(), StandardCharsets.UTF_8)) {
    writer.write("{"a":2,"b":3}n");
}

String response;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(
        process.getInputStream(), StandardCharsets.UTF_8))) {
    response = reader.readLine();
}

int exitCode = process.waitFor();

Closing Java’s output stream signals that no more input is coming. For a one-request process, the Java code should then drain both output streams, wait for the process, check the exit code, and validate the response before using it. For a long-running worker, define an explicit framing protocol—such as one JSON object per line—and keep the streams open while requests are exchanged.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Specify the protocol rather than relying on convention: character encoding, request and response shape, how errors are represented, whether a response can be null, and how versions remain compatible. Send logs and tracebacks to stderr; reserve stdout for the protocol. JSON is a useful default, not a requirement for every workload: large binary data or strict typed contracts may call for files, a binary format, or RPC.

Prevent hangs and handle failures

A subprocess has separate stdin, stdout, and stderr pipes unless you redirect them. If Python writes enough data to a pipe that Java is not reading, the child can block; Java may then wait forever. Reading stdout to completion while ignoring a busy stderr pipe is a common cause. The simple merged-stream example above avoids that particular two-pipe deadlock, but sacrifices a clean separation between response data and diagnostics.

  • Read both streams. If keeping stderr separate, drain stdout and stderr concurrently—for example, with dedicated reader tasks—or redirect stderr to a file or another destination.
  • Set a timeout. On modern Java, use process.waitFor(timeout, unit); if it expires, terminate the process, and consider destroyForcibly() if graceful termination does not work. Choose a timeout based on the work being done, not an arbitrary universal value.
  • Close stdin when finished. A Python worker reading until end-of-file cannot finish if Java leaves its input stream open.
  • Check the exit status and response separately. Exit code zero does not prove that output is valid or that the intended function ran.
  • Keep diagnostics. Capture stderr or merged output for failure reports without exposing sensitive data in ordinary logs.

Java’s Process API documents lifecycle and stream access. Python’s subprocess reference explains pipe handling, timeouts, and return codes. If calls are frequent, starting a fresh interpreter for every request may add avoidable overhead; use a persistent worker or service rather than weakening timeout and stream handling.

Make module discovery and environment explicit

A successful process launch does not guarantee that Python can find the module or its dependencies. Keep these settings distinct:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Interpreter path: selects the Python installation and therefore the packages available to it. Point to the virtual environment’s executable when that environment is intended.
  • Working directory: controls relative file paths and can affect imports. Set it deliberately with builder.directory(...) rather than relying on the Java process’s launch directory.
  • PYTHONPATH: adds locations to Python’s module search path. Set it only when needed; preferably install or package the module so its location is predictable.
  • Environment variables: a service account, container, or application server may not inherit your interactive shell’s environment.

For example, Java can set the directory and module path explicitly:

ProcessBuilder builder = new ProcessBuilder(python, "-m", "mypackage.worker");
builder.directory(new java.io.File("/opt/my-python-app"));
builder.environment().put("PYTHONPATH", "/opt/my-python-app");

Prefer a reproducible Python environment and test the selected interpreter directly. For example, run /opt/venv/bin/python -c "import mypackage; print(mypackage.__file__)" under the same account that runs Java. That helps distinguish a missing package from Java selecting the wrong Python executable.

Embed Python with GraalPy

Embedding can make sense when Java needs repeated calls into Python without creating a new operating-system process for each one, and the Python code and dependencies are compatible with GraalPy. GraalPy uses the GraalVM Polyglot API; its JVM developer guide provides current Maven and Gradle integration and context examples. The guide’s examples use GraalPy 25.x artifacts, including 25.0.3; treat that as an example version, not a permanent recommendation, and use the version appropriate for your project.

A documented context-based starting point is:

try (var context = GraalPyResources.createContext()) {
    System.out.println(context.eval("python", "'Hello Python!' ").asString());
}

Use the guide’s build integration and resource-loading setup for the selected version rather than assuming that a manually created context is sufficient for packaging and dependencies. One way to expose a callable function in a Python script is to export it to the polyglot bindings:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import polyglot

@polyglot.export_value
def add(a, b):
    return a + b

Java can retrieve and invoke that exported value from the context’s polyglot bindings:

Value function = context.getPolyglotBindings().getMember("add");
int result = function.execute(2, 3).asInt();

The exact context creation, script loading, and build configuration depend on the GraalPy version and how the application packages Python resources. Consult the official embedding guide for a runnable project layout.

Embedding is not a guarantee that every CPython package will work. Packages containing native extensions, platform-specific dependencies, or assumptions about CPython need compatibility testing on the actual operating systems and architectures you will deploy. GraalPy can be used with GraalVM JDK, Oracle JDK, or OpenJDK according to its JVM documentation, but support for a particular application still depends on its runtime and packages.

Manage the context lifecycle deliberately and close contexts when finished. Decide how calls will be coordinated across threads and what data may cross the Java/Python boundary. Avoid broad access such as allowAllAccess(true) for untrusted code: embedding puts Python execution inside the Java process, so an error or unsafe operation can affect that process. Do not assume embedding is faster for every workload; performance depends on the workload, warm-up, and runtime configuration.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

When a Python service or persistent worker is better

A separate Python process can be more than a one-shot script. A long-running worker can accept multiple requests over stdin/stdout, avoiding repeated interpreter startup while preserving a process boundary. For deployments that need independent scaling, release schedules, or operations, expose the Python capability as a service instead.

  • HTTP with JSON is approachable for request/response operations.
  • gRPC can provide typed contracts and streaming where those features justify the additional setup.
  • Queues or messaging suit asynchronous work that should not hold a Java request open.
  • Local IPC, such as Unix domain sockets or Windows named pipes, can be considered when both sides run on the same host and the platform supports the chosen transport.

A service adds serialization and network or IPC overhead; it is not automatically faster than an in-process call. Its benefit is clearer isolation and independent operation. Prefer this boundary when Python needs native packages that are troublesome to embed, a crash must not take down Java, the Python workload scales separately, or requests may be long-running. Define authentication, timeouts, retries, request limits, and error semantics as part of the service contract.

Where Py4J, JPype, and Jython fit

Py4J is primarily used for Python code to access Java objects in a JVM through a gateway. It supports callbacks in the other direction, but that architecture is not usually the simplest way for a Java application to invoke a Python module. JPype likewise provides Python access to Java and is commonly Python-hosted. Consider these when Python is the application that needs Java libraries, rather than treating them as interchangeable Java-to-Python launch APIs.

Jython may matter for legacy Jython and Python 2 systems, but it is not the default for a new Python 3 integration. GraalPy’s documentation positions it as a Python 3-oriented JVM option; neither runtime should be assumed to support every CPython extension unchanged.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Troubleshoot common failures

  • “Cannot run program python”: Python may be absent, missing from Java’s PATH, or installed under another name. Configure and log an absolute executable path, then verify it under the same service account or inside the same container.
  • ModuleNotFoundError: Java may have launched a different interpreter or the wrong environment, directory, or module path. Test the import with the exact executable and correct the installation, working directory, or PYTHONPATH.
  • The process hangs: Check whether Python is waiting for input, whether Java closed stdin, whether both output streams are being drained, and whether the task exceeds its timeout.
  • Output is empty or invalid: The function may have returned a value without printing it, diagnostics may have contaminated stdout, or the process may have failed on stderr. Define a response protocol and validate it.
  • It works locally but not in production: Compare executable, Python version, packages, operating system and architecture, working directory, environment variables, encoding, permissions, and native shared libraries. Test under the production account and deployment image.
  • GraalPy fails on a dependency: Check the package’s runtime and native-extension requirements against GraalPy’s supported platform and package guidance; use a CPython process or service if compatibility is a blocker.

Keep process execution safe

Do not concatenate untrusted input into a shell command. This pattern is dangerous because it gives input an opportunity to become shell syntax:

new ProcessBuilder("sh", "-c", "python worker.py " + userInput);

Instead, pass the interpreter, script or module, and each argument as separate elements:

new ProcessBuilder(python, "-m", "mypackage.worker", userInput);

ProcessBuilder is a process API, not a security sandbox. Keep shell invocation off unless shell behavior is genuinely required; validate input, limit permissions and resources, and define which files and environment variables the worker can access. Treat untrusted Python as untrusted code: run it in a separately hardened process or service with OS-level restrictions, not in a broadly privileged embedded context.

Practical rule

For an occasional call into an existing Python environment, use ProcessBuilder with an explicit interpreter, -m, a defined data protocol, stream handling, and a timeout. For repeated in-process calls, evaluate GraalPy against your real dependencies and deployment targets. If Python needs the full CPython ecosystem or stronger operational isolation, keep it in a persistent process or service.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.