PyJNIus: calling Java classes from Python over the JNI
Access Java classes from Python
At a glance
- What is it?
- PyJNIus is a Kivy Team library that gives Python code access to Java classes through the Java Native Interface, most often to reach Android APIs that have no Python equivalent. The import is one line, but the package now builds a Cython extension, and the Android wheels are deliberately shipped without any Java of their own.
- Who is it for?
- PyJNIus suits a Python developer who needs a specific Android or desktop Java API that has no Python binding, because autoclass turns a class name into a working Python object in one call and the metaclass route lets you declare exactly the methods you need.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One autoclass call opens a Java class
PyJNIus is a Python library for accessing Java classes using the Java Native Interface, and the API is deliberately tiny.
>>> from jnius import autoclass
>>> autoclass('java.lang.System').out.println('Hello world')
Hello world
>>> Stack = autoclass('java.util.Stack')
>>> stack = Stack()
>>> stack.push('hello')
>>> stack.push('world')
>>> print(stack.pop())
world
>>> print(stack.pop())
helloThe first line is the whole idea. autoclass takes a Java class name as a string and returns something that behaves like the Python equivalent of that class, and the rest of the snippet is ordinary Python calling into it. The static field out on java.lang.System is reached as an attribute, and the static method println is called on it.
The managed-by-Kivy-Team detail is not incidental. PyJNIus exists because python-for-android needs a way to talk to the Android platform APIs, and the same tool turned out to be useful on the desktop. The README says it can be used independently of Kivy, on desktop and mobile platforms.
The topics list is narrow and accurate: java, jni, jni-android, jni-wrapper and python. There is no attempt to be a general interop layer between Python and every language with a C ABI. It is a JNI bridge, and it is managed by the team whose primary use case it serves.
autoclass discovers everything, the metaclass declares only what you need
The advanced example in the README addresses a real problem with the one-line approach, and the fix is the more interesting half of the library.
When you use autoclass, it will discover all the methods and fields of the class and resolve them. You may want to declare and use only what you need. The same example can be done manually.
from jnius import MetaJavaClass, JavaClass, JavaMethod, JavaStaticMethod
class Hardware(JavaClass, metaclass=MetaJavaClass):
__javaclass__ = 'org/renpy/android/Hardware'
vibrate = JavaStaticMethod('(D)V')
accelerometerEnable = JavaStaticMethod('(Z)V')
accelerometerReading = JavaStaticMethod('()[F')
getDPI = JavaStaticMethod('()I')The signatures are written in JNI descriptor form. (D)V is a static method taking a double and returning void. (Z)V takes a boolean. ()[F takes nothing and returns a float array. ()I returns an int. The class name uses the slash-separated binary form, org/renpy/android/Hardware, not the dotted form.
The trade-off is real. autoclass is convenient and complete, but it resolves the entire class surface, which costs time and pulls in methods you will never call. The explicit form does the reverse: you pay a small amount of typing in exchange for a much smaller surface and a class that fails loudly if a signature is wrong rather than silently misbehaving.
The library also gives you a way to inspect what autoclass found. The signatures method of JavaMethod and JavaMultipleMethod inspects the discovered signatures of a method of an object, so you can print them on a class and read the accepted argument types and the returned type for each overload.
The import name did not change with the package name
The README puts a warning at the top that anyone upgrading will hit.
The PyPI package name is now pyjnius instead of jnius.
That is a packaging rename, not an API rename. The import in every code sample is still from jnius import autoclass, and the module directory in the repository is still jnius/. So a project that installs by package name has to change, while the code does not.
This is the kind of change that breaks builds quietly. A requirements file pinning jnius still names the old package. A pip install line in a Dockerfile, a CI workflow, or a build script has the same problem. The import statements in the application are the part that stays the same.
The release history shows the project has been through this kind of version churn recently: 1.9.0 on 2026-10-10, 1.8.0 on 2026-09-26 and 1.7.0 on 2025-09-08, with the last push on 2026-10-17.
The repository carries a CHANGELOG.md, and the documentation is on readthedocs at pyjnius.readthedocs.org. The homepage in the repository metadata is the documentation site rather than a separate project page.
The build is Cython plus setuptools, and the JDK is still required
The install is one command.
pip install pyjniusThe build behind it is not trivial. The pyproject.toml requires setuptools at 58.0.0 or newer, wheel, and Cython at 3.1.2 or newer but below 3.4.
The setup.py file explains the arrangement. The Cython sources are a single jnius.pyx file, and there is a list of PXI_FILES that includes the conversion, export, JVM, local reference and proxy modules, along with platform-specific JVM modules for Android, desktop and dlopen. The jnius_config directory holds the environment detection, imported through a temporary sys.path insertion so that it can be read without triggering the build.
The build also compiles Java. The Makefile detects the javac version and picks a target of 1.6 for javac older than 12 and 1.7 otherwise, passing the same value as -target and -source, and it drives the Java part through ant with the matching source and target properties.
The troubleshooting section starts with the obvious and important instruction: make sure a Java Development Kit is installed.
That requirement is the practical limit of the library on the desktop. PyJNIus is not a pure Python package, and while a wheel is available, a machine without a JDK cannot compile it.
The test target runs pytest from inside the tests directory with CLASSPATH pointing at the compiled test classes and PYTHONPATH at the repository root, which is how the Java and Python halves of the test suite meet.
The Android wheel ships no Java on purpose
The most interesting design decision in the repository is documented in a comment in setup.py, and it is a constraint-driven decision rather than a stylistic one.
The Android wheel is Java-free. The org.jnius.NativeInvocationHandler glue is supplied and dex'd by the host app, for example a Kivy or kivyforge bootstrap, never by the wheel. The reason given is that a class in site-packages is not on ART's dex classpath, so a bundled .class or .java file would be inert. Android therefore needs no javac or JDK and ships no Java. Desktop is unchanged and still requires a JDK to compile and bundle NativeInvocationHandler.
So the same library has two different dependency profiles depending on the target, and the packaging machinery reflects that. The pyproject.toml has a cibuildwheel.android section whose keys are all scoped so desktop builds are unaffected.
That section documents the toolchain inputs as well. cibuildwheel is pinned in the android-wheels workflow and selects the Android Python bundles, which specify the NDK installed via sdkmanager, and there is no cibuildwheel option to select an NDK version directly. The NDK is 27.3.13750724, specified by the current Python bundles and checked against the selected compiler path before each build, with a before-build step that fails the build if the compiler path is not the expected one. Cython is pinned independently at about 3.1.2 for the host-side Android C generation.
The two target ABIs are arm64_v8a and x86_64, invoked with cibuildwheel --only cp314-android_x86_64 and --only cp314-android_arm64_v8a. The comment also notes that CPython 3.15 needs an --enable cpython-prerelease flag on the command line, and that the 3.14 configuration is unchanged for it.
python-for-android is the intended path, and the README is thin there
The usage section for python-for-android is short. You get python-for-android, you compile a distribution with kivy, and PyJNIus is automatically added.
After that, the pattern is the same as any other autoclass call, with the Android class resolved by name.
from time import sleep
from jnius import autoclass
Hardware = autoclass('org.renpy.android.Hardware')
print('DPI is', Hardware.getDPI())
Hardware.accelerometerEnable(True)
for x in range(20):
print(Hardware.accelerometerReading())
sleep(0.1)The accelerometer readings in the README's sample output show the classic JNI array conversion, where a Java float array comes back into Python as a list of floats, and the values include the near-zero gravity components and the roughly 9.4 magnitude that a handheld accelerometer reports under gravity.
The library is MIT licensed, with the LICENSE file at the repository root, and it follows the Contributor Covenant 2.1.
What the README does not cover is worth naming. It does not document the full set of supported Java classes, does not explain the reference counting or local reference handling, and does not discuss thread behaviour, which matters because JNI calls into Java from a Python thread will run on whatever thread the JVM attaches. The examples/ directory has an arraylists folder, and the docs/ directory and the FAQ.md file at the root are where the detail lives.
The repository also carries a main.workflow file, a build.xml for the ant-driven Java build, renovate.json for dependency updates, and a jnius_config/ directory for the platform detection.
Editorial conclusion
PyJNIus suits a Python developer who needs a specific Android or desktop Java API that has no Python binding, because autoclass turns a class name into a working Python object in one call and the metaclass route lets you declare exactly the methods you need. It is the wrong tool if you want to call Java from Python on a machine without a JVM, since a JDK is still required on the desktop, and it is the wrong choice for a codebase that only needs one or two calls, since a dedicated service or a REST client is usually less work. Before you adopt it, note that the PyPI package name changed from jnius to pyjnius while the import name stayed jnius, so existing requirements files and install scripts need updating.
Frequently asked questions
How do I install PyJNIus?
Run pip install pyjnius. The PyPI package name is now pyjnius instead of jnius, while the import name stays from jnius import autoclass, so existing code needs no change but requirements files and install scripts do.
How do I call a Java class from Python with PyJNIus?
Use autoclass with the class name, for example autoclass('java.lang.System').out.println('Hello world'). It discovers all the methods and fields of the class and resolves them.
What is the difference between autoclass and the MetaJavaClass approach?
autoclass resolves the entire class surface automatically, while the metaclass approach with JavaMethod and JavaStaticMethod lets you declare only the methods you need, with explicit JNI signature strings such as (D)V or ()I.
Do I need a JDK to use PyJNIus on desktop?
Yes. The troubleshooting section tells you to make sure a Java Development Kit is installed, and the build compiles Java sources with javac and ant. The Android wheel is the exception, since it ships no Java and requires no JDK.
How do I use PyJNIus with python-for-android?
Get python-for-android and compile a distribution with kivy, and PyJNIus is added automatically. You then resolve Android classes with autoclass, for example autoclass('org.renpy.android.Hardware') to read the DPI or the accelerometer.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/kivy-pyjnius)Was this useful?