Documentation for JVM Developers

For JVM developers who need to use Python libraries from their JVM applications or migrate from legacy Jython code.

You do not need to install GraalPy separately - you can use GraalPy directly in Java with Maven or Gradle. This lets you call Python libraries like NumPy, pandas, or any PyPI package from your Java application. GraalPy also provides a migration path from Jython 2.x to Python 3.x with better performance and maintained Java integration capabilities.

Version Compatibility

The following table shows which Python versions are supported by each GraalPy release:

GraalPy Version Python Version GraalVM Platform
25.x Python 3.12.8 Oracle GraalVM 25.x, GraalVM Community Edition 25.x
23.x Python 3.10.8 Oracle GraalVM for JDK 21.x, Oracle GraalVM for JDK 17.x
22.x Python 3.8.5 GraalVM Enterprise Edition 21.3.x

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. The main operating system is Oracle Linux, the CPU architectures are AMD64 and ARM, and the primary JDK is Oracle GraalVM. Linux is recommended for getting started with GraalPy. Windows and macOS with GraalVM JDK are less well tested, and outside of those combinations only basic test coverage is provided. As macOS and other platforms are not prioritized, some GraalPy features may not work on these platforms. See Test Tiers for a detailed breakdown.

These guides cover everything you need to know:

Embedding Python in Java

This guide shows you how to embed Python code directly in Java applications using GraalPy. You can use GraalPy with any JDK (GraalVM JDK, Oracle JDK, or OpenJDK).

GraalPy provides dedicated Maven and Gradle plugins that handle all the complexity for you. If you are using other build systems (Ant, Make, CMake), manual configuration is required.

Maven Quick Start

The fastest way to get started is with GraalPy’s Maven archetype, which generates a complete starter project for you.

  1. Generate a new project using the GraalPy Maven archetype:

    mvn archetype:generate \
      -DarchetypeGroupId=org.graalvm.python \
      -DarchetypeArtifactId=graalpy-archetype-polyglot-app \
      -DarchetypeVersion=25.0.2
    

    This generates the following project structure:

    └── polyglot-app
        ├── pom.xml
        ├── src
        │   └── main
        │       ├── java
        │       │   └── com
        │       │       └── example
        │       │           └── Main.java
        │       └── resources
        └── target/
    
  2. Build a native executable using the GraalVM native-image tool that was added for you automatically:

     mvn -Pnative package
    
  3. Once completed, run the executable:

     ./target/polyglot_app
    

    You should see “hello java” printed to the console.

The generated project includes everything you need: the GraalVM Polyglot API for Python execution, Python virtual environment management, and examples showing how to integrate Python packages. The generated pom.xml and Java code are well-documented with explanations of all features.

For advanced plugin configuration, deployment options, and dependency management, see the Embedding Build Tools guide.

Cross-Platform Distribution

For creating cross-platform JARs with native Python packages, see the Virtual Filesystem deployment section in the Build Tools guide.

Gradle Quick Start

If you prefer Gradle, here is how to set up a new project with GraalPy embedding:

  1. Create a new Java application with Gradle:

     gradle init --type java-application \
                 --project-name interop  \
                 --package interop \
                 --no-split-project
    

    This generates the following project structure:

     └── app
         ├── build.gradle
         └── src
             └── main
                 ├── java
                 │   └── interop
                 │       └── App.java
                 └── resources
    
  2. Add GraalPy dependencies to your app/build.gradle file.
    • Include the GraalPy support and the GraalVM Polyglot API in the dependencies section:

        implementation("org.graalvm.polyglot:polyglot:25.0.2")
        implementation("org.graalvm.python:python-embedding:25.0.2")
      
  3. Replace the App.java content with this simple Python embedding example:

     package interop;
    
     import org.graalvm.polyglot.*;
     import org.graalvm.python.embedding.GraalPyResources;
    
     class App {
         public static void main(String[] args) {
             try (var context = GraalPyResources.createContext()) {
                 System.out.println(context.eval("python", "'Hello Python!'").asString());
             }
         }
     }
    
  4. Run the application with Gradle:

     ./gradlew run
    

    The application prints “Hello Python!” to the console.

    Note: GraalPy’s performance depends on the JDK you are using. For optimal performance, see the Runtime Optimization Support guide.

