CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-graalvm-polyglot--python

GraalVM Polyglot API for embedding and executing Python code within Java applications.

Pending
Overview
Eval results
Files

java-python-interop.mddocs/

Java-Python Interoperability

Java-Python interoperability provides seamless bidirectional communication between Java and Python through the proxy system. This enables Java objects to be used naturally within Python code and Python objects to be manipulated from Java with full type safety and comprehensive data type support.

Capabilities

Object Proxies

Create Java objects that behave like Python objects with member access capabilities.

/**
 * Base interface for all proxy implementations
 */
public interface Proxy {
}

/**
 * Proxy interface for objects with named members (like Python objects/dicts)
 */
public interface ProxyObject extends Proxy {
    /**
     * Gets the value of a member by key
     * @param key the member name
     * @return the member value
     */
    Object getMember(String key);
    
    /**
     * Gets all member keys as an array or iterable
     * @return object representing the available keys
     */
    Object getMemberKeys();
    
    /**
     * Checks if a member with the given key exists
     * @param key the member name to check
     * @return true if the member exists
     */
    boolean hasMember(String key);
    
    /**
     * Sets the value of a member
     * @param key the member name
     * @param value the value to set
     */
    void putMember(String key, Value value);
    
    /**
     * Removes a member
     * @param key the member name to remove
     * @return true if the member was removed
     */
    boolean removeMember(String key);
    
    /**
     * Creates a ProxyObject from a Java Map
     * @param values the map to wrap
     * @return ProxyObject representing the map
     */
    static ProxyObject fromMap(Map<String, Object> values);
}

Usage Examples:

// Create a Java object that acts like a Python dict
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("name", "Alice");
dataMap.put("age", 30);
dataMap.put("city", "New York");

ProxyObject dictProxy = ProxyObject.fromMap(dataMap);
context.getPolyglotBindings().putMember("person", dictProxy);

// Access from Python like a normal dict
Value result = context.eval("python", """
    print(person['name'])      # Alice
    print(person['age'])       # 30
    person['email'] = 'alice@example.com'
    print(len(person))         # 4
    list(person.keys())        # ['name', 'age', 'city', 'email']
    """);

// Custom ProxyObject implementation
public class ConfigObject implements ProxyObject {
    private final Map<String, Object> config = new HashMap<>();
    
    @Override
    public Object getMember(String key) {
        return config.get(key);
    }
    
    @Override
    public Object getMemberKeys() {
        return config.keySet().toArray(new String[0]);
    }
    
    @Override
    public boolean hasMember(String key) {
        return config.containsKey(key);
    }
    
    @Override
    public void putMember(String key, Value value) {
        config.put(key, value.isString() ? value.asString() : value.as(Object.class));
    }
    
    @Override
    public boolean removeMember(String key) {
        return config.remove(key) != null;
    }
    
    // Custom method for validation
    public void validate() {
        System.out.println("Config validated with " + config.size() + " entries");
    }
}

ConfigObject config = new ConfigObject();
context.getPolyglotBindings().putMember("config", config);

context.eval("python", """
    config['database_url'] = 'postgresql://localhost:5432/db'
    config['max_connections'] = 100
    config['debug'] = True
    
    # Access like Python dict
    for key in config:
        print(f"{key}: {config[key]}")
    """);

config.validate(); // Call Java method

Array Proxies

Enable Java objects to behave like Python lists and sequences.

/**
 * Proxy interface for array-like objects (like Python lists/tuples)
 */
public interface ProxyArray extends Proxy {
    /**
     * Gets an element at the specified index
     * @param index the element index
     * @return the element at the index
     */
    Object get(long index);
    
    /**
     * Sets an element at the specified index
     * @param index the element index
     * @param value the value to set
     */
    void set(long index, Value value);
    
    /**
     * Removes an element at the specified index
     * @param index the element index to remove
     * @return true if the element was removed
     */
    boolean remove(long index);
    
    /**
     * Gets the size of the array
     * @return number of elements
     */
    long getSize();
    
    /**
     * Creates a ProxyArray from a Java array
     * @param values the array to wrap
     * @return ProxyArray representing the array
     */
    static ProxyArray fromArray(Object... values);
    
