Getting Started with GraalPy on the JVM
You can use GraalPy with GraalVM JDK, Oracle JDK, or OpenJDK. To add GraalPy to your Java application, use Maven or Gradle as shown below. For other build systems (like Ant, Make, CMake, etc.), manual configuration may be required.
Platform support
GraalPy is mostly written in Java and Python, but the Python package ecosystem is rich in native packages that need platform specific support via native libraries that expose platform-specific APIs. Our main operating system is Oracle Linux, the CPU architectures we focus on are AMD64 and ARM, and the main JDK we test is Oracle GraalVM. Windows and macOS with GraalVM JDK are less well tested, and outside of those combinations we target only basic test coverage. See below for a detailed breakdown.
Maven
GraalPy can generate a Maven project that embeds Python packages into a Java application using Maven artefacts.
- GraalPy project publishes a Maven archetype to generate a starter project:
mvn archetype:generate \ -DarchetypeGroupId=org.graalvm.python \ -DarchetypeArtifactId=graalpy-archetype-polyglot-app \ -DarchetypeVersion=25.0.1 - Build a native executable using the GraalVM Native Image “tool” plugin that was added for you automatically:
mvn -Pnative package - Once completed, run the executable:
./target/polyglot_appThe application prints “hello java” to the console.
The project uses the GraalVM Polyglot API with additional features to manage Python virtual environments and integrate Python package dependencies with a Maven workflow. The Java code and the pom.xml file are heavily documented and the generated code describes available features.
See also Embedding Build Tools for more information about the GraalPy Maven Plugin.
Creating Cross-platform JARs with Native Python Packages
The generated project uses the GraalPy Maven plugin, which makes it easy to add Python dependencies. However, Python packages may have native components that are specific to the build system. In order to distribute the resulting application for other systems, follow these steps:
-
Build the project on each deployment platform. Rename JAR files so they each have a platform-specific name and move them to a temporary directory on the same machine.
-
Unzip each of the JAR files (substituting the correct names for the JAR files). A special file, vfs/fileslist.txt needs to be concatenated from each JAR file. Finally, create a new combined.jar from the combination of all files and with the concatenated fileslist.txt.
unzip linux.jar -d combined mv combined/vfs/fileslist.txt fileslist-linux.txt unzip windows.jar -d combined mv combined/vfs/fileslist.txt fileslist-windows.txt cat fileslist-linux.txt fileslist-windows.txt > combined/vfs/fileslist.txt cd combined zip -r ../combined.jar *
Gradle
- Create a Java application with Gradle using the command below and follow the prompts (select the Groovy build script language, select a test framework, and so on):
gradle init --type java-application \ --project-name interop \ --package interop \ --no-split-projectThe project is generated in the current working directory with the following structure:
└── app ├── build.gradle └── src └── main ├── java │ └── interop │ └── App.java └── resources - Open your project configuration file, app/build.gradle, and modify it as follows.
- Include the GraalPy support and the GraalVM Polyglot API in the
dependenciessection:implementation("org.graalvm.polyglot:polyglot:25.0.1") implementation("org.graalvm.polyglot:python:25.0.1")
- Include the GraalPy support and the GraalVM Polyglot API in the
- Finally, replace the code in the file named App.java as follows for a small Python embedding:
package interop; import org.graalvm.polyglot.*; class App { public static void main(String[] args) { try (var context = Context.create()) { System.out.println(context.eval("python", "'Hello Python!'").asString()); } } } - Run the application with Gradle:
./gradlew runThe application prints “Hello Python!” to the console.
Note: The performance of the GraalPy runtime depends on the JDK in which you embed it. For more information, see Runtime Optimization Support.
-
Optionally, you can also use a third-party Python package.
5.1. In app/build.gradle:
- add the graalpy-gradle-plugin to the
pluginssection:id "org.graalvm.python" version "25.0.1" - configure the GraalPy Gradle plugin:
graalPy { packages = ["termcolor==2.2"] }
5.2. In settings.gradle, add the following
pluginManagementconfiguration.pluginManagement { repositories { gradlePluginPortal() } }5.3. Update the file named App.java as follows:
package interop; import org.graalvm.polyglot.*; import org.graalvm.python.embedding.GraalPyResources; class App { ... public static void main(String[] args) { try (Context context = GraalPyResources.createContext()) { String src = """ from termcolor import colored colored_text = colored("hello java", "red", attrs=["reverse", "blink"]) print(colored_text) """; context.eval("python", src); } } - add the graalpy-gradle-plugin to the
See also Embedding Build Tools for more information about the GraalPy Gradle Plugin.
Ant, CMake, Makefile or Other Build Systems Without Direct Support for Maven Dependencies
Some (often older) projects may be using Ant, Makefiles, CMake, or other build systems that do not directly support Maven dependencies. Projects such as Apache Ivy™ enable such build systems to resolve Maven dependencies, but developers may have reasons not to use them. GraalPy comes with a tool to obtain the required JAR files from Maven.
-
Assuming there is some directory where third-party dependencies are stored for the project and that the build system is set up to put any JAR files there on the classpath, the project directory tree might look similar to this:
├───lib │ └─── ... *.jar dependencies are here └───src └─── ... *.java files and resources are here -
Install GraalPy for your system and ensure you have
graalpyon yourPATH. Open a command-line interface and enter your project directory. Then, as appropriate for your system, run one of the following commands:In a POSIX shell:
export GRAALPY_HOME=$(graalpy -c 'print(__graalpython__.home)') "${GRAALPY_HOME}/libexec/graalpy-polyglot-get" -a python -o lib -v "25.0.1"In PowerShell:
$GRAALPY_HOME = graalpy -c "print(__graalpython__.home)" & "$GRAALPY_HOME/libexec/graalpy-polyglot-get" -a python -o lib -v "25.0.1"These commands download all GraalPy dependencies into the lib directory.
-
Provided that your build system is set up to pick up the JAR files from lib, the GraalPy embedding code below should work if put in an appropriate place in the project to run as the main class.
import org.graalvm.polyglot.*; public class Hello { public static void main(String[] args) { try (var context = Context.newBuilder().option("engine.WarnInterpreterOnly", "false").build()) { System.out.println(context.eval("python", "'Hello Python!'").asString()); } } }
Related Documentation
Testing Python Applications and Packages on GraalPy
Choosing the GraalPy Runtime
GraalPy provides a Python 3.12 compliant runtime. A primary goal is to support PyTorch, SciPy, and their constituent libraries, as well as to work with other data science and machine learning libraries from the rich Python ecosystem. GraalPy is distributed as an ahead-of-time compiled native executable, compact in size.
GraalPy provides the following capabilities:
- CPython-compatible distribution. This is the most compatible option to test Python code on GraalPy, since it most closely resembles the structure of CPython distributions.
- Unique deployment mode for Python applications. Compile a Python application on GraalPy to a single native binary that embeds all needed resources.
- Access to GraalVM’s language ecosystems and tools. GraalPy can run many standard Python tools as well as tools from the GraalVM ecosystem.
GraalPy Distributions
GraalPy is available as GraalPy built on Oracle GraalVM and GraalPy Community.
-
GraalPy built on top of Oracle GraalVM provides the best experience: it comes with additional optimizations, is significantly faster and more memory-efficient. It is licensed under the GraalVM Free Terms and Conditions (GFTC) license, same as Oracle GraalVM, which permits use by any user including commercial and production use. Redistribution is permitted as long as it is not for a fee.
-
GraalPy Community is built on top of GraalVM Community Edition, and is fully open-source.
Two language runtime options are available for the Oracle and Community distributions of GraalPy:
- Native
- GraalPy is compiled ahead-of-time to a native executable.
- This means that you do not need a JVM to run GraalPy and it is compact in size.
- JVM
- You can easily exploit Java interoperability.
- Peak performance may be higher than the native option.
GraalPy Identification
The four GraalPy runtimes are identified as follows, using the general pattern graalpy(-community)(-jvm)-<version>-<os>-<arch>:
| Oracle | Community | |
|---|---|---|
| Native | graalpy-<version>-<os>-<arch> | graalpy-community-<version>-<os>-<arch> |
| JVM | graalpy-jvm-<version>-<os>-<arch> | graalpy-community-jvm-<version>-<os>-<arch> |
Comparison
| Runtime | Native (default) | JVM |
|---|---|---|
| Time to start | faster | slower |
| Time to reach peak performance | faster | slower |
| Peak performance (also considering GC) | good | best |
| Java interoperability | needs configuration | works |
Installing GraalPy
NOTE: There will be a delay between GraalPy release and its availability on Pyenv.
Linux
The easiest way to install GraalPy on Linux is to use Pyenv (the Python version manager). To install version 25.0.1 using Pyenv, run the following commands:
pyenv install graalpy-25.0.1
pyenv shell graalpy-25.0.1
Before running
pyenv install, you may need to updatepyenvto include the latest GraalPy versions.
Alternatively, you can download a compressed GraalPy installation file from GitHub releases.
- Find the download that matches the pattern graalpy-XX.Y.Z-linux-amd64.tar.gz or graalpy-XX.Y.Z-linux-aarch64.tar.gz (depending on your platform) and download.
- Uncompress the file and update your
PATHenvironment variable to include to the graalpy-XX.Y.Z-linux-amd64/bin (or graalpy-XX.Y.Z-linux-aarch64/bin) directory.
macOS
The easiest way to install GraalPy on macOS is to use Pyenv (the Python version manager). To install version 25.0.1 using Pyenv, run the following commands:
pyenv install graalpy-25.0.1
pyenv shell graalpy-25.0.1
Before running
pyenv install, you may need to updatepyenvto include the latest GraalPy versions.
Alternatively, you can download a compressed GraalPy installation file from GitHub releases.
- Find the download that matches the pattern graalpy-XX.Y.Z-macos-amd64.tar.gz or graalpy-XX.Y.Z-macos-aarch64.tar.gz (depending on your platform) and download.
- Remove the quarantine attribute.
sudo xattr -r -d com.apple.quarantine /path/to/graalpyFor example:
sudo xattr -r -d com.apple.quarantine ~/.pyenv/versions/graalpy-25.0.1 - Uncompress the file and update your
PATHenvironment variable to include to the graalpy-XX.Y.Z-macos-amd64/bin (or graalpy-XX.Y.Z-macos-aarch64/bin) directory.
Windows
- Find and download a compressed GraalPy installation file from GitHub releases that matches the pattern graalpy-XX.Y.Z-windows-amd64.tar.gz.
- Uncompress the file and update your
PATHvariable to include to the graalpy-XX.Y.Z-windows-amd64/bin directory.
Windows Limitations
The Windows distribution of GraalPy has more limitations than its Linux or macOS counterpart, so not all features and packages may be available.
It has the following known issues:
- JLine treats Windows as a dumb terminal, with no autocomplete and limited editing capabilities in the REPL
- Interactive
help()in the REPL doesn’t work - Inside a virtual environment:
- graalpy.cmd and graalpy.exe are broken
- pip.exe cannot be used directly
piphas trouble with cache file loading, use--no-cache-dir- Only pure Python binary wheels can be installed, no native extensions or source builds
- To install a package, use
myvenv/Scripts/python.exe -m pip --no-cache-dir install <pkg>
- Running from PowerShell works better than running from CMD, various scripts will fail on the latter
Installing Packages
The best way of using GraalPy is from a venv virtual environment. This generates wrapper scripts and makes the implementation usable from a shell as the standard Python interpreter.
- Create a virtual environment with GraalPy by running the following command:
graalpy -m venv <venv-dir>For example:
graalpy -m venv ~/.virtualenvs/graalpy-25.0.1 - Activate the environment in your shell session:
source <venv-dir>/bin/activateFor example:
source ~/.virtualenvs/graalpy-25.0.1/bin/activate
Multiple executables are available in the virtual environment, including: python, python3, and graalpy.
Note: To deactivate the Python environment (and return to your shell), run
deactivate.
The pip package installer is available when using a virtual environment.
The GraalPy implementation of pip may choose package versions other than the latest in cases where it ships patches to make these work better.
Python Performance
Execution Performance
GraalPy uses the state-of-the-art just-in-time (JIT) compiler of GraalVM.
When JIT compiled, GraalPy runs Python code ~4x faster than CPython on the official Python Performance Benchmark Suite.
These benchmarks can be run by installing the pyperformance package and calling pyperformance run on each of CPython and GraalPy.
To get the Jython numbers we adapted the harness and benchmarks because of missing Python 3 support in Jython.
The speedup was then calculated by taking the pair-wise intersection of working benchmarks and calculating the geomean.
Without a JIT compiler, GraalPy currently executes pure Python code around ~4x slower than CPython. This means that very short running scripts or scripts running without the Graal compiler on Oracle JDK or OpenJDK are expected to be slower.
Many Python packages from the machine learning or data science ecosystems contain C extension code. This code benefits little from GraalPy’s JIT compilation and suffers from having to emulate CPython implementation details on GraalPy. When many C extensions are involved, performance can vary a lot depending on the specific interactions of native and Python code.
Code Loading Performance and Footprint
It takes time to parse Python code so when using GraalPy to embed another language in Python, observe the general advice for embedding Graal languages related to code caching. Furthermore, some Python libraries require loading a large amount of code on startup before they can do any work. Due to the design of the Python language, incremental parsing is not possible and for some scripts, the parser may represent a significant fraction of runtime and memory. To mitigate this, GraalPy can cache the bytecode generated during parsing in .pyc files, if appropriate file system access is configured.
Creating and Managing .pyc Files
GraalPy automatically creates a .pyc file when there is an invalid or absent .pyc file that matches a corresponding .py file.
When GraalPy imports a Python source file (module) during an execution for the first time, it automatically creates a corresponding .pyc file. If GraalPy imports the same module again, then it uses the existing .pyc file. That means that there are no .pyc files for source files that were not yet executed (imported). GraalPy creates .pyc files entirely through the FileSystem API, so that a Java application with embedded Python code can manage file system access.
Note: GraalPy never deletes a .pyc file.
Every time GraalPy subsequently executes a script, it reuses the existing .pyc file, or creates a new one.
GraalPy recreates a .pyc file if the timestamp or hashcode of the original source file is changed.
GraalPy generates the hashcode based only on the Python source file by calling source.hashCode(), which is the JDK hash code over the array of source file bytes, calculated with java.util.Arrays.hashCode(byte[]).
GraalPy also recreates .pyc files if a magic number in the Python parser is changed. The magic number is hard-coded in the source of Python and can not be changed by the user (unless of course that user has access to the bytecode of Python).
The developers of GraalPy change the magic number when the bytecode format changes.
This is an implementation detail, so the magic number does not have to correspond to the version of GraalPy (as in CPython).
The magic number of pyc is a function of the actual Python runtime Java code that is running.
Changes to the magic number are communicated in the release notes so that developers or system administrators can delete old .pyc files when upgrading.
Note that if you use .pyc files, you must allow write-access to GraalPy at least when switching versions or modifying the original source code file. Otherwise, the regeneration of source code files will fail and every import will have the overhead of accessing each old .pyc file, parsing the code, serializing it, and trying (and failing) to write out a new .pyc file.
GraalPy creates the following directory structure for .pyc files:
top_directory/
__pycache__/
sourceA.graalpy.pyc
sourceB.graalpy.pyc
sourceA.py
sourceB.py
sub_directory/
__pycache__/
sourceX.graalpy.pyc
sourceX.py
By default, GraalPy creates the __pycache__ directory on the same directory level as a source code file and in this directory all .pyc files from the same directory are stored. This directory may store .pyc files created with different versions of Python (including, for example, CPython), so the user may see files ending in .cpython3-6.pyc, for example.
.pyc files are largely managed automatically by GraalPy in a manner compatible with CPython. GraalPy provides options similar to CPython to specify the location of t_.pyc_ files, and if they should be written at all, and both of these options can be changed by guest code.
The creation of .pyc files can be controlled in the same way as CPython:
- The GraalPy launcher (
graalpy) reads thePYTHONDONTWRITEBYTECODEenvironment variable. If this is set to a non-empty string, Python will not try to create a .pyc file when importing a module. - The launcher command line option
-B, if given, has the same effect as the above. - Guest language code can change the attribute
dont_write_bytecodeof thesysbuilt-in module at runtime to change the behavior for subsequent imports. - The GraalPy launcher reads the
PYTHONPYCACHEPREFIXenvironment variable. If set, it creates the __pycache__ directory at the path specified by the prefix, and creates a mirror of the directory structure of the source tree on-demand to store the .pyc files. - A guest language code can change the attribute
pycache_prefixof thesysmodule at runtime to change the location for subsequent imports.
Because the developer cannot use environment variables or CPython options to communicate these options to GraalPy, these options are made available as language options:
python.DontWriteBytecodeFlag- equivalent to-BorPYTHONDONTWRITEBYTECODEpython.PyCachePrefix- equivalent toPYTHONPYCACHEPREFIX
Note that a Python context will not enable writing .pyc files by default. The GraalPy launcher enables it by default, but if this is desired in the embedding use case, care should be taken to ensure that the __pycache__ location is properly managed and the files in that location are secured against manipulation in the same way as the source code files (.py) from which they were derived.
Note also that to upgrade the application sources to Oracle GraalPy, old .pyc files must be removed by the developer as required.
Security Considerations
GraalPy performs all file operations (obtaining the data, timestamps, and writing .pyc files) via the FileSystem API.
Developers can modify all of these operations by means of custom (for example, read-only) FileSystem implementations.
The developer can also effectively disable the creation of .pyc files by disabling I/O permissions for GraalPy.
If .pyc files are not readable, their location is not writable. If the .pyc files’ serialization data or magic numbers are corrupted in any way, the deserialization fails and GraalPy parses the .py source code file again. This comes with a minor performance hit only for the parsing of modules, which should not be significant for most applications (provided the application performs actual work in addition to loading Python code).
Modern Python for the JVM
For Python version 2 (now EOL), Jython is the de facto means of interfacing Python and Java. Most existing Jython code that uses Java integration will be based on a stable Jython release—however, these are only available in Python 2.x versions. GraalPy, in contrast, is compatible with Python 3.x and does not provide full compatibility with earlier 2.x versions of Jython.
To migrate code from Python 2 to Python 3, follow the official guide from the Python community. Once your Jython code is compatible with Python 3, follow this guide to iron out other differences between GraalPy and Jython.
GraalPy’s first-class support for Java interoperability makes using Java libraries from Python as easy as possible, with special affordances for Java code beyond the generic interoperability support for other Graal languages (languages implemented on the Truffle framework).
Not all features of Jython are supported on GraalPy.
Some are supported, but disabled by default due to their negative impact on runtime performance.
During migration, you can enable these features using a command line option: --python.EmulateJython.
We recommend to move away from these features, however, to achieve optimal performance.
Migrating Jython Scripts
To move plain Jython scripts from Jython to GraalPy, use a GraalPy JVM-based runtime. (For more information, see available GraalPy Distributions).
Importing a Java Package
There are certain features of Jython’s Java integration that are enabled by default on GraalPy. Here is an example:
>>> import java.awt as awt
>>> win = awt.Frame()
>>> win.setSize(200, 200)
>>> win.setTitle("Hello from Python!")
>>> win.getSize().toString()
'java.awt.Dimension[width=200,height=200]'
>>> win.show()
This example produces the same result when run on both Jython and GraalPy.
However, when the example is run on GraalPy, only packages that are in the java namespace can be imported directly.
To import classes from packages outside the java namespace, use the --python.EmulateJython option.
Note: When embedding GraalPy in a modularized application, you may have to add exports for the required modules according to JSR 376.
Additionally, it is not possible to import Java packages as Python modules in all circumstances. For example, this will work:
import java.lang as lang
But, this will not work:
import javax.swing as swing
from javax.swing import *
Instead, import one of the classes directly:
import javax.swing.Window as Window
Basic Object Usage
Constructing and working with Java objects and classes is achieved with conventional Python syntax. The methods of a Java object can also be retrieved and referenced as first class objects (bound to their instance), in the same way as Python methods. For example:
>>> from java.util import Random
>>> rg = Random(99)
>>> rg.nextInt()
1491444859
>>> boundNextInt = rg.nextInt
>>> boundNextInt()
1672896916
Java-to-Python Types: Automatic Conversion
Method overloads are resolved by matching the Python arguments in a best-effort manner to the available parameter types.
This approach is also taken when converting data.
The goal here is to make using Java from Python as smooth as possible.
The matching approach taken by GraalPy is similar to Jython, but GraalPy uses a more dynamic approach to matching—Python types emulating int or float are also converted to the appropriate Java types.
This enables you, for example, to use a Pandas frame as double[][] or NumPy array elements as int[] when the elements fit into those Java primitive types.
| Java type | Python type |
|---|---|
null |
None |
boolean |
bool |
byte, short, int, long |
int, any object that has an __int__ method |
float |
float, any object that has a __float__ method |
char |
str of length 1 |
java.lang.String |
str |
byte[] |
bytes, bytearray, wrapped Java array, Python list with only the appropriate types |
| Java arrays | Wrapped Java array or Python list with only the appropriate types |
| Java objects | Wrapped Java object of the appropriate type |
java.lang.Object |
Any object |
Special Jython Module: jarray
GraalPy implements the jarray module (to create primitive Java arrays) for compatibility.
This module is always available, since we have not found its presence to have a negative impact.
For example:
>>> import jarray
>>> jarray.array([1,2,3], 'i')
Note that its usage is equivalent to constructing the array type using the java.type function and then populating the array, as follows:
>>> import java
>>> java.type("int[]")(10)
The code that creates a Java array can also use Python types. However, implicitly, this may produce a copy of the array data, which can be deceptive when using a Java array as an output parameter:
>>> i = java.io.ByteArrayInputStream(b"foobar")
>>> buf = [0, 0, 0]
>>> i.read(buf) # buf is automatically converted to a byte[] array
3
>>> buf
[0, 0, 0] # the converted byte[] array is lost
>>> jbuf = java.type("byte[]")(3)
>>> i.read(jbuf)
3
>>> jbuf
[98, 97, 122]
Exceptions from Java
You can catch Java exceptions as you would expect. For example:
>>> import java
>>> v = java.util.Vector()
>>> try:
... x = v.elementAt(7)
... except java.lang.ArrayIndexOutOfBoundsException as e:
... print(e.getMessage())
...
7 >= 0
Java Collections
-
Java arrays and collections that implement the
java.util.Collectioninterface can be accessed using the[]syntax. An empty collection is consideredfalsein boolean conversions. The length of a collection is exposed by thelenbuilt-in function. For example:>>> from java.util import ArrayList >>> l = ArrayList() >>> l.add("foo") True >>> l.add("baz") True >>> l[0] 'foo' >>> l[1] = "bar" >>> del l[1] >>> len(l) 1 >>> bool(l) True >>> del l[0] >>> bool(l) False -
Java iterables that implement the
java.lang.Iterableinterface can be iterated over using aforloop or theiterbuilt-in function and are accepted by all built-ins that expect an iterable. For example:>>> [x for x in l] ['foo', 'bar'] >>> i = iter(l) >>> next(i) 'foo' >>> next(i) 'bar' >>> next(i) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration >>> set(l) {'foo', 'bar'} -
An iterator can be iterated as well. For example:
>>> from java.util import ArrayList >>> l = ArrayList() >>> l.add("foo") True >>> i = l.iterator() # Calls the Java iterator methods >>> next(i) 'foo' -
Mapped collections that implement the
java.util.Mapinterface can be accessed using the[]notation. An empty map is consideredfalsein boolean conversions. Iteration of a map yields its keys, consistent withdict. For example:>>> from java.util import HashMap >>> m = HashMap() >>> m['foo'] = 5 >>> m['foo'] 5 >>> m['bar'] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: bar >>> [k for k in m] ['foo'] >>> bool(m) True >>> del m['foo'] >>> bool(m) False
Inheritance from Java
Inheriting from a Java class (or implementing a Java interface) is supported with some syntactical and significant behavioral differences from Jython.
To create a class that inherits from a Java class (or implements a Java interface), use the conventional Python class statement.
Declared methods override (implement) superclass (interface) methods when their names match.
It is important to understand that there is actually delegation happening here - when inheriting from Java, two classes are created, one in Java and one in Python. These reference each other and any methods that are declared in Python that override or implement a Java method on the superclass are declared on the Java side as delegating to Python. The created object does not behave like a Python object but instead in the same way as a foreign Java object. The reason for this is that when you create an instance of your new class, you get a reference to the Java object.
To call Python methods that do not override or implement methods that already existed on the superclass, you need to use the special this attribute.
Once you are in a Python method, your self refers to the Python object, and to get back from a Python method to Java, use the special attribute __super__.
And since we do not expose static members on the instance side, if you need to call a static method from an instance on the Java side, use getClass().static to get to the meta-object holding the static members.
One important consequence of the two-object-schema here is that the __init__ method on the Python object is actually called before the connection to the Java side is established.
So you cannot currently override construction of the Java object or run code during initialization that would affect the Java half of the combined structure.
You will have to create a factory method if you want to achieve this.
For example:
import atexit
from java.util.logging import Logger, Handler
class MyHandler(Handler):
def __init__(self):
self.logged = []
def publish(self, record):
self.logged.append(record)
logger = Logger.getLogger("mylog")
logger.setUseParentHandlers(False)
handler = MyHandler()
logger.addHandler(handler)
# Make sure the handler is not used after the Python context has been closed
atexit.register(lambda: logger.removeHandler(handler))
logger.info("Hi")
logger.warning("Bye")
# The python attributes/methods of the object are accessed through 'this' attribute
for record in handler.this.logged:
print(f'Python captured message "{record.getMessage()}" at level {record.getLevel().getName()}')
For more information about how the generated Java subclass behaves, see the Truffle documentation.
Embedding Python into Java
The other way to use Jython was to embed it into a Java application. There were two options for such an embedding.
-
Use the
PythonInterpreterobject that Jython provides. Existing code using Jython in this manner depends directly on the Jython package (for example, in the Maven configuration), because the Java code has references to Jython internal classes. These classes do not exist in GraalVM, and no equivalent classes are exposed. To migrate from this usage, switch to the GraalVM SDK. Using this SDK, no APIs particular to Python are exposed, everything is achieved via the GraalVM API, with maximum configurability of the Python runtime. Refer to the Getting Started documentation for preparing a setup. -
Embed Jython in Java via JSR 223 by using the classes of the the
javax.scriptpackage, and, in particular, via theScriptEngineclass. We do not recommend this approach, because theScriptEngineAPIs are not a clean fit for the options and capabilities of GraalPy. However, to migrate existing code, we provide an example ScriptEngine implementation that you can inline into your project. Refer to the reference manual for embedding for details.
Python Context Options
Below are the options you can set on contexts for GraalPy.
CheckHashPycsMode
Value of the --check-hash-based-pycs command line option - 'default' means the 'check_source' flag in hash-based pycs determines invalidation - 'always' causes the interpreter to hash the source file for invalidation regardless of value of 'check_source' bit - 'never' causes the interpreter to always assume hash-based pycs are valid The default value is 'default'. See PEP 552 'Deterministic pycs' for more details. Accepts: default|always|never
CompressionModulesBackend (Has to be the same for all Contexts in an Engine)
Choose the backend for the Zlib, Bz2, and LZMA modules. Accepts: java|native
CoreHome
Set the location of what is usually lib/graalpy<graalvm_major>.<graalvm_minor>. Overrides any environment variables or Java options. Accepts: <path>
DontWriteBytecodeFlag
Equivalent to the Python -B flag. Don't write bytecode files. Accepts: true|false
EmulateJython (Has to be the same for all Contexts in an Engine)
Emulate some Jython features that can cause performance degradation. Accepts: true|false
IgnoreEnvironmentFlag
Equivalent to the Python -E flag. Ignore PYTHON* environment variables. Accepts: true|false
InspectFlag
Equivalent to the Python -i flag. Inspect interactively after running a script. Accepts: true|false
InstallSignalHandlers
Install default signal handlers on startup. Accepts: true|false
IntMaxStrDigits
Equivalent to the Python -X int_max_str_digits option.
IsolateFlag
Equivalent to the Python -I flag. Isolate from the users environment by not adding the cwd to the path. Accepts: true|false
NoSiteFlag
Equivalent to the Python -S flag. Don't imply 'import site' on initialization. Accepts: true|false
NoUserSiteFlag
Equivalent to the Python -s flag. Don't add user site directory to sys.path. Accepts: true|false
PosixModuleBackend
Equivalent to setting PYTHONHASHSEED environment variable. Accepts: random|[0,4294967295]
PyCachePrefix
If this is set, GraalPy will write .pyc files in a mirror directory tree at this path, instead of in __pycache__ directories within the source tree. Equivalent to setting the PYTHONPYCACHEPREFIX environment variable for the standard launcher. Accepts: <path>
PythonOptimizeFlag
Remove assert statements and any code conditional on the value of __debug__. Accepts: true|false
PythonPath
Equivalent to setting the PYTHONPATH environment variable for the standard launcher. ':'-separated list of directories prefixed to the default module search path. Accepts: <path>[:<path>]
QuietFlag
Equivalent to the Python -q flag. Don't print version and copyright messages on interactive startup. Accepts: true|false
SafePathFlag
Equivalent to the Python -P flag. Don't prepend a potentially unsafe path to sys.path. Accepts: true|false
Sha3ModuleBackend (Has to be the same for all Contexts in an Engine)
Choose the backend for the Sha3 module. Accepts: java|native
StandardStreamEncoding (Has to be the same for all Contexts in an Engine)
Equivalent to setting the PYTHONIOENCODING environment variable for the standard launcher. Accepts: <Encoding>[:<errors>]
StdLibHome
Set the location of lib/python. Accepts: <path>
SysPrefix
Set the location of sys.prefix. Overrides any environment variables or Java options. Accepts: <path>
UnbufferedIO
Equivalent to the Python -u flag. Force stdout and stderr to be unbuffered. Accepts: true|false
VerboseFlag
Equivalent to the Python -v flag. Turn on verbose mode. Accepts: true|false
WarnDefaultEncodingFlag
Equivalent to the Python -X warn_default_encoding flag. Enable opt-in EncodingWarning for 'encoding=None'. Accepts: true|false
WarnOptions
Equivalent to setting the PYTHONWARNINGS environment variable for the standard launcher. Accepts: <action>[:<message>[:<category>[:<module>[:<line>]]]][,<action>[:<message>[:<category>[:<module>[:<line>]]]]]
AlwaysRunExcepthook
This option is set by the Python launcher to tell the language it can print exceptions directly. Accepts: true|false
BaseExecutable
The sys._base_executable path. Set by the launcher, but may need to be overridden in certain special situations. Accepts: <path>
Executable
The sys.executable path. Set by the launcher, but may need to be overridden in certain special situations. Accepts: <path>
ForceImportSite
Force to automatically import site.py module. Accepts: true|false
InitialLocale
Sets the language and territory, which will be used for initial locale. Format: 'language[_territory]', e.g., 'en_GB'. Leave empty to use the JVM default locale.
PythonHome
Set the home of Python. Equivalent of GRAAL_PYTHONHOME env variable. Determines default values for the CoreHome, StdLibHome, SysBasePrefix, SysPrefix. Accepts: <path>
SysBasePrefix
Set the location of sys.base_prefix. Overrides any environment variables or Java options. Accepts: <path>
UnsupportedPlatformEmulates (Has to be the same for all Contexts in an Engine)
Allow running on unsupported platforms, making GraalPy behave as if running on macOS, Windows, or Linux. This option is useful to run GraalPy on platforms with a compliant Java implementation, but without native support for GraalPy. When this option is set, native libraries cannot be loaded and the Java backends must be used for common operating system libraries provided by Python. Accepts: windows|macos|linux
VenvlauncherCommand
Option used by the venvlauncher to pass on the launcher target command.
WarnExperimentalFeatures
Print warnings when using experimental features at runtime. Accepts: true|false
Native Executables with Python
GraalPy supports GraalVM Native Image to generate native binaries of Java applications that embed Python code.
Quickstart
If you started with the Maven archetype, the generated pom.xml file already includes the necessary configuration for creating a native executable using the Maven plugin for Native Image building.
To build the application, run:
mvn -Pnative package
This command packages the project and creates a native executable.
The generated pom.xml and Main.java files explain how Python resources are included in the resulting binary. The generated project serves as a starting point and includes the entire Python standard library by default, allowing your Python code to use any standard library modules. You can manually remove unused Python libraries to reduce both the executable size and startup time. The created example demonstrates useful default options for the Python context, but you can adjust these settings to control what your Python code can access.
Reducing Binary Size
Python is a feature-rich language with an extensive standard library. This can result in large native executables when embedding GraalPy in Java applications. You can significantly reduce the size by excluding components your application doesn’t need by considering what your Python code actually uses.
Removing Pre-initialized Python Heap
By default, GraalPy includes a pre-initialized Python context in the executable for faster startup. Disabling this reduces the binary size by about 15MiB. You should remove this if:
- You are creating more than one context
- Binary size is more important than a slight startup delay
To remove the pre-initialized heap, add this flag to your build configuration:
-Dpolyglot.image-build-time.PreinitializeContexts=
Disabling Runtime Compilation of Python Code
If binary size is significantly more important than execution speed, you can disable JIT compilation entirely. This will reduce binary size by around 40%. You should use this if:
- Your Python scripts are very short-running
- Performance is not critical
- Your scripts spend most time on I/O operations
Be aware that this may significantly impact your Python performance, so be sure to test the runtime behavior of your actual use cases when choosing to use this option.
This can be achieved by passing the following options:
-Dtruffle.TruffleRuntime=com.oracle.truffle.api.impl.DefaultTruffleRuntime
-Dpolyglot.engine.WarnInterpreterOnly=false
Summary
Combining these approaches can reduce the binary size by 50% or more. Since every application is different, experiment with different combinations to find what works best for your specific use case.
Shipping Python Packages
Our Maven archetype by default is set up to include all needed Python files in the native binary itself, so the image is self-contained.
Python Standalone Applications
GraalPy enables you to package your Python applications or libraries into native executables or JAR files with no external dependencies. This means users can run your application without installing Python or any packages.
GraalPy uses the Truffle framework to bundle your Python code, dependencies, and the Python runtime into standalone executables. Truffle’s filesystem virtualization allows everything to work from a single file, though packages with native C extensions may have limitations.
GraalPy includes a module named standalone to create a Python binary for Linux, macOS, and Windows. The module bundles all your application’s resources into a single file.
Prerequisite: GraalPy 23.1.0 or later. Download here or verify your version with
graalpy --version.
Quickstart of Python Standalone Applications
To create an native executable from a Python file with its dependencies, use this command:
graalpy -m standalone native \
--module my_script.py \
--output my_binary \
--venv my_env
Where:
--module my_script.pystates the main Python file that contains your application’s entry point--output my_binarystates the name for your standalone executable (no file extension needed)--venv my_envstates the path to virtual environment with installed packages (you can omit this if there are no dependencies)
This produces a native my_binary file which includes your Python code, the GraalPy runtime, and the Python standard library in a single, self-contained executable.
Use graalpy -m standalone native --help for further options.
Security Considerations of Python Standalone Applications
Standalone executables do not protect your source code. Your Python code becomes bytecode, and bytecode can be easily decompiled back to readable Python code.
Interoperability
GraalPy can interoperate with Java and other Graal languages that are implemented on the Truffle framework. This means that you can use other languages’ objects and functions directly from your Python scripts. This interoperability works in both directions. Python can call other languages, and other languages can call Python code.
Interacting with Java from Python scripts
Java is the host language of the JVM and runs the GraalPy interpreter itself. This means you can seamlessly access any Java class available in your classpath directly from Python.
Basic Java access
Import the java module to access Java classes and methods:
import java
BigInteger = java.type("java.math.BigInteger")
myBigInt = BigInteger.valueOf(42)
# Call Java methods directly
myBigInt.shiftLeft(128) # returns a <JavaObject[java.math.BigInteger] at ...>
# Java method names that are Python keywords must be accessed using `getattr`
getattr(myBigInt, "not")() # returns a <JavaObject[java.math.BigInteger] at ...>
byteArray = myBigInt.toByteArray()
# Java arrays can act like Python lists
assert len(byteArray) == 1 and byteArray[0] == 42
Importing Java packages
You can import packages from the java namespace using conventional Python import syntax:
import java.util.ArrayList
from java.util import ArrayList
assert java.util.ArrayList == ArrayList
al = ArrayList()
al.add(1)
al.add(12)
assert list(al) == [1, 12]
Java module methods
In addition to the type built-in method, the java module exposes the following methods:
| Built-in | Specification |
|---|---|
instanceof(obj, class) |
Returns True if obj is an instance of class (class must be a foreign object class). |
is_function(obj) |
Returns True if obj is a Java host language function wrapped using interop. |
is_object(obj) |
Returns True if obj is a Java host language object wrapped using interop. |
is_symbol(obj) |
Returns True if obj is a Java host symbol, representing the constructor and static members of a Java class, as obtained by java.type. |
Here’s how to use these methods in practice:
ArrayList = java.type('java.util.ArrayList')
my_list = ArrayList()
assert java.is_symbol(ArrayList)
assert not java.is_symbol(my_list)
assert java.is_object(ArrayList)
assert java.is_function(my_list.add)
assert java.instanceof(my_list, ArrayList)
See the Polyglot Programming and Embed Languages documentation for more information about interoperability with other programming languages.
Interacting with foreign objects from Python scripts
When you use foreign objects in Python, GraalPy automatically makes them behave like their Python equivalents.
For example, a Java ArrayList acts like a Python list, and a Java HashMap acts like a Python dict:
from java.util import ArrayList, HashMap
type(ArrayList()).mro() # => [<class 'polyglot.ForeignList'>, <class 'list'>, <class 'polyglot.ForeignObject'>, <class 'object'>]
type(HashMap()).mro() # => [<class 'polyglot.ForeignDict'>, <class 'dict'>, <class 'polyglot.ForeignObject'>, <class 'object'>]
This means you can use Python methods on foreign objects:
from java.util import ArrayList, HashMap
# ArrayList behaves like a Python list so you can use Python methods
l = ArrayList()
l.append(1) # Python list method - l: [1]
l.extend([2, 3]) # Python list method - l: [1, 2, 3]
l.add(4) # Java ArrayList method still works - l: [1, 2, 3, 4]
l[1:3] # Python slicing works - returns [2, 3]
l.pop(1) # Python list method - returns 2, l: [1, 3, 4]
l.insert(1, 2) # Python list method - l: [1, 2, 3, 4]
l == [1, 2, 3, 4] # Python comparison works - True
# HashMap behaves like a Python dict so you can use Python methods
h = HashMap()
h[1] = 2 # Python dict syntax - h: {1: 2}
h.setdefault(3, 4) # Python dict method - h: {1: 2, 3: 4}
h |= {3: 6} # Python dict operator - h: {1: 2, 3: 6}
h == {1: 2, 3: 6} # Python comparison works - True
When a method is defined both in Python and on the foreign object, the Python’s method takes precedence.
To call the foreign method explicitly, use super(type_owning_the_python_method, foreign_object).method(*args):
from java.util import ArrayList
l = ArrayList()
l.extend([5, 6, 7])
l.remove(7) # Calls Python list.remove()
assert l == [5, 6]
super(list, l).remove(0) # Calls Java's ArrayList.remove()
assert l == [6]
See the Interop Types to Python section for more interop traits and how they map to Python types.
Interacting with other dynamic languages from Python scripts
The polyglot API allows non-JVM specific interactions with other languages from Python scripts. This includes all interactions with dynamic languages supported via the Truffle framework, including JavaScript and Ruby.
Installing other dynamic languages
To use other languages, like JavaScript, you need to add their Maven dependencies to your project.
If you’re using Maven with GraalPy, add the JavaScript dependency to your pom.xml file:
<dependency>
<groupId>org.graalvm.polyglot</groupId>
<artifactId>js</artifactId>
<version>25.0.1</version>
</dependency>
Examples
Here are practical examples of using the polyglot API to work with JavaScript from Python:
- Import the
polyglotmodule to interact with other languages:import polyglot - Evaluate inlined code in another language:
assert polyglot.eval(string="1 + 1", language="js") == 2 - Evaluate code from a file:
with open("./my_js_file.js", "w") as f: f.write("Polyglot.export('JSMath', Math)") polyglot.eval(path="./my_js_file.js", language="js") - Import a global value from the polyglot scope:
Math = polyglot.import_value("JSMath")This global value should then work as expected:
- Accessing attributes reads from the polyglot members namespace:
assert Math.E == 2.718281828459045 - Calling a method on the result attempts to do a straight
invokeand falls back to reading the member and trying to execute it.assert Math.toString() == "[object Math]" - Accessing items is supported both with strings and numbers.
assert Math["PI"] == 3.141592653589793
- Accessing attributes reads from the polyglot members namespace:
- Use the JavaScript regular expression engine to match Python strings:
js_re = polyglot.eval(string="RegExp()", language="js") pattern = js_re.compile(".*(?:we have (?:a )?matching strings?(?:[!\\?] )?)(.*)") if pattern.exec("This string does not match"): raise SystemError("that shouldn't happen") md = pattern.exec("Look, we have matching strings! This string was matched by Graal.js") assert "Graal.js" in md[1]This program matches Python strings using the JavaScript regular expression object. Python reads the captured group from the JavaScript result and checks for a substring in it.
Exporting Python Objects to other languages
Use the polyglot module to expose Python objects to JVM languages and other Graal languages (languages implemented on the Truffle framework).
This allows other languages to call your Python code directly.
- You can export a Python object so other languages can access it:
import ssl polyglot.export_value(value=ssl, name="python_ssl")Then use it, for example, from JavaScript code:
Polyglot.import('python_ssl').get_server_certificate(["oracle.com", 443]) - You can decorate a Python function to export it by name:
@polyglot.export_value def python_method(): return "Hello from Python!"Then use it, for example, from Java code:
import org.graalvm.polyglot.*; class Main { public static void main(String[] args) { try (var context = Context.create()) { context.eval(Source.newBuilder("python", "file:///python_script.py").build()); String result = context. getPolyglotBindings(). getMember("python_method"). execute(). asString(); assert result.equals("Hello from Python!"); } } }
Mapping types between Python and other languages
The interop protocol defines different types and traits that determine foreign objects behavior and restrictions when used in Python.
Interop Types to Python
All foreign objects passed into Python have the Python type polyglot.ForeignObject or a subclass.
Types not listed in the table below have no special interpretation in Python.
| Interop Type | Inherits from | Python Interpretation |
|---|---|---|
array |
ForeignList, list |
An array behaves like a Python list. |
boolean |
ForeignBoolean, ForeignNumber | boolean behaves like Python booleans, including the fact that in Python, all booleans are also integers (1 and 0 for true and false, respectively). |
buffer |
ForeignObject | Buffers work like Python buffer objects (such as those used with memoryview) to avoid copying data. |
exception |
ForeignException, BaseException |
An exception can be caught in a generic except clause. |
executable |
ForeignExecutable | An executable object can be executed as a function, but never with keyword arguments. |
hash |
ForeignDict, dict |
A hash behaves like a Python dict, with any “hashable” object as a key. “Hashable” follows Python semantics: generally every interop type with an identity is deemed “hashable”. |
instantiable |
ForeignInstantiable | An instantiable object can be called just like a Python type, but never with keyword arguments. |
iterable |
ForeignIterable | An iterable is treated in the same way as any Python object with an __iter__ method. That is, it can be used in a loop and other places that accept Python iterables. |
iterator |
ForeignIterator, iterator |
An iterator is treated in the same way as any Python object with a __next__ method. |
members |
ForeignObject | Objects with members can be accessed using Python dot notation (.) or getattr(). |
MetaObject |
ForeignAbstractClass | Meta objects can be used in subtype and isinstance checks. |
null |
ForeignNone, NoneType |
null behaves like Python None. All interop null values (including JavaScript undefined and null) are treated as None in Python. |
number |
ForeignNumber | number behaves like Python numbers (int and float). Foreign ranges are imported in some places such as typed arrays. |
string |
ForeignString, str |
Behaves in the same way as a Python string. |
Foreign numbers inherit from polyglot.ForeignNumber and not int or float because InteropLibrary has currently no way to differentiate integers and floats.
However:
- When foreign numbers are represented as Java primitives
byte,short,int,long, they are considered Pythonintobjects. - When foreign numbers are represented as Java primitives
float,double, they are considered Pythonfloatobjects. - When foreign booleans are represented as Java primitives
boolean, they are considered Pythonboolobjects.
Python to Interop Types
The following table shows how Python objects are converted to interop types when passed to other languages:
| Interop Type | Python Interpretation |
|---|---|
array |
Any object with __getitem__ and __len__ methods, but not if it also has keys, values, and items methods (in the same way that dict does.) |
boolean |
Only subtypes of Python bool. Note that in contrast to Python semantics, Python bool is never also an interop number. |
exception |
Any Python BaseException subtype. |
executable |
Any Python object with a __call__ method. |
hash |
Only subtypes of dict. |
instantiable |
Any Python type. |
iterable |
Any Python object that has __iter__ or __getitem__ methods. |
iterator |
Any Python object with a __next__ method. |
members |
Any Python object. Note that the rules for readable/writable are a bit ad-hoc, since checking that is not part of the Python MOP. |
MetaObject |
Any Python type. |
null |
Only None. |
number |
Only subtypes of int and float. |
string |
Only subtypes of str. |
The Interoperability Extension API
You can extend the interoperability protocol directly from Python through a simple API defined in the polyglot module.
This API lets you define interoperability behavior for custom or user-defined types that are not automatically supported.
This is particularly useful for external types which are not compatible by default with the interop protocol.
For example, numpy numeric types (for example, numpy.int32) which are not supported by default by the interop protocol need special handling to work properly with other languages.
The polyglot module provides these functions for customizing interop behavior:
| Function | Description |
|---|---|
register_interop_behavior |
Takes the receiver type as the first argument. The remaining keyword arguments correspond to the respective interop messages. Not all interop messages are supported. |
get_registered_interop_behavior |
Takes the receiver type as the first argument. Returns the list of extended interop messages for the given type. |
@interop_behavior |
Class decorator that takes the receiver type as the only argument. The interop messages are extended via static methods defined in the decorated class (supplier). |
register_interop_type |
Takes a foreign class and python class as positional arguments and allow_method_overwrites as an optional argument (default: False). Every instance of the foreign class is then treated as an instance of the given python class. |
@interop_type |
Class decorator that takes the foreign class and optionally allow_method_overwrites as arguments. The instances of the foreign class will be treated as an instance of the annotated python class. |
Interop behavior usage example
You can use the register_interop_behavior API to add custom interop behavior to existing types:
For example, to make numpy.int32 work properly with other languages:
import polyglot
import numpy
polyglot.register_interop_behavior(numpy.int32,
is_number=True,
fitsInByte=lambda v: -128 <= v < 128,
fitsInShort=lambda v: -0x8000 <= v < 0x8000,
fitsInInt = True,
fitsInLong = True,
fitsInBigInteger = True,
asByte = int,
asShort = int,
asInt = int,
asLong = int,
asBigInteger = int,
)
Alternatively, you can use the @interop_behavior decorator when you need to define multiple behaviors for a type.
With this decorator, you define interop behaviors using static methods in a decorated class.
The static method names must match the keyword argument names used by register_interop_behavior.
The following example uses the decorator approach for numpy.float64:
from polyglot import interop_behavior
import numpy
@interop_behavior(numpy.float64)
class Float64InteropBehaviorSupplier:
@staticmethod
def is_number(_):
return True
@staticmethod
def fitsInDouble(_):
return True
@staticmethod
def asDouble(v):
return float(v)
Both classes can then behave as expected when embedded:
import java.nio.file.Files;
import java.nio.file.Path;
import org.graalvm.polyglot.Context;
class Main {
public static void main(String[] args) {
try (var context = Context.create()) {
context.eval("python", Files.readString(Path.of("path/to/interop/behavior/script.py")));
assert context.eval("python", "numpy.float64(12)").asDouble() == 12.0;
assert context.eval("python", "numpy.int32(12)").asByte() == 12;
}
}
}
Interop types usage example
The register_interop_type API allows the usage of python classes for foreign objects.
When you register a Python class for a foreign type, instances of that foreign object will no longer have the default polyglot.ForeignObject or polyglot.Foreign* class.
Instead, GraalPy creates a new generated class that inherits from both your Python class and polyglot.ForeignObject.
This lets you add Python methods to foreign objects, and map foreign functionality to Python’s magic methods or more idiomatic Python patterns.
This is a simple Java class to customize:
package org.example;
class MyJavaClass {
private int x;
private int y;
public MyJavaClass(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
The following snippet sets up the Java environment and makes the object available to Python:
import org.example.MyJavaClass;
class Main {
public static void main(String[] args) {
MyJavaClass myJavaObject = new MyJavaClass(42, 17);
try (var context = Context.create()) {
// myJavaObject will be globally available in example.py as my_java_object
context.getBindings("python").putMember("my_java_object", myJavaObject);
context.eval(Source.newBuilder("python", "example.py"));
}
}
}
This snippet states how to customize the Java object’s behavior using Python classes:
# example.py
import java
from polyglot import register_interop_type
print(my_java_object.getX()) # 42
print(type(my_java_object)) # <class 'polyglot.ForeignObject'>
class MyPythonClass:
def get_tuple(self):
return (self.getX(), self.getY())
foreign_class = java.type("org.example.MyJavaClass")
register_interop_type(foreign_class, MyPythonClass)
print(my_java_object.get_tuple()) # (42, 17)
print(type(my_java_object)) # <class 'polyglot.Java_org.example.MyJavaClass_generated'>
print(type(my_java_object).mro()) # [polyglot.Java_org.example.MyJavaClass_generated, MyPythonClass, polyglot.ForeignObject, object]
class MyPythonClassTwo:
def get_tuple(self):
return (self.getY(), self.getX())
def __str__(self):
return f"MyJavaInstance(x={self.getX()}, y={self.getY()})"
# If 'allow_method_overwrites=True' is not given, this would lead to an error due to the method conflict of 'get_tuple'
register_interop_type(foreign_class, MyPythonClassTwo, allow_method_overwrites=True)
# A newly registered class will be before already registered classes in the mro.
# It allows overwriting methods from already registered classes with the flag 'allow_method_overwrites=True'
print(type(my_java_object).mro()) # [generated_class, MyPythonClassTwo, MyPythonClass, polyglot.ForeignObject, object]
print(my_java_object.get_tuple()) # (17, 42)
print(my_java_object) # MyJavaInstance(x=42, y=17)
For simpler cases, you can use the @interop_type decorator:
import java
from polyglot import interop_type
foreign_class = java.type("org.example.MyJavaClass")
@interop_type(foreign_class)
class MyPythonClass:
def get_tuple(self):
return (self.getX(), self.getY())
Supported messages
Most interop messages are supported by the interop behavior extension API. The naming convention for register_interop_behavior keyword arguments uses snake_case, so the interop fitsInLong message becomes fits_in_long.
Each message can be extended with either a pure Python function (no default keyword arguments, free vars, or cell vars allowed) or a boolean constant.
The following table describes the supported interop messages:
| Message | Extension argument name | Expected return type |
|---|---|---|
isBoolean |
is_boolean |
bool |
isDate |
is_date |
bool |
isDuration |
is_duration |
bool |
isExecutable |
is_executable |
bool |
isIterator |
is_iterator |
bool |
isNumber |
is_number |
bool |
isString |
is_string |
bool |
isTime |
is_time |
bool |
isTimeZone |
is_time_zone |
bool |
fitsInBigInteger |
fits_in_big_integer |
bool |
fitsInByte |
fits_in_byte |
bool |
fitsInDouble |
fits_in_double |
bool |
fitsInFloat |
fits_in_float |
bool |
fitsInInt |
fits_in_int |
bool |
fitsInLong |
fits_in_long |
bool |
fitsInShort |
fits_in_short |
bool |
asBigInteger |
as_big_integer |
int |
asBoolean |
as_boolean |
bool |
asByte |
as_byte |
int |
asDate |
as_date |
tuple: (year: int, month: int, day: int) |
asDouble |
as_double |
float |
asDuration |
as_duration |
tuple: (seconds: int, nano_adjustment: int) |
asFloat |
as_float |
float |
asInt |
as_int |
int |
asLong |
as_long |
int |
asShort |
as_short |
int |
asString |
as_string |
str |
asTime |
as_time |
tuple: (hour: int, minute: int, second: int, microsecond: int) |
asTimeZone |
as_time_zone |
str (timezone name) or int (UTC delta in seconds) |
execute |
execute |
object |
readArrayElement |
read_array_element |
object |
getArraySize |
get_array_size |
int |
hasArrayElements |
has_array_elements |
bool |
isArrayElementReadable |
is_array_element_readable |
bool |
isArrayElementModifiable |
is_array_element_modifiable |
bool |
isArrayElementInsertable |
is_array_element_insertable |
bool |
isArrayElementRemovable |
is_array_element_removable |
bool |
removeArrayElement |
remove_array_element |
None |
writeArrayElement |
write_array_element |
None |
hasIterator |
has_iterator |
bool |
hasIteratorNextElement |
has_iterator_next_element |
bool |
getIterator |
get_iterator |
Python iterator |
getIteratorNextElement |
get_iterator_next_element |
object |
hasHashEntries |
has_hash_entries |
bool |
getHashEntriesIterator |
get_hash_entries_iterator |
Python iterator |
getHashKeysIterator |
get_hash_keys_iterator |
Python iterator |
getHashSize |
get_hash_size |
int |
getHashValuesIterator |
get_hash_values_iterator |
Python iterator |
isHashEntryReadable |
is_hash_entry_readable |
bool |
isHashEntryModifiable |
is_hash_entry_modifiable |
bool |
isHashEntryInsertable |
is_hash_entry_insertable |
bool |
isHashEntryRemovable |
is_hash_entry_removable |
bool |
readHashValue |
read_hash_value |
object |
writeHashEntry |
write_hash_entry |
None |
removeHashEntry |
remove_hash_entry |
None |
Embedding Build Tools
Note: The GraalPy build tools are being developed GraalPy Extensions repository on GitHub.
The GraalPy Maven and Gradle plugins simplify embedding Python in Java applications by automatically managing Python resources during your build process:
- Your Python code: Application files, modules, and scripts that are part of your project
- Third-party packages: Python libraries (like NumPy, requests) automatically installed in the build according to your plugin configuration.
These plugins handle the complexity of packaging Python code with your Java application, ensuring all dependencies are available at runtime but you need to configure your Java application to access them at runtime. The GraalPyResources API provides factory methods that create a preconfigured GraalPy Context.
The preconfigured GraalPy Context is a GraalVM Context that has been automatically set up with the right settings to access your Python resources without you having to manually configure all the details.
Deployment
You can choose between two deployment approaches:
- Virtual Filesystem: Resources are embedded within your JAR or executable
- External Directory: Resources are stored in a separate directory
Virtual Filesystem
With the Virtual Filesystem approach, your Python resources are embedded directly inside your JAR file or Native Image executable as standard Java resources. This creates a self-contained application with everything bundled together.
This approach involves the following steps:
- Python files are packaged as Java resources in dedicated resource directories (like
src/main/resources) - Multiple resource directories are merged during the build process by Maven or Gradle
- You can configure the Java resource path (default:
org.graalvm.python.vfs) that gets mapped to a Virtual Filesystem mount point in Python (default:/graalpy_vfs) - GraalPy’s Virtual Filesystem transparently maps these resources to Python file paths
- Your Python code can use normal file operations (
open(),import, etc.) without knowing the files are embedded
For example, a file at src/main/resources/org.graalvm.python.vfs/src/mymodule.py becomes accessible to Python as /graalpy_vfs/src/mymodule.py.
You can customize the resource path (default: org.graalvm.python.vfs) and mount point (default: /graalpy_vfs) to avoid conflicts with other libraries.
To use the Virtual Filesystem in your Java application, use the factory methods in the GraalPyResources API:
GraalPyResources.createContext()- Creates a ready-to-use context with default Virtual Filesystem configurationGraalPyResources.contextBuilder()- Returns a context builder for additional customization before creating the contextGraalPyResources.contextBuilder(VirtualFileSystem)- Returns a context builder with a custom Virtual Filesystem configuration
Java Resource Path
When building reusable libraries, use a unique Java resource path to prevent conflicts with other Virtual Filesystem users. This ensures your library’s Python resources don’t interfere with other libraries on the classpath.
The recommended path is:
GRAALPY-VFS/${project.groupId}/${project.artifactId}
This path must be configured identically in both your build plugin and runtime code using the VirtualFileSystem$Builder#resourceDirectory API.
Java Module System compatibility: The “GRAALPY-VFS” prefix bypasses module encapsulation rules since it’s not a valid Java package name, eliminating the need for additional module system configuration that would otherwise be required for accessing resources in named modules.
Extracting files from Virtual Filesystem
Some files need to exist on the real filesystem rather than staying embedded as Java resources.
This is required for Python C extensions (.so, .dylib, .pyd, .dll) and font files (.ttf) that must be accessed by the operating system loader outside the Truffle sandbox.
GraalPy automatically extracts these file types to a temporary directory when first accessed, then delegates to the real files for subsequent operations.
Use the VirtualFileSystem$Builder#extractFilter API to modify which files get extracted automatically. For full control, extract all resources to a user-defined directory before creating your GraalPy context:
GraalPyResources.extractVirtualFileSystemResources(VirtualFileSystem vfs, Path externalResourcesDirectory)- Extract resources to a specified directoryGraalPyResources.contextBuilder(Path externalResourcesDirectory)- Create a context builder using the extracted resources directory
For more information, see GraalPyResources.
External Directory
With the External Directory approach, your Python resources are stored in a separate directory on the filesystem rather than being embedded as Java resources. This creates a deployment where Python files exist as regular files that you must distribute alongside your application.
This approach involves the following steps:
- Python files remain as regular filesystem files (not embedded as Java resources)
- You are responsible for deploying and managing the external directory
- Python code accesses files directly from the real filesystem
- Smaller JAR/executable size since Python resources aren’t embedded
To use an external directory, create your GraalPy context with:
GraalPyResources.createContextBuilder(Path)- Creates a context builder pointing to your external directory path
Directory Structure
The GraalPyResources factory methods rely on this directory structure, which includes a standard Python virtual environment in the venv subdirectory:
| Directory | Purpose | Management | Python Path |
|---|---|---|---|
${root}/src/ |
Your Python application code | You manage | Default search path (equivalent to PYTHONPATH) |
${root}/venv/ |
Third-party Python packages | Plugin manages | Context configured as if executed from this virtual environment |
The ${root} placeholder refers to different locations depending on your deployment approach:
- Virtual Filesystem:
/graalpy_vfs(Python) /${project_resources_directory}/org.graalvm.python.vfs(Java) - External Directory: Filesystem path like
python-resources/
The GraalPy Context is automatically configured to run within this virtual environment, making all installed packages available for import.
Important: Plugin completely manages
venv/- any manual changes will be overridden during builds.
Python Dependency Management for Reproducible Builds
Python packages typically specify dependencies as version ranges (e.g., B>=2.0.0) rather than fixed versions.
This means today’s build might install B==2.0.0, but tomorrow’s clean build could pull the newly released B==2.0.1, potentially introducing breaking changes or GraalPy incompatibilities.
Locking Dependencies
We highly recommend locking all Python dependencies when packages change. Run a Maven goal or Gradle task to generate graalpy.lock, which captures exact versions of all dependencies (those specified explicitly in the pom.xml or build.gradle files and all their transitive dependencies).
Commit the graalpy.lock file to version control (e.g., git). Once this file exists, Maven or Gradle builds will install the exact same package versions captured in the graalpy.lock file.
If you modify dependencies in pom.xml or build.gradle and they no longer match what’s in graalpy.lock, the build will fail and the user will be asked to explicitly regenerate the graalpy.lock file.
We recommend specifying dependencies without version numbers in the pom.xml or build.gradle file. GraalPy automatically installs compatible versions for well-known packages.
Once installed, lock these versions to ensure reproducible builds.
See the “Locking Python Packages” sections below for specific Maven and Gradle commands.
For information on the specific Maven or Gradle lock packages actions, see the Locking Python Packages section.
GraalPy Maven Plugin
The GraalPy Maven Plugin automates Python resource management in Maven-based Java projects. It downloads Python packages, creates virtual environments, and configures deployment for both Virtual Filesystem (embedded) and External Directory approaches.
Maven Plugin Configuration
Configure the plugin in your pom.xml file with these elements:
| Element | Description |
|---|---|
packages |
Python dependencies using pip syntax (e.g., requests>=2.25.0) - optional |
requirementsFile |
Path to pip-compatible requirements.txt file - optional, mutually exclusive with packages |
resourceDirectory |
Custom path for Virtual Filesystem deployment (must match Java runtime configuration) |
externalDirectory |
Path for External Directory deployment (mutually exclusive with resourceDirectory) |
Add the plugin configuration to your pom.xml file:
<plugin>
<groupId>org.graalvm.python</groupId>
<artifactId>graalpy-maven-plugin</artifactId>
<configuration>
<!-- Python packages (pip-style syntax) -->
<packages>
<package>termcolor==2.2</package>
</packages>
<!-- Choose ONE deployment approach: -->
<!-- Virtual Filesystem (embedded) -->
<resourceDirectory>GRAALPY-VFS/${project.groupId}/${project.artifactId}</resourceDirectory>
<!-- OR External Directory (separate files) -->
<externalDirectory>${basedir}/python-resources</externalDirectory>
</configuration>
</plugin>
Using requirements.txt
The requirementsFile element declares a path to a pip-compatible requirements.txt file.
When configured, the plugin forwards this file directly to pip using pip install -r,
allowing full use of pip’s native dependency format.
<configuration>
<requirementsFile>requirements.txt</requirementsFile>
...
</configuration>
Important: You must configure either
packagesorrequirementsFile, but not both.When
requirementsFileis used:
- the GraalPy lock file is not created and not used
- the
lock-packagesgoal is disabled- dependency locking must be handled externally by pip (for example using
pip freeze)Mixing
packagesandrequirementsFilein the same configuration is not supported.
Excluding Build-Only Packages
You can remove build-only packages from final JAR using maven-jar-plugin:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
<configuration>
<excludes>
<exclude>**/site-packages/pip*/**</exclude>
<exclude>**/site-packages/setuptools*/**</exclude>
</excludes>
</configuration>
</plugin>
Locking Python Packages
Generate a lock file to ensure reproducible builds:
$ mvn org.graalvm.python:graalpy-maven-plugin:lock-packages
Note: This action overwrites any existing graalpy.lock file.
To customize the lock file path, configure graalPyLockFile :
<configuration>
<graalPyLockFile>${basedir}/graalpy.lock</graalPyLockFile>
</configuration>
Note: This only changes the path (defaults to ${basedir}/graalpy.lock). To generate the lock file, run the
lock-packagesgoal.
For more information of this feature, please see the Python Dependency Management for Reproducible Builds section.
GraalPy Gradle Plugin
The GraalPy Gradle Plugin automates Python resource management in Gradle-based Java projects. It downloads Python packages, creates virtual environments, and configures deployment for both Virtual Filesystem (embedded) and External Directory approaches.
Gradle Plugin Configuration
Configure the plugin in your build.gradle file with these elements:
| Element | Description |
|---|---|
packages |
Python dependencies using pip syntax (e.g., requests>=2.25.0) |
resourceDirectory |
Custom path for Virtual Filesystem deployment (must match Java runtime configuration) |
externalDirectory |
Path for External Directory deployment (mutually exclusive with resourceDirectory) |
Add the plugin configuration to your build.gradle file:
plugins {
id 'org.graalvm.python' version '25.0.1'
}
graalPy {
// Python packages (pip-style syntax)
packages = ["termcolor==2.2"]
// Choose ONE deployment approach:
// Virtual Filesystem (embedded)
resourceDirectory = "GRAALPY-VFS/my.group.id/artifact.id"
// OR External Directory (separate files)
externalDirectory = file("$rootDir/python-resources")
}
The plugin automatically injects these dependencies of the same version as the plugin version:
org.graalvm.python:pythonorg.graalvm.python:python-embedding
Locking Python Packages
Generate a lock file to ensure reproducible builds:
gradle graalPyLockPackages
Note: This overwrites any existing graalpy.lock file.
To customize the lock file path, configure graalPyLockFile:
graalPy {
graalPyLockFile = file("$rootDir/graalpy.lock")
...
}
Note: This only changes the path (defaults to $rootDir/graalpy.lock). To generate the lock file, run the
graalPyLockPackagestask.
For more information of this feature, please see the Python Dependency Management for Reproducible Builds section.
Related Documentation
Permissions for Python Embeddings
Access Control and Security Limits for Python Scripts
Embedding GraalPy into Java works with the GraalVM Polyglot Sandbox.
Python’s POSIX Interface
The way the operating system interface is exposed to Python scripts is GraalPy-specific. By default all access is routed through Java interfaces, but some packages rely on details of POSIX APIs and require direct native access.
Graal languages (those implemented on the Truffle framework) usually implement system-related functions using the Truffle abstraction layer, which is OS independent and provides extension points for the users when embedding GraalPy or other Graal languages into Java applications. See, for example, the Truffle FileSystem service-provider.
The standard Python library also provides an OS abstraction, but exposes lower level interfaces. For example, the os module directly exposes some POSIX functions.
On non-POSIX platforms, this interface is emulated to a degree.
GraalPy provides two alternative implementations (each referred to as a “backend”) of system-related functionality offered by built-in Python modules such as os.
The PosixModuleBackend option determines which backend is used: valid values are native and java.
Native Backend
This backend directly calls the POSIX API in mostly the same way as CPython (the reference Python implementation).
This approach is the most compatible with CPython and provides bare access to the underlying OS interface without an intermediate emulation layer.
By default, this implementation bypasses the Truffle abstraction layer, and therefore it is not sandboxed and does not support custom implementations of Truffle FileSystem service-provider, and other Polyglot API providers related to system interfaces.
The native backend is chosen by default when GraalPy is started via the graalpy or any other Python related launcher.
Limitations of the Native Backend
Known limitations are:
os.forkis not supported_posixsubprocess.fork_execdoes not support thepreexec_fnparameter
Java Backend
This backend uses the Truffle abstraction layer and therefore supports custom Polyglot API providers related to system interfaces and sandboxing. Because this abstraction is POSIX agnostic, it does not expose all the necessary functionality. Some functionality is emulated, and some functionality is unsupported.
The Java backend is the default when GraalPy is run via the Context API, that is, embedded in Java applications.
Limitations of the Java Backend
GraalPy can log information about known incompatibility of functions executed at runtime, which includes the OS interface-related functions.
To turn on this logging, use the command-line option --log.python.compatibility.level=FINE (or other desired logging level).
Known limitations of the Java backend are:
- Its state is disconnected from the actual OS state, which applies especially to:
- file descriptors: Python-level file descriptors are not usable in native code.
- current working directory: is initialized to the current working
directory at the startup, but is then maintained separately. So, for example,
chdirin Python does not change the actual current working directory of the process. - umask: has the same limitation as that of the current working directory, but it is always initialized
to
0022regardless of the actual system value at startup.
- Resolution of file access/modification times depends on the JDK. The best the Java backend can guarantee is seconds resolution.
os.accessand any other functionality based onfaccessatPOSIX function does not support:- effective IDs.
follow_symlinks=Falseunless the mode is onlyF_OK.
Python Native Extensions
Python native extensions run by default as native binaries, with full access to the underlying system. See Embedding limitations
Tooling Support for Python
Debugging
GraalPy provides the standard Python debugger pdb.
Refer to the official PDB documentation for usage.
The built-in breakpoint() function uses pdb by default.
GraalPy also comes with built-in support for graphical debugging through Chrome’s developer tools.
To enable the debugger, pass the --inspect command-line option.
You can inspect variables, set watch expressions, interactively evaluate code snippets, and so on.
- Run a Python script using the command-line option
--inspect:graalpy --inspect my_script.py - You should see output similar to:
Debugger listening on ws://127.0.0.1:9229/VrhCaY7wR5tIqy2zLsdFr3f7ixY3QB6kVQ0S54_SOMo For help, see: https://www.graalvm.org/tools/chrome-debugger E.g. in Chrome open: devtools://devtools/bundled/js_app.html?ws=127.0.0.1:9229/VrhCaY7wR5tIqy2zLsdFr3f7ixY3QB6kVQ0S54_SOMo -
Open your Chrome browser and enter the URL provided. Now you can inspect the stack, variables, evaluate variables, and selected expressions in a tooltip, and so on. For example:

Profiling
GraalPy provides three main profiling capabilities: a CPU sampler, a CPU tracer, and a memory tracer.
These are described below. (For details, use the graalpy --help:tools command.)
CPU Sampler
Use the --cpusampler command-line option to take a CPU sample. For example:
graalpy --cpusampler my_script.py
You should see output similar to:
CPU Sampler Output (Click to expand)
--------------------------------------------------------------------------------------------------------------------------------------------------------------
Sampling Histogram. Recorded 564 samples with period 10ms. Missed 235 samples.
Self Time: Time spent on the top of the stack.
Total Time: Time spent somewhere on the stack.
--------------------------------------------------------------------------------------------------------------------------------------------------------------
Thread[main,5,main]
Name || Total Time || Self Time || Location
--------------------------------------------------------------------------------------------------------------------------------------------------------------
parse_starttag || 1090ms 19.3% || 570ms 10.1% || <install-dir>/lib/python3.10/html/parser.py~300-347:11658-13539
match || 190ms 3.4% || 190ms 3.4% || <venv-dir>/lib/python3.10/site-packages/soupsieve/css_parser.py~320-323:9862-10026
_replace_cdata_list_attribute_values || 190ms 3.4% || 190ms 3.4% || <venv-dir>/lib/python3.10/site-packages/bs4/builder/__init__.py~295-331:11245-13031
goahead || 1430ms 25.4% || 150ms 2.7% || <install-dir>/lib/python3.10/html/parser.py~133-250:4711-9678
check_for_whole_start_tag || 130ms 2.3% || 130ms 2.3% || <install-dir>/lib/python3.10/html/parser.py~351-382:13647-14758
<module> || 800ms 14.2% || 130ms 2.3% || <venv-dir>/lib/python3.10/site-packages/soupsieve/css_parser.py~1-1296:0-47061
...
--------------------------------------------------------------------------------------------------------------------------------------------------------------
CPU Tracer
Use the --cputracer --cputracer.TraceStatements command-line options to trace CPU usage. For example:
graalpy --cputracer --cputracer.TraceStatements my_script.py
You should see output similar to:
CPU Tracer Output (Click to Expand)
--------------------------------------------------------------------------------------------------------------------
Tracing Histogram. Counted a total of 1135 element executions.
Total Count: Number of times the element was executed and percentage of total executions.
Interpreted Count: Number of times the element was interpreted and percentage of total executions of this element.
Compiled Count: Number of times the compiled element was executed and percentage of total executions of this element.
--------------------------------------------------------------------------------------------------------------------
Name | Total Count | Interpreted Count | Compiled Count | Location
--------------------------------------------------------------------------------------------------------------------
get_newfunc_typeid | 110 9.7% | 110 100.0% | 0 0.0% | capi.c~596:0
PyTruffle_PopulateType | 110 9.7% | 110 100.0% | 0 0.0% | capi.c~721:0
PyTruffle_AllocMemory | 86 7.6% | 86 100.0% | 0 0.0% | obmalloc.c~77:0
PyTruffle_AllocateType | 66 5.8% | 66 100.0% | 0 0.0% | capi.c~874:0
PyMem_RawMalloc | 66 5.8% | 66 100.0% | 0 0.0% | obmalloc.c~170:0
initialize_type_structure | 50 4.4% | 50 100.0% | 0 0.0% | capi.c~181:0
_Py_TYPE | 45 4.0% | 45 100.0% | 0 0.0% | object_shared.c~55:0
PyType_GetFlags | 41 3.6% | 41 100.0% | 0 0.0% | typeobject_shared.c~44:0
get_tp_name | 37 3.3% | 37 100.0% | 0 0.0% | capi.c~507:0
...
--------------------------------------------------------------------------------------------------------------------
Memory Tracer
Use the --memtracer --memtracer.TraceStatements command-line options to trace memory usage. For example:
graalpy --experimental-options --memtracer --memtracer.TraceStatements my_script.py
You should see output similar to:
Memory Tracer Output (Click to Expand)
----------------------------------------------------------------------------
Location Histogram with Allocation Counts. Recorded a total of 565 allocations.
Total Count: Number of allocations during the execution of this element.
Self Count: Number of allocations in this element alone (excluding sub calls).
----------------------------------------------------------------------------
Name | Self Count | Total Count | Location
----------------------------------------------------------------------------
PyTruffle_PopulateType | 440 77.9% | 440 77.9% | capi.c~721:0
PyType_Ready | 61 10.8% | 68 12.0% | typeobject.c~463:0
_PyObject_MakeTpCall | 20 3.5% | 24 4.2% | object.c~155:0
PyUnicode_FromString | 11 1.9% | 11 1.9% | capi.c~2161:0
PyErr_NewException | 11 1.9% | 11 1.9% | capi.c~1537:0
_PyUnicode_AsASCIIString | 6 1.1% | 6 1.1% | capi.c~2281:0
PyDict_New | 4 0.7% | 4 0.7% | capi.c~1505:0
PyTuple_New | 4 0.7% | 4 0.7% | capi.c~2097:0
PyUnicode_FromStringAndSize | 3 0.5% | 3 0.5% | unicodeobject.c~171:0
...
----------------------------------------------------------------------------
Coverage
GraalPy provides its own implementation of the Coverage.py tool to measure code coverage of Python programs.
Enable it using the --coverage command-line option, as shown below.
(For details, use the graalpy --help:tools command.)
graalpy --coverage my_script.py
You should see output similar to:
CPU Sampler Output (Click to expand)
------------------------------------------------------------------------------------------------------------------------------------------------
Code coverage histogram.
Shows what percent of each element was covered during execution
------------------------------------------------------------------------------------------------------------------------------------------------
Path | Statements | Lines | Roots
------------------------------------------------------------------------------------------------------------------------------------------------
<venv-dir>/lib/python3.10/site-packages/_distutils_hack/__init__.py | 0.00% | 0.00% | 0.00%
<venv-dir>/lib/python3.10/site-packages/bs4/__init__.py | 56.10% | 56.14% | 55.26%
<venv-dir>/lib/python3.10/site-packages/bs4/builder/__init__.py | 79.12% | 78.84% | 50.00%
<venv-dir>/lib/python3.10/site-packages/bs4/builder/_html5lib.py | 2.41% | 2.46% | 2.38%
<venv-dir>/lib/python3.10/site-packages/bs4/builder/_htmlparser.py | 69.08% | 68.67% | 83.33%
<venv-dir>/lib/python3.10/site-packages/bs4/builder/_lxml.py | 3.72% | 3.78% | 4.00%
<venv-dir>/lib/python3.10/site-packages/bs4/css.py | 32.73% | 31.48% | 15.38%
<venv-dir>/lib/python3.10/site-packages/bs4/dammit.py | 65.46% | 65.29% | 24.14%
<venv-dir>/lib/python3.10/site-packages/bs4/element.py | 44.15% | 43.13% | 31.08%
<venv-dir>/lib/python3.10/site-packages/bs4/formatter.py | 73.49% | 74.36% | 66.67%
<venv-dir>/lib/python3.10/site-packages/certifi/__init__.py | 100.00% | 100.00% | 100.00%
<venv-dir>/lib/python3.10/site-packages/certifi/core.py | 33.33% | 33.33% | 25.00%
<venv-dir>/lib/python3.10/site-packages/charset_normalizer/__init__.py | 100.00% | 100.00% | 100.00%
<venv-dir>/lib/python3.10/site-packages/charset_normalizer/api.py | 11.87% | 11.94% | 16.67%
<venv-dir>/lib/python3.10/site-packages/charset_normalizer/assets/__init__.py | 100.00% | 100.00% | 100.00%
<venv-dir>/lib/python3.10/site-packages/charset_normalizer/cd.py | 12.81% | 13.54% | 4.35%
<venv-dir>/lib/python3.10/site-packages/charset_normalizer/constant.py | 100.00% | 100.00% | 100.00%
<venv-dir>/lib/python3.10/site-packages/charset_normalizer/legacy.py | 25.00% | 25.00% | 50.00%
<venv-dir>/lib/python3.10/site-packages/charset_normalizer/md.py | 22.05% | 20.37% | 17.24%
<venv-dir>/lib/python3.10/site-packages/charset_normalizer/models.py | 38.46% | 38.50% | 9.30%
<venv-dir>/lib/python3.10/site-packages/charset_normalizer/utils.py | 26.79% | 26.89% | 3.33%
<venv-dir>/lib/python3.10/site-packages/charset_normalizer/version.py | 100.00% | 100.00% | 100.00%
<venv-dir>/lib/python3.10/site-packages/idna/__init__.py | 100.00% | 100.00% | 100.00%
<install-dir>/lib/python3.10/collections/abc.py | 100.00% | 100.00% | 100.00%
<install-dir>/lib/python3.10/contextlib.py | 40.80% | 37.99% | 31.71%
<install-dir>/lib/python3.10/copy.py | 36.36% | 36.41% | 21.43%
<install-dir>/lib/python3.10/copyreg.py | 3.20% | 3.20% | 7.69%
<install-dir>/lib/python3.10/csv.py | 25.17% | 23.91% | 25.00%
<install-dir>/lib/python3.10/datetime.py | 30.32% | 30.01% | 14.74%
<install-dir>/lib/python3.10/email/__init__.py | 42.86% | 42.86% | 20.00%
<install-dir>/lib/python3.10/email/_encoded_words.py | 35.11% | 34.44% | 14.29%
<install-dir>/lib/python3.10/email/_parseaddr.py | 12.64% | 12.15% | 10.71%
<install-dir>/lib/python3.10/email/_policybase.py | 55.22% | 54.69% | 39.29%
<install-dir>/lib/python3.10/email/base64mime.py | 35.00% | 35.00% | 20.00%
<install-dir>/lib/python3.10/typing.py | 49.86% | 48.93% | 34.60%
<install-dir>/lib/python3.10/urllib/__init__.py | 100.00% | 100.00% | 100.00%
<install-dir>/lib/python3.10/warnings.py | 21.29% | 20.77% | 25.00%
<install-dir>/lib/python3.10/weakref.py | 37.93% | 36.78% | 23.68%
<install-dir>/lib/python3.10/zipfile.py | 17.86% | 17.23% | 11.03%
<src-dir>/my_script.py | 100.00% | 100.00% | 100.00%
------------------------------------------------------------------------------------------------------------------------------------------------
Trace
The standard Python trace module is also provided.
Note: This works in the same way as CPython. The programmatic API also works, with some limitations: it does not currently track calls, only line counts and called functions.
For example, running this command:
graalpy -m trace -c -s text_styler.py Welcome to GraalPy!
You should see output similar to:
CPU Tracer Output (Click to Expand)
_ __ __ __
| | / /__ / /________ ____ ___ ___ / /_____
| | /| / / _ \/ / ___/ __ \/ __ `__ \/ _ \ / __/ __ \
| |/ |/ / __/ / /__/ /_/ / / / / / / __/ / /_/ /_/ /
|__/|__/\___/_/\___/\____/_/ /_/ /_/\___/ \__/\____/
______ ______ __
/ ____/________ _____ _/ / __ \__ __/ /
/ / __/ ___/ __ `/ __ `/ / /_/ / / / / /
/ /_/ / / / /_/ / /_/ / / ____/ /_/ /_/
\____/_/ \__,_/\__,_/_/_/ \__, (_)
/____/
lines cov% module (path)
9 100% __about__ (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__about__.py)
51 100% __future__ (<install-dir>/lib/python3.10/__future__.py)
1 100% __init__ (<venv-dir>/lib/python3.10/site-packages/pyfiglet/fonts/__init__.py)
27 100% _adapters (<install-dir>/lib/python3.10/importlib/_adapters.py)
25 100% _common (<install-dir>/lib/python3.10/importlib/_common.py)
44 100% _manylinux (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/_manylinux.py)
20 100% _musllinux (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/_musllinux.py)
66 100% _osx_support (<install-dir>/lib/python3.10/_osx_support.py)
43 100% _parseaddr (<install-dir>/lib/python3.10/email/_parseaddr.py)
62 100% _policybase (<install-dir>/lib/python3.10/email/_policybase.py)
20 100% _structures (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/_structures.py)
105 100% abc (<install-dir>/lib/python3.10/importlib/abc.py)
18 100% actions (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/actions.py)
41 100% appdirs (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/appdirs.py)
59 100% base64 (<install-dir>/lib/python3.10/base64.py)
14 100% base64mime (<install-dir>/lib/python3.10/email/base64mime.py)
11 100% bisect (<install-dir>/lib/python3.10/bisect.py)
124 100% calendar (<install-dir>/lib/python3.10/calendar.py)
94 100% charset (<install-dir>/lib/python3.10/email/charset.py)
122 100% common (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/common.py)
40 100% context (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/jaraco/context.py)
3 100% contextlib (<install-dir>/lib/python3.10/contextlib.py)
91 100% copy (<install-dir>/lib/python3.10/copy.py)
1497 100% core (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/core.py)
108 100% dataclasses (<install-dir>/lib/python3.10/dataclasses.py)
31 100% datetime (<install-dir>/lib/python3.10/datetime.py)
9 100% encoders (<install-dir>/lib/python3.10/email/encoders.py)
2493 100% entities (<install-dir>/lib/python3.10/html/entities.py)
58 100% errors (<install-dir>/lib/python3.10/email/errors.py)
49 100% exceptions (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/exceptions.py)
5 100% expat (<install-dir>/lib/python3.10/xml/parsers/expat.py)
41 100% feedparser (<install-dir>/lib/python3.10/email/feedparser.py)
45 100% functools (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/jaraco/functools.py)
69 100% gettext (<install-dir>/lib/python3.10/gettext.py)
56 100% header (<install-dir>/lib/python3.10/email/header.py)
162 100% helpers (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/helpers.py)
1 100% inspect (<install-dir>/lib/python3.10/inspect.py)
47 100% linecache (<install-dir>/lib/python3.10/linecache.py)
95 100% markers (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/markers.py)
192 100% more (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/more_itertools/more.py)
204 100% optparse (<install-dir>/lib/python3.10/optparse.py)
14 100% os (<install-dir>/lib/python3.10/os.py)
167 100% parse (<install-dir>/lib/python3.10/urllib/parse.py)
19 100% parser (<install-dir>/lib/python3.10/email/parser.py)
242 100% pathlib (<install-dir>/lib/python3.10/pathlib.py)
66 100% pkgutil (<install-dir>/lib/python3.10/pkgutil.py)
137 100% platform (<install-dir>/lib/python3.10/platform.py)
102 100% plistlib (<install-dir>/lib/python3.10/plistlib.py)
79 100% pprint (<install-dir>/lib/python3.10/pprint.py)
54 100% queue (<install-dir>/lib/python3.10/queue.py)
21 100% quopri (<install-dir>/lib/python3.10/quopri.py)
32 100% quoprimime (<install-dir>/lib/python3.10/email/quoprimime.py)
101 100% random (<install-dir>/lib/python3.10/random.py)
43 100% recipes (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/more_itertools/recipes.py)
51 100% requirements (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/requirements.py)
46 100% resources (<install-dir>/lib/python3.10/importlib/resources.py)
155 100% results (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/results.py)
79 100% selectors (<install-dir>/lib/python3.10/selectors.py)
30 100% signal (<install-dir>/lib/python3.10/signal.py)
94 100% socket (<install-dir>/lib/python3.10/socket.py)
143 100% specifiers (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/specifiers.py)
50 100% string (<install-dir>/lib/python3.10/string.py)
118 100% subprocess (<install-dir>/lib/python3.10/subprocess.py)
96 100% sysconfig (<install-dir>/lib/python3.10/sysconfig.py)
67 100% tags (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/tags.py)
119 100% tempfile (<install-dir>/lib/python3.10/tempfile.py)
35 100% testing (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/testing.py)
7 100% text_styler (<src-dir>/text_styler.py)
51 100% textwrap (<install-dir>/lib/python3.10/textwrap.py)
2 100% threading (<install-dir>/lib/python3.10/threading.py)
32 100% tokenize (<install-dir>/lib/python3.10/tokenize.py)
43 100% traceback (<install-dir>/lib/python3.10/traceback.py)
703 100% typing (<install-dir>/lib/python3.10/typing.py)
238 100% unicode (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/unicode.py)
76 100% util (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/util.py)
20 100% utils (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/utils.py)
1 100% version (<venv-dir>/lib/python3.10/site-packages/pyfiglet/version.py)
16 100% warnings (<install-dir>/lib/python3.10/warnings.py)
127 100% weakref (<install-dir>/lib/python3.10/weakref.py)
432 100% zipfile (<install-dir>/lib/python3.10/zipfile.py)
Using PyCharm with GraalPy
You can use GraalPy in PyCharm to create a virtual environment, install packages, and develop and run your Python application.
-
Install
graalpy. (For more information, see Installing GraalPy.) -
Install PyCharm. (For more information, see Install PyCharm.)
-
Create, or open, a Python project. (For more information, see Create a Python project, or Open, reopen, and close projects, respectively.)
-
Create a new
venvvirtual environment for your Python project. (For more information, see Create a virtualenv environment.) -
Install packages by following the PyCharm instructions. (For more information, see Install, uninstall, and upgrade packages.)
-
Use the PyCharm menu items to run your Python application. Alternatively, use the terminal emulator to run the
graalpycommand.
Using Visual Studio Code with GraalPy
You can use GraalPy in Visual Studio (VS) Code to create a virtual environment, install packages, and develop and run your Python application.
-
Install
graalpy. (For more information, see Installing GraalPy.) -
Install VS Code and the Python Extension, following the instructions here: Install Visual Studio Code and the Python Extension.
-
Create, or open, a Python file.
-
Create a new venv virtual environment for your Python project. (For more information, see Creating environments.)
-
Install packages by following the VS Code instructions. (For more information, see Install and use packages.)
-
Use the VS Code menu items to run your Python application. (For more information, see Run Hello World.) Alternatively, use a VS Code terminal emulator to run the
graalpycommand. -
You cannot use VS Code to debug your Python application. Instead, open a VS Code terminal emulator and follow these instructions: Debugging a Python Application.
GraalPy Troubleshooting
GraalPy Embedding
VirtualFileSystem cannot load a file
There are situations where a file cannot be loaded even though it is part of the Virtual Filesystem resources. GraalPy tries to prevent such situations by automatically extracting some well known files to the real filesystem, but if you see an error like:
ImportError: cannot load /graalpy_vfs/venv/lib/python3.11/site-packages/_cffi_backend.graalpy250dev09433ef706-311-native-aarch64-darwin.so:
NFIUnsatisfiedLinkError: dlopen(/graalpy_vfs/venv/lib/python3.11/site-packages/_cffi_backend.graalpy250dev09433ef706-311-native-aarch64-darwin.so, 0x0002):
then the default behavior did not work as intended.
Depending on how you deploy Python resources in your application, you can try one of the following:
- if you need to package resources within your Jar or Native Image executable:
- if the problematic file is not one of the following types:
.so,.dylib,.pyd,.dll, or.ttf, which are extracted to the real filesystem by default, you can simply attempt to add it to the extraction filter using:
VirtualFileSystem.Builder.extractFilter(filter);- if the previous does not help, it is also possible to extract all Python resources into a user-defined directory before creating a GraalPy context, and then configure the context to use that directory:
// extract the Python resources from the jar or native image into a given directory GraalPyResources.extractVirtualFileSystemResources(VirtualFileSystem.create(), externalResourceDirectoryPath); // create a GraalPy context configured with an external Python resource directory Context context = GraalPyResources.contextBuilder(externalResourceDirectoryPath).build(); - if the problematic file is not one of the following types:
-
or if you’re able to ship resources in a separate directory, you have to set the
externalDirectorytag in Maven orexternalDirectoryfield in Gradle and also configure the GraalPy context to use that directory as well:// create a Context configured with an external Python resource directory Context context = GraalPyResources.contextBuilder(externalResourceDirectoryPath).build();Please note, that if switching from Virtual FileSystem to an external directory, also all user files from the previous Virtual FileSystem resource root have to be moved into that directory as well.
For more details about the Python resources in GraalPy Embedding please refer to the Embedding Build Tools documentation.
For more details about GraalPy context and Virtual FileSystem configuration please refer to GraalPyResources and VirtualFileSystem javadoc.
Issues with GraalPy ‘java’ posix backend
The Virtual FileSystem is built on the Truffle filesystem and relies on the GraalPy Java POSIX backend. Unfortunately, certain Python packages bypass Python’s I/O and directly access files through their native extensions. If you encounter an error like:
NotImplementedError: 'PyObject_AsFileDescriptor' not supported when using 'java' posix backend
then you have to override the default java GraalPy backend option byt setting the native POSIX backend
and completely omit the Virtual FileSystem at runtime.
Depending on how you deploy Python resources in your application, you can do one of the following:
- if you need to package Python resources within your Jar or Native Image executable, then:
// extract the Python resources from the jar or native image into a given directory GraalPyResources.extractVirtualFileSystemResources(VirtualFileSystem.create(), externalResourceDirectoryPath); // create a Context.Builder configured with an external python resource directory Builder builder = GraalPyResources.contextBuilder(externalResourceDirectoryPath); // override the python.PosixModuleBackend option with "native" builder.option("python.PosixModuleBackend", "native"); // create a context Context context = builder.build(); - or if you’re able to ship Python resources in a separate directory, you have to set the
externalDirectorytag in Maven orexternalDirectoryfield in Gradle and configure the GraalPy context accordingly:// create a Context.Builder configured with an external python resource directory Builder builder = GraalPyResources.contextBuilder(externalResourceDirectoryPath); // override the python.PosixModuleBackend option with "native" builder.option("python.PosixModuleBackend", "native"); // create a context Context context = builder.build();Please note, that if switching from Virtual FileSystem to an external directory, also all user files from the previous Virtual FileSystem resource root have to be moved into that directory as well.
For more details about the Python resources in GraalPy Embedding please refer to the Embedding Build Tools documentation.
For more details about GraalPy context and Virtual FileSystem configuration please refer to GraalPyResources and VirtualFileSystem javadoc.
ImportError reports “unknown location”
A possible cause of an ImportError ending with (unknown location) when running a GraalPy Java Embedding project might be
that an embedded Python package was built for a different operating system. If you see an error like the following:
ImportError: cannot import name 'exceptions' from 'cryptography.hazmat.bindings._rust' (unknown location)
You probably need to rebuild your project on the correct operating system before running it.
GraalVM JDK Compatibility
To enable runtime compilation when running GraalPy from a Maven or Gradle application, you must use versions of GraalPy and the Polyglot API dependencies that are compatible with the specified GraalVM JDK version. If you see errors like the following:
Your Java runtime '23.0.1+11-jvmci-b01' with compiler version '24.1.1' is incompatible with polyglot version '24.1.0'.
You need to keep the versions of your GraalPy and Polyglot dependencies according to the error message, so either upgrade the version of your JDK or your polyglot and GraalPy dependencies.
Note, this can also apply to cases when your dependencies are transitively pulled in by another artifact, e.g. micronaut-graalpy.
The following artifacts could not be resolved: org.graalvm.python:python-language-enterprise
python-language-enterprise was discontinued, use org.graalvm.polyglot:python instead.
Issues with Meson build system when installing Python packages on OSX with Maven or Gradle GraalPy plugin
Errors like the following:
../meson.build:1:0: ERROR: Failed running 'cython', binary or interpreter not executable.
could be caused by the GraalPy launcher used internally by the Maven or Gradle GraalPy plugin for installing Python packages. Currently, there is no straightforward solution. However, a workaround is to set the Java system property graalpy.vfs.venvLauncher to a launcher from a downloaded GraalPy distribution with a version matching the GraalPy Maven artifacts version.
e.g.
mvn package -Dgraalpy.vfs.venvLauncher={graalpy_directroy}/Contents/Home/bin/graalpy
No language and polyglot implementation was found on the module-path.
If you see an error like:
java.lang.IllegalStateException: No language and polyglot implementation was found on the module-path. Make sure at last one language is added to the module-path.
you are probably missing the python langauge dependency in your Maven of Gradle configuration file.
You need to add either org.graalvm.polyglot:python or org.graalvm.polyglot:python-community to your dependencies.
Detailed Test Tier Breakdown
GraalPy test tiers are similar to CPython Platform Support Tiers, but do not constitute or imply any commitment to support.
Generally, running pure Python code on GraalPy without JIT is compatible with any recent JDK. However, support for native extensions, platform-specific APIs, and just-in-time compilation is more limited.
Platform testing is organized into Tiers, each with specific goals.
Tiers are identified by a “target tuple”: [CPU architecture]-[Operating System]-[libc]-[JDK]-[Java version].
JDK names correspond to those used by SDKMAN!.
The term “graal” is used to refer to testing on both Oracle GraalVM and GraalVM Community Edition, including GraalVM Native Image.
The following tables list tested platforms by tier.
Platforms not listed are untested.
Tier 1
- CI failures block releases.
- Changes which would break the main or release branches are not allowed to be merged; any breakage should be fixed or reverted immediately.
- All core developers are responsible to keep main, and thus these platforms, working.
- Platform-specific Python APIs and Python C extensions are fully tested.
| Platform | Notes |
|---|---|
| amd64-linux-glibc-graal-latest | Oracle Linux 8 or similar. |
| aarch64-linux-glibc-graal-latest | Oracle Linux 8 or similar. |
Tier 2
- CI failures block releases.
- Changes which would break the main or release branches are not allowed to be merged; any breakage should be fixed or tests marked as skipped.
- Circa 10% of tests running on Tier 1 platforms may be skipped on Tier 2 platforms.
- Platform-specific Python APIs are fully tested; Python C extensions may have more issues than on Tier 1 platforms.
| Platform | Notes |
|---|---|
| aarch64-macos-darwin-graal-latest | macOS on M-series CPUs. |
Tier 3
- CI failures block releases.
- Changes which would break the main or release branches are not allowed to be merged; any breakage should be fixed or tests marked as skipped.
- Circa 25% of tests running on Tier 1 platforms may be skipped on Tier 3.
- Tests for platform-specific Python APIs and Python C extension are run, but not prioritized.
| Platform | Notes |
|---|---|
| amd64-macos-darwin-graal-latest | macOS on Intel CPUs running BigSur or newer. |
| amd64-windows-msvc-graal-latest | Windows 11, Windows Server 2025, or newer. |
| amd64-linux-glibc-oracle-21 | JDK 21 is tested without JIT compilation. |
| aarch64-linux-glibc-oracle-21 | JDK 21 is tested without JIT compilation. |
| aarch64-macos-darwin-oracle-21 | JDK 21 is tested without JIT compilation. |
| amd64-macos-darwin-oracle-21 | JDK 21 is tested without JIT compilation. |
| amd64-windows-msvc-oracle-21 | JDK 21 is tested without JIT compilation. |