Adding Python Dependencies

To use third-party Python packages like NumPy or Requests in your embedded application:

  1. Add the GraalPy Gradle plugin and configure dependencies in app/build.gradle:

    plugins {
        id "java"
        id "application"
        id "org.graalvm.python" version "25.0.2"
    }
    
    graalPy {
       packages = ["termcolor==2.2"]
    }
    
  2. Update your Java code to use the Python package:

    package interop;
    
    import org.graalvm.polyglot.*;
    import org.graalvm.python.embedding.GraalPyResources;
    
    class App {
        public static void main(String[] args) {
            try (Context context = GraalPyResources.contextBuilder().build()) {
                String src = """
                from termcolor import colored
                colored_text = colored("hello java", "red", attrs=["reverse", "blink"])
                print(colored_text)
                """;
                context.eval("python", src);
            }
        }
    }
    

For complete plugin configuration options, deployment strategies, and dependency management, see Embedding Build Tools.

Other Build Systems (Ant, CMake, Makefile)

If you are using build systems like Ant, Makefiles, or CMake that do not directly support Maven dependencies, you can still use GraalPy. Projects like Apache Ivy™ can resolve Maven dependencies for these systems, you might prefer a simpler approach. GraalPy provides a tool to download the required JAR files directly.

Manual Setup

  1. Set up your project structure with a directory for dependencies:

     ├── lib/           # JAR dependencies 
     │   └── *.jar
     └── src/           # Your Java source files
         └── *.java
    
  2. Download GraalPy dependencies using the bundled tool:

    First, install GraalPy and ensure graalpy is in your PATH.

    Then run the appropriate command for your system:

    On Linux/macOS:

     export GRAALPY_HOME=$(graalpy -c 'print(__graalpython__.home)')
     export GRAALPY_VERSION=$(graalpy -c 'import __graalpython__; print(__graalpython__.version)')
     "${GRAALPY_HOME}/libexec/graalpy-polyglot-get" -a python -o lib -v "${GRAALPY_VERSION}"
    

    On Windows (PowerShell):

     $GRAALPY_HOME = graalpy -c "print(__graalpython__.home)"
     $GRAALPY_VERSION = graalpy -c "import __graalpython__; print(__graalpython__.version)"
     & "$GRAALPY_HOME/libexec/graalpy-polyglot-get" -a python -o lib -v "$GRAALPY_VERSION"
    

    This downloads all required GraalPy JARs into your lib directory.

  3. Write your embedding code:

     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());
             }
         }
     }
    

    Make sure your build system includes all JARs from the lib directory in the classpath.

Embedding Build Tools

Note: The GraalPy build tools are being developed in the GraalPy Extensions repository on GitHub.

This guide covers advanced configuration and deployment options for the GraalPy Maven and Gradle plugins. For quick start tutorials and basic setup, see Embedding Python in Java.

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 configuration
  • GraalPyResources.contextBuilder() - Returns a context builder for additional customization before creating the context
  • GraalPyResources.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 directory
  • GraalPyResources.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.

Dependency Management

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 packages or requirementsFile, but not both.

When requirementsFile is used:

  • the GraalPy lock file is not created and not used
  • the lock-packages goal is disabled
  • dependency locking must be handled externally by pip (for example using pip freeze)

Mixing packages and requirementsFile in 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-packages goal.

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.2'
}

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:python
  • org.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 graalPyLockPackages task.

For more information of this feature, please see the Python Dependency Management for Reproducible Builds section.

Permissions for Python Embeddings

When embedding GraalPy in Java applications, you need to understand how Python scripts access system resources and what security limitations apply.

GraalPy integrates with the GraalVM Polyglot Sandbox to provide configurable access control for embedded Python code.

Python’s POSIX Interface

GraalPy exposes the operating system interface to Python scripts in a GraalPy-specific way. By default, all access is routed through Java interfaces, but some packages rely on POSIX API details and require direct native access.