    /**
     * Creates a ProxyArray from a Java List
     * @param values the list to wrap
     * @return ProxyArray representing the list
     */
    static ProxyArray fromList(List<Object> values);
}

Usage Examples:

// Create array proxy from Java array
ProxyArray arrayProxy = ProxyArray.fromArray("apple", "banana", "cherry", "date");
context.getPolyglotBindings().putMember("fruits", arrayProxy);

// Access from Python like a normal list
context.eval("python", """
    print(fruits[0])           # apple
    print(len(fruits))         # 4
    fruits[1] = 'blueberry'    # Modify element
    
    # Iterate like Python list
    for i, fruit in enumerate(fruits):
        print(f"{i}: {fruit}")
    
    # List comprehension
    upper_fruits = [fruit.upper() for fruit in fruits]
    print(upper_fruits)
    """);

// Dynamic list proxy
public class DynamicList implements ProxyArray {
    private final List<Object> items = new ArrayList<>();
    
    @Override
    public Object get(long index) {
        if (index < 0 || index >= items.size()) {
            return null; // Python None
        }
        return items.get((int) index);
    }
    
    @Override
    public void set(long index, Value value) {
        while (items.size() <= index) {
            items.add(null); // Extend list if needed
        }
        items.set((int) index, value.as(Object.class));
    }
    
    @Override
    public boolean remove(long index) {
        if (index >= 0 && index < items.size()) {
            items.remove((int) index);
            return true;
        }
        return false;
    }
    
    @Override
    public long getSize() {
        return items.size();
    }
    
    // Java-specific methods
    public void clear() {
        items.clear();
    }
    
    public List<Object> getJavaList() {
        return new ArrayList<>(items);
    }
}

DynamicList dynamicList = new DynamicList();
context.getPolyglotBindings().putMember("dynamic_list", dynamicList);

context.eval("python", """
    # Use like Python list
    dynamic_list[0] = "first"
    dynamic_list[1] = "second" 
    dynamic_list[5] = "sixth"   # Auto-extends with None values
    
    print(f"Length: {len(dynamic_list)}")  # 6
    print(f"Item 2: {dynamic_list[2]}")    # None
    print(f"Item 5: {dynamic_list[5]}")    # sixth
    """);

// Get Java list back
List<Object> javaList = dynamicList.getJavaList();
System.out.println("Java list: " + javaList);

Executable Proxies

Make Java objects callable from Python like functions.

/**
 * Proxy interface for executable/callable objects (like Python functions)
 */
public interface ProxyExecutable extends Proxy {
    /**
     * Executes this object with the given arguments
     * @param arguments the arguments to pass to execution
     * @return the execution result
     */
    Object execute(Value... arguments);
}

/**
 * Proxy interface for instantiable objects (like Python classes/constructors)
 */
public interface ProxyInstantiable extends Proxy {
    /**
     * Creates a new instance using this object as constructor
     * @param arguments the constructor arguments
     * @return the new instance
     */
    Object newInstance(Value... arguments);
}

Usage Examples:

// Simple function proxy
public class MathFunctions implements ProxyExecutable {
    @Override
    public Object execute(Value... arguments) {
        if (arguments.length != 2) {
            throw new IllegalArgumentException("Expected 2 arguments");
        }
        
        double a = arguments[0].asDouble();
        double b = arguments[1].asDouble();
        return a + b; // Simple addition
    }
}

context.getPolyglotBindings().putMember("add", new MathFunctions());

Value result = context.eval("python", """
    result = add(5.5, 3.2)  # Call like Python function
    print(f"Result: {result}")  # 8.7
    result
    """);

// Multi-function proxy with operation selection
public class Calculator implements ProxyExecutable {
    @Override
    public Object execute(Value... arguments) {
        if (arguments.length < 3) {
            throw new IllegalArgumentException("Expected: operation, a, b");
        }
        
        String operation = arguments[0].asString();
        double a = arguments[1].asDouble();
        double b = arguments[2].asDouble();
        
        return switch (operation) {
            case "add" -> a + b;
            case "subtract" -> a - b;
            case "multiply" -> a * b;
            case "divide" -> b != 0 ? a / b : Double.NaN;
            default -> throw new IllegalArgumentException("Unknown operation: " + operation);
        };
    }
}

