GraalVM Polyglot API for embedding and executing Python code within Java applications.
—
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.
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 methodEnable 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);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
""");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]
""");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}")
""");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
""");/**
* 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