Graal languages (implemented on the Truffle framework) typically implement system-related functions using the Truffle abstraction layer. This layer is OS-independent and provides extension points when embedding GraalPy or other Graal languages into Java applications. For example, see 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 (“backends”) of system-related functionality for built-in Python modules such as os. The PosixModuleBackend option determines which backend is used: native or java.

Native Backend

The native 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.

However, this implementation bypasses the Truffle abstraction layer by default. This means:

The native backend is chosen by default when GraalPy is started via the graalpy launcher or any other Python-related launcher.

Limitations of the Native Backend

Known limitations:

  • os.fork is not supported
  • _posixsubprocess.fork_exec does not support the preexec_fn parameter

Java Backend

The Java backend uses the Truffle abstraction layer, which provides:

  • Sandboxing support for security
  • Custom Polyglot API providers for system interfaces
  • Cross-platform compatibility

Because this abstraction is POSIX-agnostic, it cannot expose all necessary functionality. Some functionality is emulated, and some is unsupported.

The Java backend is the default when embedding GraalPy in Java applications using the Context API.

Limitations of the Java Backend

To help identify compatibility issues, GraalPy can log information about known incompatibilities of functions executed at runtime. To enable this logging, use: --log.python.compatibility.level=FINE

Known limitations of the Java backend are:

State Isolation:

The Java backend’s state is disconnected from the actual OS state:

  • File descriptors: Python-level file descriptors are not usable in native code
  • Current working directory: Initialized to the startup directory but maintained separately. For example, os.chdir() in Python does not change the actual process working directory
  • umask: Same limitation as working directory, but always initialized to 0022 regardless of the actual system value

Other Limitations:

  • File timestamps: Resolution depends on the JDK. The Java backend can only guarantee seconds resolution
  • File access checks: os.access() and functionality based on the faccessat POSIX function do not support:
    • Effective IDs
    • follow_symlinks=False unless the mode is only F_OK

Context Security Configuration

When embedding GraalPy using the Polyglot API, you can configure security and access permissions through the Context.Builder class. These settings control what system resources and host functionality your embedded Python code can access.

This table shows the common security methods:

Method Description Notes
allowAllAccess(boolean) Grants unrestricted access to all host functionality and system resources. Security Risk: Should only be used in trusted environments or for testing. When true, bypasses most security restrictions. When false (default), access is controlled by other specific permission methods.
allowHostAccess(HostAccess) Controls access to Java host objects and classes. Use HostAccess.ALL for unrestricted access or create custom policies. Default is HostAccess.NONE which blocks host access.
allowHostClassLookup(Predicate<String>) Controls which Java classes can be looked up by name from Python. Accepts a predicate function to filter allowed class names. More granular than allowHostAccess for class loading.
allowPolyglotAccess(boolean) Controls access to other polyglot languages and their objects. Required for interop with other languages like JavaScript. Default is false.
allowIO(IOAccess) Controls file system and network access. Use IOAccess.ALL for full access, IOAccess.NONE to block all I/O. Can create custom policies for specific paths or protocols.
allowEnvironmentAccess(boolean) Controls access to environment variables. Required for accessing os.environ["PATH"] and other environment variables. Default is false.
allowCreateProcess(boolean) Controls whether Python code can create new processes. Required for the subprocess module to work. Default is false.
allowCreateThread(boolean) Controls whether Python code can create new threads. Default is false for security.
allowNativeAccess(boolean) Controls access to native libraries and system calls. Required for Python packages with native extensions. Default is false.

Secure Configuration Example

This example shows how to configure a production-ready context with restricted permissions:

Context context = Context.newBuilder("python")
    .allowHostAccess(HostAccess.EXPLICIT)  // Only explicitly exported objects
    .allowIO(IOAccess.newBuilder()         // Restrict to specific directory
        .fileSystem(FileSystem.newDefaultFileSystem(Path.of("/safe/directory")))
        .build())
    .allowCreateThread(false)              // No thread creation
    .allowNativeAccess(false)              // No native code
    .build();

Development Configuration Example