context.getPolyglotBindings().putMember("calc", new Calculator());

context.eval("python", """
    print(calc("add", 10, 5))       # 15.0
    print(calc("multiply", 3, 4))   # 12.0
    print(calc("divide", 15, 3))    # 5.0
    """);

// Class-like instantiable proxy
public class PersonFactory implements ProxyInstantiable {
    @Override
    public Object newInstance(Value... arguments) {
        String name = arguments.length > 0 ? arguments[0].asString() : "Unknown";
        int age = arguments.length > 1 ? arguments[1].asInt() : 0;
        
        Map<String, Object> person = new HashMap<>();
        person.put("name", name);
        person.put("age", age);
        person.put("greet", (ProxyExecutable) args -> "Hello, I'm " + name);
        
        return ProxyObject.fromMap(person);
    }
}

context.getPolyglotBindings().putMember("Person", new PersonFactory());

context.eval("python", """
    # Create instances like Python classes
    alice = Person("Alice", 30)
    bob = Person("Bob", 25)
    
    print(alice['name'])     # Alice
    print(alice['age'])      # 30
    greeting = alice['greet']()  # Call method
    print(greeting)          # Hello, I'm Alice
    """);

Iterator Proxies

Support Python iteration protocols with Java objects.

/**
 * Proxy interface for iterable objects (like Python iterables)
 */
public interface ProxyIterable extends Proxy {
    /**
     * Gets an iterator for this iterable
     * @return the iterator object
     */
    Object getIterator();
}

/**
 * Proxy interface for iterator objects (like Python iterators)
 */
public interface ProxyIterator extends Proxy {
    /**
     * Checks if there are more elements
     * @return true if more elements are available
     */
    boolean hasNext();
    
    /**
     * Gets the next element from the iterator
     * @return the next element
     * @throws NoSuchElementException if no more elements
     */
    Object getNext();
}

Usage Examples:

// Custom iterable that generates Fibonacci numbers
public class FibonacciSequence implements ProxyIterable {
    private final int maxCount;
    
    public FibonacciSequence(int maxCount) {
        this.maxCount = maxCount;
    }
    
    @Override
    public Object getIterator() {
        return new FibonacciIterator(maxCount);
    }
    
    private static class FibonacciIterator implements ProxyIterator {
        private final int maxCount;
        private int count = 0;
        private long a = 0, b = 1;
        
        FibonacciIterator(int maxCount) {
            this.maxCount = maxCount;
        }
        
        @Override
        public boolean hasNext() {
            return count < maxCount;
        }
        
        @Override
        public Object getNext() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            
            long result = a;
            long next = a + b;
            a = b;
            b = next;
            count++;
            return result;
        }
    }
}

context.getPolyglotBindings().putMember("fibonacci", new FibonacciSequence(10));

context.eval("python", """
    # Use in for loop like Python iterable
    for i, fib in enumerate(fibonacci):
        print(f"Fibonacci {i}: {fib}")
    
    # Convert to list
    fib_list = list(fibonacci)  # Note: creates new iterator
    print(f"First 10 Fibonacci numbers: {fib_list}")
    
    # Use in comprehensions
    even_fibs = [f for f in fibonacci if f % 2 == 0]
    print(f"Even Fibonacci numbers: {even_fibs}")
    """);

// Range-like iterable
public class JavaRange implements ProxyIterable {
    private final int start, stop, step;
    
    public JavaRange(int stop) {
        this(0, stop, 1);
    }
    
    public JavaRange(int start, int stop, int step) {
        this.start = start;
        this.stop = stop;
        this.step = step;
    }
    
    @Override
    public Object getIterator() {
        return new RangeIterator();
    }
    
    private class RangeIterator implements ProxyIterator {
        private int current = start;
        
        @Override
        public boolean hasNext() {
            return step > 0 ? current < stop : current > stop;
        }
        
        @Override
        public Object getNext() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            int result = current;
            current += step;
            return result;
        }
    }
}

