# JPype: reaching Java from Python without reimplementing Python

> JPype is a Python module that gives Python full access to Java class libraries by interfacing at the native level in both virtual machines rather than re-implementing the language as Jython does. It ships as the jpype1 package, supports Python 3.8 through 3.15 and Java 11 through 25, and is Apache-2.0 licensed.

**jpype-project/jpype** — JPype is cross language bridge to allow Python programs full access to Java class libraries.

- Repository: https://github.com/jpype-project/jpype
- Website: http://www.jpype.org
- Stars: 1,295 · Forks: 211
- Language: Python
- License: Apache-2.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/jpype-project-jpype

## The bridge is at the native level, not a reimplementation

JPype is a Python module to provide full access to Java from within Python. The project summary describes it as allowing Python to make use of Java only libraries, exploring and visualization of Java structures, development and testing of Java libraries, scientific computing, and much more.

The framing that matters is the contrast with Jython, and the project states it directly. This is achieved not through re-implementing Python, as Jython has done, but rather through interfacing at the native level in both virtual machines. That shared memory based approach achieves decent computing performance while providing access to the entirety of CPython and Java libraries.

The three-way comparison is the right frame. A reimplementation such as Jython translates Python semantics onto the JVM, so it inherits the JVM's performance characteristics and its memory model, but it is a Python that lives in Java. A native extension compiled against CPython's own C API runs inside the Python process, so it inherits CPython's semantics but can only reach Java through a bridge you write yourself. JPype takes the third route, running both runtimes in one process over shared memory, so neither language is being re-expressed in the other.

The stated benefit of that arrangement is a specific pairing of properties. Python for rapid prototyping and Java for strong typed production code, in one environment. The library is for teams that have Java libraries they must use, or Java code they must test, and want to drive them from Python rather than port them.

The cost is implicit in the design and worth naming early. Two runtimes in one process means two garbage collectors, two sets of thread models, and one address space. The performance claim is about the bridge being cheap, not about the system being simple.

## The version matrix is wide on both sides

The badges at the top of the project summary double as a compatibility statement, and they are unusually specific.

On the Python side, the package is published under the name `jpype1`, with the PyPI implementation badge, the Python versions badge, and a conda-forge channel. On the Java side, the badge lists Java 11, 17, 21, and 25, and the JVM badge lists Open, Oracle, and Corretto, which is the three major JVM distribution families.

That JVM list is the more consequential of the two. A bridge that has to work across OpenJDK, Oracle's JDK, and Amazon's Corretto means it is tested against three different codebases for the JVM itself, since HotSpot builds differ in build flags, in the presence of optional components, and in their own bugs. The fact that the project tracks Java 11 through 25, with 11 being the oldest supported, also tells you the project keeps a working relationship with long-term support releases rather than only chasing the newest one.

The project metadata confirms the Python side. The package requires Python 3.8 or newer, and the classifiers list 3.8 through 3.15, which is a very wide range for a project with a native component. The distribution name is `jpype1` while the repository is `jpype`, a distinction that catches people out, and the description in the metadata is simply A Python to Java bridge.

The runtime dependency list is one entry, `packaging`, and the optional extras are minimal. The `docs` extra is sphinx with the sphinx-rtd-theme, and the `tests` extra is pytest. There is a PyInstaller entry point group, with a hook directory pointing at `jpype._pyinstaller.entry_points:get_hook_dirs` and a tests entry point, which is how JPype participates in PyInstaller's hook system so that a frozen application bundles the native library and the JVM lookup correctly.

## The native half is a CMake project with an Ivy build

JPype is a hybrid project and the build reflects that.

The Python package is built with scikit-build-core, at version 0.9 or newer, as the build backend. That choice is what lets a Python project contain a C++ component and still produce a normal wheel, with the native library compiled as part of the wheel build rather than requiring a separate toolchain step by the person installing it.

The C++ side is in the `native/` directory, and there is a `CMakeLists.txt` at the root. There is also an `ivy.xml`, which is the Apache Ivy descriptor for Java dependency resolution, and the `project/` directory, which holds the Java side of the project. The `jpype/` directory is the Python package, and `test/` and `examples/` sit alongside it.