For development and testing environments, you might use a more permissive configuration:

Context context = Context.newBuilder("python")
    .allowAllAccess(true)                         // Full access for development
    .option("python.PosixModuleBackend", "java")  // Use sandboxed backend
    .build();

Warning: Using allowAllAccess(true) disables most security protections. Only use this in trusted environments or during development. For production deployments, configure specific permissions using the individual allow methods.

Python Native Extensions

Python native extensions run by default as native binaries with full access to the underlying system. This means they bypass the security controls described above.

For more information about limitations when embedding native extensions, see Embedding limitations.

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.

Call Java from Python

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.

Call Foreign Objects from Python

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.

Call Other Languages from Python

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.

Multithreading

GraalPy implements the Python global interpreter lock (GIL), which prevents any two threads from executing Python code at the same instant. When methods in other languages are called from Python, no Python code is running while the other language executes. To give other Python threads a chance to run at this point in time, GraalPy releases the GIL around such foreign method calls by default. This (un)locking of the GIL can impact performance negatively if the foreign code runs only for a very short while, however, so this behavior can be controlled per dynamic scope using Python context managers.

class JavaFile(io.FileIO):
    def write(self, obj):
        # Unlock the GIL when doing IO in Java
        with polyglot.gil_locked_during_interop(False):
            self.java_file.write(obj)

with polyglot.gil_locked_during_interop(True):
    # Keep the GIL locked when accessing Java maps, because those method calls will return very quickly
    some_file.write(java_map.get(key1) + java_map.get(key2))

Beware of always keeping the GIL locked. That may lead to deadlocks if the foreign language attempts to wait on another thread of execution, and that thread tries to call back into Python.

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.2</version>
</dependency>

Examples

Here are practical examples of using the polyglot API to work with JavaScript from Python:

  1. Import the polyglot module to interact with other languages:
    import polyglot
    
  2. Evaluate inlined code in another language:
    assert polyglot.eval(string="1 + 1", language="js") == 2
    
  3. 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")
    
  4. 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 invoke and 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
      
  5. 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.

Export Python Objects

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.

  1. 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])
    
  2. 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.*;
    import org.graalvm.python.embedding.GraalPyResources;
    
    class Main {
        public static void main(String[] args) {
            try (var context = GraalPyResources.createContext()) {
                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!");
            }
        }
     }
    

Types Mapping

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 Python int objects.
  • When foreign numbers are represented as Java primitives float, double, they are considered Python float objects.
  • When foreign booleans are represented as Java primitives boolean, they are considered Python bool objects.

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.

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;
import org.graalvm.python.embedding.GraalPyResources;