// Create range function
context.getPolyglotBindings().putMember("jrange", (ProxyExecutable) args -> {
    if (args.length == 1) {
        return new JavaRange(args[0].asInt());
    } else if (args.length == 2) {
        return new JavaRange(args[0].asInt(), args[1].asInt(), 1);
    } else if (args.length == 3) {
        return new JavaRange(args[0].asInt(), args[1].asInt(), args[2].asInt());
    }
    throw new IllegalArgumentException("Expected 1-3 arguments");
});

context.eval("python", """
    # Use like Python range
    for i in jrange(5):
        print(i)  # 0, 1, 2, 3, 4
    
    for i in jrange(2, 8, 2):
        print(i)  # 2, 4, 6
    
    # Convert to list
    numbers = list(jrange(10, 20))
    print(numbers)  # [10, 11, 12, ..., 19]
    """);

Hash Map Proxies

Support Python dictionary-like operations with Java objects.

/**
 * Proxy interface for hash map objects (like Python dicts with arbitrary keys)
 */
public interface ProxyHashMap extends Proxy {
    /**
     * Gets the size of the hash map
     * @return number of key-value pairs
     */
    long getHashSize();
    
    /**
     * Checks if the hash map contains the specified key
     * @param key the key to check
     * @return true if the key exists
     */
    boolean hasHashEntry(Value key);
    
    /**
     * Gets the value for the specified key
     * @param key the key to look up
     * @return the value, or null if key doesn't exist
     */
    Value getHashValue(Value key);
    
    /**
     * Sets the value for the specified key
     * @param key the key
     * @param value the value to set
     */
    void putHashEntry(Value key, Value value);
    
    /**
     * Removes the entry for the specified key
     * @param key the key to remove
     * @return true if the entry was removed
     */
    boolean removeHashEntry(Value key);
    
    /**
     * Gets an iterator for the hash entries
     * @return iterator over key-value pairs
     */
    Value getHashEntriesIterator();
}

Usage Examples:

// Custom hash map implementation
public class JavaHashMap implements ProxyHashMap {
    private final Map<Object, Object> map = new HashMap<>();
    
    @Override
    public long getHashSize() {
        return map.size();
    }
    
    @Override
    public boolean hasHashEntry(Value key) {
        return map.containsKey(keyToJava(key));
    }
    
    @Override
    public Value getHashValue(Value key) {
        Object value = map.get(keyToJava(key));
        return value != null ? context.asValue(value) : null;
    }
    
    @Override
    public void putHashEntry(Value key, Value value) {
        map.put(keyToJava(key), value.as(Object.class));
    }
    
    @Override
    public boolean removeHashEntry(Value key) {
        return map.remove(keyToJava(key)) != null;
    }
    
    @Override
    public Value getHashEntriesIterator() {
        List<ProxyArray> entries = new ArrayList<>();
        for (Map.Entry<Object, Object> entry : map.entrySet()) {
            entries.add(ProxyArray.fromArray(entry.getKey(), entry.getValue()));
        }
        return context.asValue(ProxyArray.fromList(entries));
    }
    
    private Object keyToJava(Value key) {
        if (key.isString()) return key.asString();
        if (key.isNumber()) return key.asLong();
        return key.as(Object.class);
    }
}

context.getPolyglotBindings().putMember("java_dict", new JavaHashMap());

context.eval("python", """
    # Use like Python dict with any key types
    java_dict["string_key"] = "string_value"
    java_dict[42] = "number_key_value"
    java_dict[(1, 2)] = "tuple_key_value"
    
    print(len(java_dict))  # 3
    print(java_dict["string_key"])  # string_value
    print(java_dict[42])  # number_key_value
    
    # Check existence
    print("string_key" in java_dict)  # True
    print("missing" in java_dict)     # False
    
    # Iterate over items
    for key, value in java_dict.items():
        print(f"{key}: {value}")
    """);

Temporal Proxies

Support Python date/time objects with Java temporal types.

/**
 * Proxy interface for date objects
 */  
public interface ProxyDate extends Proxy {
    /**
     * Converts to Java LocalDate
     * @return LocalDate representation
     */
    LocalDate asDate();
}

