GraalVM Polyglot API for multi-language runtime environments with host-guest interoperability and security controls.
—
The proxy system enables host (Java) objects to mimic guest language objects with custom behavior. Proxy interfaces allow you to create objects that seamlessly integrate with guest languages by implementing specific behavioral contracts.
All proxy implementations must extend the base Proxy interface.
package org.graalvm.polyglot.proxy;
public interface Proxy {
// Marker interface - no methods
}The Proxy interface serves as a marker interface that identifies objects as polyglot proxies. Host objects implementing Proxy interfaces can be exposed to guest languages and will behave according to the implemented proxy contracts.
ProxyObject enables host objects to behave like objects with named members (properties/fields).
public interface ProxyObject extends Proxy {
/**
* Returns the value of the member.
* @param key the member identifier
* @return the member value, or null if the member does not exist
*/
Object getMember(String key);
/**
* Returns an array-like object containing all member keys.
* @return array-like object with member names, or null if not supported
*/
Object getMemberKeys();
/**
* Checks if a member exists.
* @param key the member identifier
* @return true if the member exists
*/
boolean hasMember(String key);
/**
* Sets the value of a member.
* @param key the member identifier
* @param value the new member value
*/
void putMember(String key, Value value);
/**
* Removes a member.
* @param key the member identifier
* @return true if the member was removed, false if it didn't exist
*/
boolean removeMember(String key);
}ProxyObject Implementation Example:
import org.graalvm.polyglot.proxy.ProxyObject;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
public class CustomObject implements ProxyObject {
private final Map<String, Object> properties = new HashMap<>();
public CustomObject() {
properties.put("name", "Custom Object");
properties.put("version", "1.0");
}
@Override
public Object getMember(String key) {
return properties.get(key);
}
@Override
public Object getMemberKeys() {
return properties.keySet().toArray(new String[0]);
}
@Override
public boolean hasMember(String key) {
return properties.containsKey(key);
}
@Override
public void putMember(String key, Value value) {
properties.put(key, value.as(Object.class));
}
@Override
public boolean removeMember(String key) {
return properties.remove(key) != null;
}
}
// Usage
Context context = Context.create("js");
CustomObject obj = new CustomObject();
context.getBindings("js").putMember("customObj", obj);
context.eval("js", """
console.log(customObj.name); // "Custom Object"
customObj.description = "A proxy object";
console.log(customObj.description); // "A proxy object"
delete customObj.version;
console.log(customObj.version); // undefined
""");ProxyArray enables host objects to behave like arrays with indexed access.
public interface ProxyArray extends ProxyIterable {
/**
* Returns the element at the given index.
* @param index the element index
* @return the element at the index
*/
Object get(long index);
/**
* Sets the element at the given index.
* @param index the element index
* @param value the new element value
*/
void set(long index, Value value);
/**
* Removes the element at the given index.
* @param index the element index
* @return true if the element was removed
*/
boolean remove(long index);
/**
* Returns the array size.
* @return the array size
*/
long getSize();
}ProxyArray Implementation Example:
import org.graalvm.polyglot.proxy.ProxyArray;
import java.util.List;
import java.util.ArrayList;
public class DynamicArray implements ProxyArray {
private final List<Object> elements = new ArrayList<>();
@Override
public Object get(long index) {
if (index < 0 || index >= elements.size()) {
return null;
}
return elements.get((int) index);
}
@Override
public void set(long index, Value value) {
int idx = (int) index;
// Extend list if necessary
while (elements.size() <= idx) {
elements.add(null);
}
elements.set(idx, value.as(Object.class));
}
@Override
public boolean remove(long index) {
if (index < 0 || index >= elements.size()) {
return false;
}
elements.remove((int) index);
return true;
}
@Override
public long getSize() {
return elements.size();
}
@Override
public Object getIterator() {
return new ArrayIterator(elements);
}
}
// Usage
Context context = Context.create("js");
DynamicArray arr = new DynamicArray();
context.getBindings("js").putMember("dynamicArray", arr);
context.eval("js", """
dynamicArray[0] = "first";
dynamicArray[1] = 42;
dynamicArray[2] = true;
console.log(dynamicArray.length); // 3
console.log(dynamicArray[1]); // 42
for (let item of dynamicArray) {
console.log(item); // "first", 42, true
}
""");ProxyExecutable enables host objects to behave like functions that can be called.
public interface ProxyExecutable extends Proxy {
/**
* Executes the proxy as a function.
* @param arguments the function arguments
* @return the execution result
*/
Object execute(Value... arguments);
}ProxyExecutable Implementation Example:
import org.graalvm.polyglot.proxy.ProxyExecutable;
public class MathFunction implements ProxyExecutable {
private final String operation;
public MathFunction(String operation) {
this.operation = operation;
}
@Override
public Object execute(Value... arguments) {
if (arguments.length != 2) {
throw new IllegalArgumentException("Expected exactly 2 arguments");
}
double a = arguments[0].asDouble();
double b = arguments[1].asDouble();
return switch (operation) {
case "add" -> a + b;
case "subtract" -> a - b;
case "multiply" -> a * b;
case "divide" -> b != 0 ? a / b : Double.NaN;
case "power" -> Math.pow(a, b);
default -> throw new IllegalArgumentException("Unknown operation: " + operation);
};
}
}
// Usage
Context context = Context.create("js");
context.getBindings("js").putMember("add", new MathFunction("add"));
context.getBindings("js").putMember("multiply", new MathFunction("multiply"));
Value result = context.eval("js", "add(multiply(3, 4), 5)"); // (3 * 4) + 5 = 17
System.out.println(result.asDouble()); // 17.0ProxyInstantiable enables host objects to behave like constructors that can create new instances.
public interface ProxyInstantiable extends Proxy {
/**
* Creates a new instance using this proxy as a constructor.
* @param arguments the constructor arguments
* @return the new instance
*/
Object newInstance(Value... arguments);
}ProxyInstantiable Implementation Example:
import org.graalvm.polyglot.proxy.ProxyInstantiable;
import org.graalvm.polyglot.proxy.ProxyObject;
public class PersonConstructor implements ProxyInstantiable {
@Override
public Object newInstance(Value... arguments) {
if (arguments.length < 2) {
throw new IllegalArgumentException("Person requires name and age");
}
String name = arguments[0].asString();
int age = arguments[1].asInt();
return new PersonInstance(name, age);
}
// Inner class representing a Person instance
public static class PersonInstance implements ProxyObject {
private final Map<String, Object> properties = new HashMap<>();
public PersonInstance(String name, int age) {
properties.put("name", name);
properties.put("age", age);
}
@Override
public Object getMember(String key) {
if ("greet".equals(key)) {
return new ProxyExecutable() {
@Override
public Object execute(Value... arguments) {
return "Hello, I'm " + properties.get("name") +
" and I'm " + properties.get("age") + " years old.";
}
};
}
return properties.get(key);
}
@Override
public Object getMemberKeys() {
Set<String> keys = new HashSet<>(properties.keySet());
keys.add("greet");
return keys.toArray(new String[0]);
}
@Override
public boolean hasMember(String key) {
return properties.containsKey(key) || "greet".equals(key);
}
@Override
public void putMember(String key, Value value) {
if (!"greet".equals(key)) {
properties.put(key, value.as(Object.class));
}
}
@Override
public boolean removeMember(String key) {
return !"greet".equals(key) && properties.remove(key) != null;
}
}
}
// Usage
Context context = Context.create("js");
context.getBindings("js").putMember("Person", new PersonConstructor());
context.eval("js", """
let alice = new Person("Alice", 30);
console.log(alice.name); // "Alice"
console.log(alice.greet()); // "Hello, I'm Alice and I'm 30 years old."
let bob = new Person("Bob", 25);
bob.occupation = "Developer";
console.log(bob.occupation); // "Developer"
""");ProxyIterable enables host objects to be iterated in guest languages.
public interface ProxyIterable extends Proxy {
/**
* Returns an iterator for this object.
* @return an iterator (typically a ProxyIterator)
*/
Object getIterator();
}ProxyIterator represents iterator objects that can be used in for-loops and other iteration constructs.
public interface ProxyIterator extends Proxy {
/**
* Checks if the iterator has more elements.
* @return true if there are more elements
*/
boolean hasNext();
/**
* Returns the next element and advances the iterator.
* @return the next element
*/
Object getNext();
}Iterator Implementation Example:
import org.graalvm.polyglot.proxy.ProxyIterable;
import org.graalvm.polyglot.proxy.ProxyIterator;
public class Range implements ProxyIterable {
private final int start;
private final int end;
private final int step;
public Range(int start, int end, int step) {
this.start = start;
this.end = end;
this.step = step;
}
@Override
public Object getIterator() {
return new RangeIterator(start, end, step);
}
private static class RangeIterator implements ProxyIterator {
private int current;
private final int end;
private final int step;
public RangeIterator(int start, int end, int step) {
this.current = start;
this.end = end;
this.step = step;
}
@Override
public boolean hasNext() {
return step > 0 ? current < end : current > end;
}
@Override
public Object getNext() {
if (!hasNext()) {
throw new RuntimeException("No more elements");
}
int value = current;
current += step;
return value;
}
}
}
// Usage
Context context = Context.create("js");
context.getBindings("js").putMember("range", new Range(0, 10, 2));
context.eval("js", """
for (let num of range) {
console.log(num); // 0, 2, 4, 6, 8
}
// Convert to array
let arr = Array.from(range);
console.log(arr); // [0, 2, 4, 6, 8]
""");ProxyHashMap enables host objects to behave like hash maps or dictionaries with key-value operations.
public interface ProxyHashMap extends Proxy {
/**
* Returns the value for the given key.
* @param key the key
* @return the value, or null if not found
*/
Object getHashValue(Object key);
/**
* Sets a key-value pair.
* @param key the key
* @param value the value
*/
void putHashEntry(Object key, Value value);
/**
* Removes a key-value pair.
* @param key the key to remove
* @return true if the key was removed
*/
boolean removeHashEntry(Object key);
/**
* Returns the number of entries.
* @return the size
*/
long getHashSize();
/**
* Checks if a key exists.
* @param key the key
* @return true if the key exists
*/
boolean hasHashEntry(Object key);
/**
* Returns an iterator over all entries.
* @return iterator yielding [key, value] pairs
*/
Object getHashEntriesIterator();
/**
* Returns an iterator over all keys.
* @return iterator yielding keys
*/
Object getHashKeysIterator();
/**
* Returns an iterator over all values.
* @return iterator yielding values
*/
Object getHashValuesIterator();
}ProxyHashMap Implementation Example:
import org.graalvm.polyglot.proxy.ProxyHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
public class ConcurrentMap implements ProxyHashMap {
private final Map<Object, Object> map = new ConcurrentHashMap<>();
@Override
public Object getHashValue(Object key) {
return map.get(key);
}
@Override
public void putHashEntry(Object key, Value value) {
map.put(key, value.as(Object.class));
}
@Override
public boolean removeHashEntry(Object key) {
return map.remove(key) != null;
}
@Override
public long getHashSize() {
return map.size();
}
@Override
public boolean hasHashEntry(Object key) {
return map.containsKey(key);
}
@Override
public Object getHashEntriesIterator() {
return new MapEntriesIterator(map.entrySet().iterator());
}
@Override
public Object getHashKeysIterator() {
return new SimpleIterator(map.keySet().iterator());
}
@Override
public Object getHashValuesIterator() {
return new SimpleIterator(map.values().iterator());
}
}
// Usage
Context context = Context.create("js");
context.getBindings("js").putMember("concurrentMap", new ConcurrentMap());
context.eval("js", """
concurrentMap.set("user:1", {name: "Alice", age: 30});
concurrentMap.set("user:2", {name: "Bob", age: 25});
console.log(concurrentMap.size); // 2
console.log(concurrentMap.get("user:1").name); // "Alice"
for (let [key, value] of concurrentMap.entries()) {
console.log(`${key}: ${value.name}`);
}
""");The polyglot API provides specialized proxy interfaces for temporal (date/time) values.
public interface ProxyDate extends Proxy {
/**
* Returns the date as a LocalDate.
* @return the date
*/
LocalDate asDate();
}public interface ProxyTime extends Proxy {
/**
* Returns the time as a LocalTime.
* @return the time
*/
LocalTime asTime();
}public interface ProxyTimeZone extends Proxy {
/**
* Returns the timezone as a ZoneId.
* @return the timezone
*/
ZoneId asTimeZone();
}public interface ProxyDuration extends Proxy {
/**
* Returns the duration.
* @return the duration
*/
Duration asDuration();
}ProxyInstant combines date, time, and timezone information.
public interface ProxyInstant extends ProxyDate, ProxyTime, ProxyTimeZone {
// Inherits asDate(), asTime(), and asTimeZone() methods
}Temporal Proxy Example:
import org.graalvm.polyglot.proxy.ProxyInstant;
import java.time.*;
public class CustomDateTime implements ProxyInstant {
private final ZonedDateTime dateTime;
public CustomDateTime(ZonedDateTime dateTime) {
this.dateTime = dateTime;
}
@Override
public LocalDate asDate() {
return dateTime.toLocalDate();
}
@Override
public LocalTime asTime() {
return dateTime.toLocalTime();
}
@Override
public ZoneId asTimeZone() {
return dateTime.getZone();
}
}
// Usage
Context context = Context.create("js");
ZonedDateTime now = ZonedDateTime.now(ZoneId.of("America/New_York"));
context.getBindings("js").putMember("customTime", new CustomDateTime(now));
context.eval("js", """
console.log(customTime); // Shows as date/time object
let jsDate = new Date(customTime); // Converts to JavaScript Date
console.log(jsDate.getFullYear()); // Accesses year
""");ProxyNativeObject represents objects that wrap native pointers.
public interface ProxyNativeObject extends Proxy {
/**
* Returns the native pointer value.
* @return the pointer as a long value
*/
long asPointer();
}Native Pointer Example:
import org.graalvm.polyglot.proxy.ProxyNativeObject;
public class NativeBuffer implements ProxyNativeObject {
private final long nativePointer;
private final int size;
public NativeBuffer(long pointer, int size) {
this.nativePointer = pointer;
this.size = size;
}
@Override
public long asPointer() {
return nativePointer;
}
public int getSize() {
return size;
}
}
// Usage with languages that support native pointers
Context context = Context.create("llvm"); // Example with LLVM language
NativeBuffer buffer = new NativeBuffer(0x7fff12345678L, 1024);
context.getBindings("llvm").putMember("nativeBuffer", buffer);You can implement multiple proxy interfaces to create objects with rich behavior.
import org.graalvm.polyglot.proxy.*;
import java.util.*;
public class SmartCollection implements ProxyArray, ProxyObject, ProxyIterable {
private final List<Object> data = new ArrayList<>();
private final Map<String, Object> metadata = new HashMap<>();
public SmartCollection() {
metadata.put("created", System.currentTimeMillis());
metadata.put("type", "SmartCollection");
}
// ProxyArray implementation
@Override
public Object get(long index) {
return index >= 0 && index < data.size() ? data.get((int) index) : null;
}
@Override
public void set(long index, Value value) {
int idx = (int) index;
while (data.size() <= idx) {
data.add(null);
}
data.set(idx, value.as(Object.class));
}
@Override
public boolean remove(long index) {
if (index >= 0 && index < data.size()) {
data.remove((int) index);
return true;
}
return false;
}
@Override
public long getSize() {
return data.size();
}
// ProxyObject implementation
@Override
public Object getMember(String key) {
switch (key) {
case "length": return data.size();
case "push": return new ProxyExecutable() {
@Override
public Object execute(Value... arguments) {
for (Value arg : arguments) {
data.add(arg.as(Object.class));
}
return data.size();
}
};
case "pop": return new ProxyExecutable() {
@Override
public Object execute(Value... arguments) {
return data.isEmpty() ? null : data.remove(data.size() - 1);
}
};
default: return metadata.get(key);
}
}
@Override
public Object getMemberKeys() {
Set<String> keys = new HashSet<>(metadata.keySet());
keys.addAll(Arrays.asList("length", "push", "pop"));
return keys.toArray(new String[0]);
}
@Override
public boolean hasMember(String key) {
return metadata.containsKey(key) ||
Arrays.asList("length", "push", "pop").contains(key);
}
@Override
public void putMember(String key, Value value) {
if (!Arrays.asList("length", "push", "pop").contains(key)) {
metadata.put(key, value.as(Object.class));
}
}
@Override
public boolean removeMember(String key) {
return metadata.remove(key) != null;
}
// ProxyIterable implementation
@Override
public Object getIterator() {
return new SimpleIterator(data.iterator());
}
}
// Usage
Context context = Context.create("js");
SmartCollection collection = new SmartCollection();
context.getBindings("js").putMember("smartArray", collection);
context.eval("js", """
// Use as array
smartArray[0] = "first";
smartArray[1] = "second";
console.log(smartArray.length); // 2
// Use array methods
smartArray.push("third", "fourth");
console.log(smartArray.length); // 4
// Use as object
smartArray.description = "A smart collection";
console.log(smartArray.description); // "A smart collection"
// Iterate
for (let item of smartArray) {
console.log(item); // "first", "second", "third", "fourth"
}
""");Proxy methods can throw exceptions that will be properly propagated to guest languages:
public class SafeCalculator implements ProxyExecutable {
@Override
public Object execute(Value... arguments) {
if (arguments.length != 2) {
throw new IllegalArgumentException("Calculator requires exactly 2 arguments");
}
try {
double a = arguments[0].asDouble();
double b = arguments[1].asDouble();
if (Double.isNaN(a) || Double.isNaN(b)) {
throw new ArithmeticException("Arguments cannot be NaN");
}
return a / b;
} catch (ClassCastException e) {
throw new IllegalArgumentException("Arguments must be numbers", e);
}
}
}
// Usage
Context context = Context.create("js");
context.getBindings("js").putMember("divide", new SafeCalculator());
try {
context.eval("js", "divide('not', 'numbers')");
} catch (PolyglotException e) {
if (e.isHostException()) {
Throwable hostException = e.asHostException();
System.out.println("Host exception: " + hostException.getMessage());
}
}public class EfficientProxy implements ProxyObject, ProxyArray {
private final Map<String, Object> members = new HashMap<>();
private final List<Object> elements = new ArrayList<>();
// Cache frequently accessed members
private ProxyExecutable cachedMethod;
@Override
public Object getMember(String key) {
if ("expensiveMethod".equals(key)) {
if (cachedMethod == null) {
cachedMethod = new ExpensiveMethod();
}
return cachedMethod;
}
return members.get(key);
}
// ... other implementations
}Install with Tessl CLI
npx tessl i tessl/maven-org-graalvm-polyglot--graalvm-sdk