class Main {
    public static void main(String[] args) {
        try (var context = GraalPyResources.createContext()) {
            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;
import org.graalvm.python.embedding.GraalPyResources;

class Main {

   public static void main(String[] args) {
      MyJavaClass myJavaObject = new MyJavaClass(42, 17);
      try (var context = GraalPyResources.createContext()) {
         // 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

Native Executables with Python

GraalPy supports GraalVM Native Image to generate native binaries of Java applications that embed Python code.

Building Executables with Python

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

GraalPy Maven archetype by default is set up to include all needed Python files in the native binary itself, so the image is self-contained.

Migrate from Jython to GraalPy

Jython has been the standard way to run Python code on the JVM and integrate with Java libraries. However, Jython’s latest stable releases only support Python 2.x, which reached end-of-life (EOL) in 2020.

GraalPy provides a modern alternative that supports Python 3.x on the JVM with excellent Java interoperability. While GraalPy offers similar capabilities to Jython, there are important differences in how Java integration works.

This guide shows you how to migrate from Jython to GraalPy.

Prerequisites

GraalPy Java Interoperability Overview

GraalPy provides excellent Java interoperability, allowing you to use Java libraries from Python with minimal friction. It offers specialized features for Java integration that go beyond what’s available for other languages on the GraalVM platform.

Important: GraalPy doesn’t support all Jython features out of the box. Some features are available but disabled by default for performance reasons. You can enable them during migration with --python.EmulateJython, but updating your code to use GraalPy’s native approach is recommended for better performance.

Java Package Imports

Packages That Work by Default

Certain features of Jython’s Java integration are enabled by default on GraalPy. Here’s what works the same:

>>> 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.

Packages Outside the Java Namespace

To import classes from packages outside the java namespace, use the --python.EmulateJython option during migration.

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 classes directly:

import javax.swing.Window as Window

Java Object Usage

Working with Java Objects

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:

>>> from java.util import Random
>>> rg = Random(99)
>>> rg.nextInt()
1491444859
>>> boundNextInt = rg.nextInt
>>> boundNextInt()
1672896916

Type Conversion

GraalPy automatically converts between Python and Java types to make method calls and data passing seamless. When you call Java methods, GraalPy matches your Python arguments to the available Java parameter types using a best-effort approach.

GraalPy’s type matching is more flexible than Jython’s as it can convert any Python object with __int__ or __float__ methods to the corresponding Java types. This means you can use NumPy arrays as Java int[] or Pandas DataFrames as Java double[][] when the data types are compatible.

Here are the supported type conversions:

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

Java Arrays

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:

>>> 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:

>>> 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]

Java Exceptions

You can catch Java exceptions as you would expect:

>>> import java
>>> v = java.util.Vector()
>>> try:
...    x = v.elementAt(7)
... except java.lang.ArrayIndexOutOfBoundsException as e:
...    print(e.getMessage())
...
7 >= 0

Java Collections

Collection Interface Features

Java arrays and collections that implement the java.util.Collection interface can be accessed using the [] syntax. An empty collection is considered false in boolean conversions. The length of a collection is exposed by the len built-in function:

>>> 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

Iterating Over Collections

Java iterables that implement the java.lang.Iterable interface can be iterated over using a for loop or the iter built-in function and are accepted by all built-ins that expect an iterable:

>>> [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'}

You can also iterate over Java iterators directly:

>>> from java.util import ArrayList
>>> l = ArrayList()
>>> l.add("foo")
True
>>> i = l.iterator()  # Calls the Java iterator methods
>>> next(i)
'foo'

Working with Maps

Mapped collections that implement the java.util.Map interface can be accessed using the [] notation. An empty map is considered false in boolean conversions. Iteration of a map yields its keys, consistent with dict:

>>> 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 Classes

Understanding Java Class Inheritance

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.

Important: 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.

Inheritance behavior up to GraalPy version 25.1

When inheriting from a Java class, you can pass the keyword new_style=False. This is the default up to and including GraalPy version 25.1.

Key points for legacy inheritance:

  • The generated class is a Java object and no forwarding to the Python-side happens by default
  • 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__
  • 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
  • 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

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.

Inheritance behavior from GraalPy 25.1

When inheriting from a Java class, you can pass the keyword new_style=True. This is the default after GraalPy version 25.1. The generated class is a Java object, but attribute lookup is dispatched to the Python object as needed.

Features of modern inheritance:

  • Multiple levels of inheritance are supported
  • super() calls work both in the constructor override via __new__ as well as in Java method overrides
  • The self in a method refers to the Java object, but any access that does not refer to a field or method on the Java class is transparently dispatched to the Python side
  • Static methods can be called both from the instance as well as the class

For example:

from java.util.logging import Level

class PythonLevel(Level, new_style=True):
    def __new__(cls, name="default name", level=2):
        """Provide a default constructor that modifies
        the construction of the Java instance"""
        return super().__new__(cls, name, level)

    def __init__(self, *args, **kwarg):
        """After the instance is created, initialize the
        misc_value field"""
        self.misc_value = 42

    def getName(self):
        """This overrides the Java method on
        java.util.logging.Level"""
        return super().getName() + " from Python with super()"

    def pythonName(self):
        """This adds a method that is only visible from Python,
        but self and super calls work as expected"""
        return f"PythonName for Level {self.intValue()} named {super().getName()}"

    def callStaticFromPython(self, name):
        """Java static methods can be called from the
        instance as well as the class"""
        return self.parse(name)

pl = PythonLevel()
assert issubclass(PythonLevel, Level)
assert PythonLevel.parse("INFO").getName() == "INFO"

Embedding Python into Java

If you were embedding Jython in Java applications, there were two main approaches that need different migration paths:

  • PythonInterpreter approach: Use the PythonInterpreter object 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.

    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 Embedding Getting Started documentation for preparing a setup.

  • JSR 223 ScriptEngine approach: Embed Jython in Java via JSR 223 by using the classes of the javax.script package, and, in particular, via the ScriptEngine class.

    This approach is not recommended, because the ScriptEngine APIs are not a clean fit for the options and capabilities of GraalPy.

    However, to migrate existing code, an example ScriptEngine implementation is provided that you can inline into your project. Refer to the Embedding Languages reference manual 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>

PyExpatModuleBackend (Has to be the same for all Contexts in an Engine)

      Choose the backend for the pyexpat module. Accepts: java|native

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

UnicodeCharacterDatabaseNativeFallback (Has to be the same for all Contexts in an Engine)

      Allow the unicodedata module to fall back from the ICU database to CPython's native UCD for unsupported features. 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

Detailed Test Tier Breakdown

GraalPy organizes platform testing into tiers that indicate the level of testing rigor and support you can expect for different platform configurations. This tiering system helps you understand:

  • How thoroughly your platform is tested
  • What level of stability to expect
  • Which features are fully supported vs. experimental

Platforms are identified using the target tuple format: [CPU architecture]-[Operating System]-[libc]-[JDK]-[Java version]. JDK names follow SDKMAN! conventions, and “graal” refers to both Oracle GraalVM and GraalVM Community Edition (including Native Image).

Important: GraalPy test tiers are similar to CPython Platform Support Tiers, but do not constitute or imply any commitment to support.

Pure Python code runs reliably on GraalPy with recent JDKs when JIT compilation is disabled. However, advanced features like native extensions, platform-specific APIs, and JIT compilation have varying support depending on your platform tier.

Tier 1

  • Stability: 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.
  • Responsibility: All core developers are responsible to keep main, and thus these platforms, working.
  • Coverage: 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

  • Stability: 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.
  • Test Coverage: Circa 10% of tests running on Tier 1 platforms may be skipped on Tier 2 platforms.
  • Feature Support: 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

  • Stability: 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.
  • Test Coverage: Circa 25% of tests running on Tier 1 platforms may be skipped on Tier 3.
  • Feature Support: Tests for platform-specific Python APIs and Python C extension are run, but not prioritized.
Platform Notes
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.

Tier 4

  • Stability: CI failures do not block releases. Tests may be broken on the main and release branches.
  • Test Coverage: Smoke tests with platform-agnostic pure Python workloads are run on a regular schedule.
  • Feature Support: Only basic pure Python functionality is tested; platform-specific features and extensions are not prioritized.
Platform Notes
amd64-linux-musl-graal-latest Ensures GraalPy can be built for and used on musl libc platforms such as Alpine Linux.
amd64-linux-glibc-j9-17 Ensures that non-Oracle JVMs work for pure Python code without JIT.
ppc64le-linux-glibc-oracle-17 Ensures that other architectures (ppc64le, s390x, risc-v) work for pure Python code without JIT.

GraalPy Troubleshooting

This guide helps you resolve common issues when using GraalPy, whether running it standalone or embedded in Java applications.

Virtual FileSystem Issues

VirtualFileSystem Cannot Load Files

Some files may fail to load from the Virtual Filesystem even though they’re included as resources. GraalPy automatically extracts certain file types to the real filesystem, but you may still encounter errors.

Example error:

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):

This indicates that the native extension requires access to the real filesystem.

Solution: Depending on how you deploy Python resources, choose one of these approaches:

Option 1: Package Resources in JAR/Executable

If the problematic file is not automatically extracted (files other than .so, .dylib, .pyd, .dll, or .ttf), add it to the extraction filter:

VirtualFileSystem vfs = VirtualFileSystem.newBuilder()
    .extractFilter(filename -> filename.endsWith(".your_extension"))
    .build();

If that doesn’t resolve the issue, extract all resources to an external directory:

// Extract the Python resources from the jar or native image into a directory
GraalPyResources.extractVirtualFileSystemResources(VirtualFileSystem.create(), externalResourceDirectoryPath);
// Create a GraalPy context configured with the external directory
Context context = GraalPyResources.contextBuilder(externalResourceDirectoryPath).build();
Option 2: Use External Directory

Configure your build tool to use an external directory by setting:

Then configure your context:

// Create a context configured with an external Python resource directory
Context context = GraalPyResources.contextBuilder(externalResourceDirectoryPath).build();

Important: When switching from Virtual FileSystem to external directory, move all user files from the previous Virtual FileSystem resource root to the new directory.

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.

POSIX Backend Issues

Issues with Java POSIX Backend

The Virtual FileSystem relies on GraalPy’s Java POSIX backend. Some Python packages bypass Python’s I/O and directly access files through native extensions.

Example error:

NotImplementedError: 'PyObject_AsFileDescriptor' not supported when using 'java' posix backend

This indicates that a Python package requires direct file descriptor access which is not supported by the Java POSIX backend.

Solution: Override the default Java backend by setting the native POSIX backend and extract resources from the Virtual FileSystem.

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 resources and configure native backend
    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 externalDirectory tag in Maven or externalDirectory field 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();
    

Important: When switching from Virtual FileSystem to external directory, move all user files from the previous Virtual FileSystem resource root to the new directory.

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.

Import and Compatibility Issues

ImportError Reports “Unknown Location”

An ImportError ending with (unknown location) typically occurs when an embedded Python package was built for a different operating system.

Example error:

ImportError: cannot import name 'exceptions' from 'cryptography.hazmat.bindings._rust' (unknown location)

This indicates that the Python package contains platform-specific native extensions that are incompatible with your current operating system.

Solution: Rebuild your project on the target operating system before running it.

Dependency and Build Issues

GraalVM JDK Compatibility

To enable runtime compilation when running GraalPy from a Maven or Gradle application, you must use compatible versions of GraalPy and Polyglot API dependencies with your GraalVM JDK version.

Example error:

Your Java runtime '23.0.1+11-jvmci-b01' with compiler version '24.1.1' is incompatible with polyglot version '24.1.0'.

This indicates version misalignment between your GraalVM JDK, compiler, and Polyglot dependencies.

Solution: Align the versions of your GraalPy and Polyglot dependencies according to the error message:

  • Upgrade your JDK version, or
  • Update your Polyglot and GraalPy dependencies

Note: This can also apply when dependencies are transitively pulled in by other artifacts, for example, micronaut-graalpy.

Deprecated Dependencies

You may encounter issues when using outdated or discontinued dependency artifacts in your project configuration.

Example error:

Could not find artifact org.graalvm.python:python-language-enterprise

This indicates you’re trying to use a deprecated or discontinued dependency.

Solution: Replace python-language-enterprise with org.graalvm.polyglot:python.

macOS Build System Issues (Meson/Cython)

On macOS, you may encounter build system errors when installing Python packages that use Meson or Cython.

Example error:

../meson.build:1:0: ERROR: Failed running 'cython', binary or interpreter not executable.

This is caused by the GraalPy launcher used internally by the Maven or Gradle GraalPy plugin for installing Python packages.

Solution: Set the Java system property graalpy.vfs.venvLauncher to a launcher from a downloaded GraalPy distribution with a version matching your GraalPy Maven artifacts version.

Example command:

mvn package -Dgraalpy.vfs.venvLauncher={graalpy_directory}/Contents/Home/bin/graalpy

Missing Language Dependencies

When running GraalPy applications, you may encounter runtime errors indicating missing language implementations.

Example error:

java.lang.IllegalStateException: No language and polyglot implementation was found on the module-path. Make sure at least one language is added to the module-path.

This indicates that the Python language dependency is missing from your Maven or Gradle configuration.

Solution: Add one of these dependencies to your project:

  • org.graalvm.polyglot:python (Oracle GraalVM)
  • org.graalvm.polyglot:python-community (Community Edition)