/**
 * Proxy interface for time objects
 */
public interface ProxyTime extends Proxy {
    /**
     * Converts to Java LocalTime
     * @return LocalTime representation
     */
    LocalTime asTime();
}

/**
 * Proxy interface for timezone objects
 */
public interface ProxyTimeZone extends Proxy {
    /**
     * Converts to Java ZoneId
     * @return ZoneId representation
     */
    ZoneId asTimeZone();
}

/**
 * Proxy interface for duration objects
 */
public interface ProxyDuration extends Proxy {
    /**
     * Converts to Java Duration
     * @return Duration representation
     */
    Duration asDuration();
}

/**
 * Proxy interface for instant/timestamp objects
 */
public interface ProxyInstant extends Proxy {
    /**
     * Converts to Java Instant
     * @return Instant representation
     */
    Instant asInstant();
}

Usage Examples:

// Custom date proxy
public class JavaDate implements ProxyDate {
    private final LocalDate date;
    
    public JavaDate(LocalDate date) {
        this.date = date;
    }
    
    @Override
    public LocalDate asDate() {
        return date;
    }
    
    // Additional methods accessible from Python
    public int getYear() { return date.getYear(); }
    public int getMonth() { return date.getMonthValue(); }
    public int getDay() { return date.getDayOfMonth(); }
    public String format(String pattern) { 
        return date.format(DateTimeFormatter.ofPattern(pattern)); 
    }
}

// Date factory function
context.getPolyglotBindings().putMember("create_date", (ProxyExecutable) args -> {
    int year = args[0].asInt();
    int month = args[1].asInt();
    int day = args[2].asInt();
    return new JavaDate(LocalDate.of(year, month, day));
});

context.eval("python", """
    # Create and use date objects
    date = create_date(2023, 12, 25)
    print(f"Year: {date.getYear()}")    # 2023
    print(f"Month: {date.getMonth()}")  # 12
    print(f"Day: {date.getDay()}")      # 25
    print(date.format("yyyy-MM-dd"))    # 2023-12-25
    """);

// Duration proxy for time calculations
public class JavaDuration implements ProxyDuration {
    private final Duration duration;
    
    public JavaDuration(Duration duration) {
        this.duration = duration;
    }
    
    @Override
    public Duration asDuration() {
        return duration;
    }
    
    public long getSeconds() { return duration.getSeconds(); }
    public long getMillis() { return duration.toMillis(); }
    public String toString() { return duration.toString(); }
}

context.getPolyglotBindings().putMember("duration_from_seconds", (ProxyExecutable) args -> {
    long seconds = args[0].asLong();
    return new JavaDuration(Duration.ofSeconds(seconds));
});

context.eval("python", """
    # Create and use duration objects
    duration = duration_from_seconds(3661)  # 1 hour, 1 minute, 1 second
    print(f"Seconds: {duration.getSeconds()}")  # 3661
    print(f"Millis: {duration.getMillis()}")    # 3661000
    print(f"String: {duration.toString()}")     # PT1H1M1S
    """);

Types

/**
 * Exception thrown when proxy operations fail
 */
public final class ProxyException extends RuntimeException {
    public ProxyException(String message);
    public ProxyException(String message, Throwable cause);
}

/**
 * Interface for native pointer objects
 */
public interface ProxyNativeObject extends Proxy {
    /**
     * Gets the native pointer address
     * @return native pointer as long
     */
    long asPointer();
}

/**
 * Utility class for creating common proxy objects
 */
public final class ProxyUtils {
    /**
     * Creates a simple executable proxy from a lambda
     * @param function the function to execute
     * @return ProxyExecutable implementation
     */
    public static ProxyExecutable executable(Function<Value[], Object> function);
    
    /**
     * Creates a proxy object from method references
     * @param object the target object
     * @return ProxyObject with methods exposed
     */
    public static ProxyObject fromObject(Object object);
}

Install with Tessl CLI

npx tessl i tessl/maven-org-graalvm-polyglot--python

docs

context-management.md

engine-language-management.md

index.md

java-python-interop.md

security-access-control.md

source-management.md

value-operations.md

tile.json