The examples directory is small and tells you the intended usage. There is `examples/jms/`, a directory for Java Message Service examples, `examples/linux/`, which is presumably a platform-specific example, and `examples/rmi.py`, a single file for Remote Method Invocation, which is the Java mechanism for calling methods in another process.

The RMI example is the one worth pausing on, because it marks a boundary. JPype's own model is in-process and shared-memory, which means Java objects live in the same address space as Python objects and can be passed directly. RMI is the opposite: a Java object in another JVM, reached over the network. Supporting both means the library has to distinguish between a local Java reference and a remote one, and the example is there to make that distinction visible.

The build and test tooling in the tree is also worth noting. There is `coverage.sh` and `leak_coverage.sh`, which is a pair that tells you the project measures not only code coverage but also leaks, and `resolve.sh`, and `lgtm.yml` for the LGTM static analysis configuration, alongside `codecov.yml` and the `archive/` directory for older material.

## Running two runtimes in one process has a specific failure surface

The pyproject.toml contains one of the more revealing comments in the project, and it is about a crash rather than a feature.

The comment under the pytest configuration says that pytest's built-in faulthandler plugin calls faulthandler.enable() before the JVM exists, and faulthandler.disable() at exit, which reinstalls the pre-JVM SIGSEGV handler over HotSpot's. The consequence is that the JVM's own crash diagnostics are lost, and the mechanism is clear from what precedes it.

That is a real and specific hazard, and it generalises into the main operational risk of this kind of bridge. Signal handlers and low-level runtime state are process-global, and two runtimes that each want to install their own will conflict. The JVM installs a SIGSEGV handler so it can produce a useful crash log instead of a bare segfault, and if Python's fault handler overwrites it, you lose the JVM's crash diagnostics. 

The same shape of problem appears wherever two runtimes both want global state. Thread naming, signal handling, character encoding defaults, and the question of which runtime owns the main thread are all process-global, and in a shared-memory design they are shared too.

The mypy overrides in pyproject.toml show the other edge of the design. The modules `_jpype`, `commonx`, `brokenx`, `java.*`, `jpypex.common`, and `jedi.*` are all set to ignore missing imports, with a todo comment on `_jpype` saying to add a proper `_jpype.pyi` file. Those module names are not Python modules at all in the ordinary sense: `java.*` is the pattern for a Java package, so the type checker is being told not to complain that it cannot find stubs for arbitrary Java namespaces, and the `x`-suffixed names are the deliberately broken fixtures the test suite uses to check the library's error handling. The `java.*` override is what makes it possible to type check a Python codebase that imports Java packages through the bridge.

## The project started on SourceForge and carries a laboratory identifier

The project's history is stated in one line, and it explains a few things about the codebase.

The work on this project began on Sourceforge, and the identifier LLNL-CODE-812311 is given directly underneath. LLNL is Lawrence Livermore National Laboratory, and the code identifier format is the one national laboratories use for the software they release. That tells you the project began as laboratory work, which explains a great deal about its character: a C++ extension with careful memory handling, a test suite that measures leaks, a static analysis configuration, and a deep commitment to the platform-specific behaviour of the JVM across three vendors.

The lineage from SourceForge shows in the packaging too. The distribution is named `jpype1` while the repository is `jpype`, which is the pattern of a project that started as `jpype` on SourceForge and continued under a new name on GitHub while keeping the old install name so that `import jpype` kept working for existing users. The project also carries a `NOTICE` file alongside the `LICENSE`, and the SPDX identifier is stated in the README as Apache-2.0.

The authorship is recorded in the metadata. Steve Menard and Karl Einar Nelson are the authors, and Martin K. Scherer is a maintainer. 

The release history shows a steady cadence with a recent burst. Version 1.6.0 is from 2025-06-01, 1.7.0 from 2026-04-09, and 1.7.1 from 2026-05-06, and the version in the metadata is 1.7.2.dev0, so a patch release is in progress. The last push to the repository was on 2026-10-14, and the default branch is `master`, with the version tracked by a `.bumpversion.cfg` file.

## Where JPype is the wrong tool

The boundaries are sharp, and they come from the same source as the strengths.

First, two runtimes in one process is a real operational cost, not just a theoretical one. The JVM has a start-up time and a memory footprint, and that cost is paid whether or not you use Java features. A Python service that occasionally wants to call one Java library is paying a JVM's worth of resources for the privilege. The shared-memory design makes the bridge itself cheap, and it does nothing about the cost of the two runtimes coexisting.

Second, the cross-language call is not free at the object level. Passing a Java object into Python and calling methods on it crosses the boundary, and the cost profile of a Python loop that calls a Java method a million times is not the cost profile of a Java loop doing the same. The project is careful about performance in general, but the design choice is where the boundary is, and a workload that crosses it in a hot loop will feel it.

Third, the debugging story is complicated. A stack trace that crosses the bridge shows frames from both runtimes, and the tooling for reading those is not the tooling you are used to. A segfault in a mixed process is a segfault in a process with two different opinions about what a crash is, which is the SIGSEGV handler conflict from the pytest comment in a different guise.

Fourth, and this is the alternative worth naming, if the Java code is yours, the honest question is whether it should be Java. A native extension compiled against the CPython C API avoids the JVM entirely, and a service written in one language avoids two runtimes. JPype's case is strongest when the Java is a third party library you must call, or when the production path must be strongly typed Java and you want to prototype in Python against the same library.

Fifth, packaging is a genuine cost. A JPype application needs a JVM present at runtime, and the PyInstaller hook support in the metadata exists precisely because freezing such an application is non-trivial. A pure Python deployment, a container with a slim base image, or a serverless function all become harder once a JVM is in the picture.

Sixth, the version matrix, wide as it is, is a matrix. Supporting Python 3.8 through 3.15 and Java 11 through 25 across three JVM vendors means the project has a real test burden, and it means a JDK upgrade on your side is something to check rather than assume.

## Conclusion

JPype fits a Python team that needs to call into an existing Java library without rewriting it in Python, or that wants to prototype in Python and keep the production path in strongly typed Java, and that is prepared to live with one process holding two runtimes. It is the wrong choice for a project that wants a single runtime, for a deployment that cannot start a JVM, or for work where calling across the bridge is in the innermost loop. Check three things before you adopt it: that a JVM is present and that its version is in the supported range, since the project tracks Java 11, 17, 21, and 25, that the JVM start-up and memory cost fits your process budget, and that the boundary between the two runtimes is explicit in your design, because the shared-memory model gives you speed and also gives you two sets of runtime rules in one address space.

## FAQ

### What is JPype and how is it different from Jython?

JPype is a Python module giving Python full access to Java class libraries. It works not by re-implementing Python as Jython does, but by interfacing at the native level in both virtual machines, a shared memory based approach that provides access to the entirety of CPython and Java libraries.

### Which Python and Java versions does JPype support?

The package requires Python 3.8 or newer, with classifiers for 3.8 through 3.15, and it is published as `jpype1`. The supported Java versions are 11, 17, 21, and 25, and the supported JVM implementations are Open, Oracle, and Corretto. The current version is 1.7.2.dev0 after 1.7.1 from 2026-05-06.

### How do I install JPype and what are its dependencies?

It is on PyPI as `jpype1` and on conda-forge as `jpype1`, and it is built with scikit-build-core so the native component compiles as part of the wheel. The only runtime dependency is `packaging`, with optional extras for docs (sphinx, sphinx-rtd-theme) and tests (pytest), and there are PyInstaller entry points so frozen applications bundle the native library correctly.

### What are the known rough edges when running Python and Java in one process?

Two runtimes share global process state, so signal handlers can conflict. The project configuration notes that pytest's faulthandler plugin installs a SIGSEGV handler over HotSpot's before the JVM exists, which costs you the JVM's own crash diagnostics. The mypy overrides also show that Java packages are stubbed as ignore-missing-imports, with a todo to add a proper `_jpype.pyi` file.

## Sources

- [jpype-project/jpype on GitHub](https://github.com/jpype-project/jpype)
- [License: Apache-2.0](https://github.com/jpype-project/jpype/blob/master/LICENSE)
- [Project website](http://www.jpype.org)
- [README](https://github.com/jpype-project/jpype/blob/master/README.md)
- [Releases](https://github.com/jpype-project/jpype/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/jpype-project-jpype
