GraalVM Polyglot API for embedding multiple programming languages in Java applications with secure language interoperability
—
The proxy system enables Java objects to be seamlessly exposed to guest languages with customizable behaviors. Proxy interfaces allow Java objects to participate in polyglot interactions by implementing language-specific protocols for object access, function execution, and data manipulation.
All proxy types implement the base Proxy interface.
public interface Proxy {
// Marker interface - no methods
}Enable Java objects to behave like guest language objects with named members.
public interface ProxyObject extends Proxy {
Object getMember(String key);
Object[] getMemberKeys();
boolean hasMember(String key);
void putMember(String key, Value value);
boolean removeMember(String key);
}Usage:
public class PersonProxy implements ProxyObject {
private final Map<String, Object> properties = new HashMap<>();
public PersonProxy(String name, int age) {
properties.put("name", name);
properties.put("age", age);
}
@Override
public Object getMember(String key) {
return properties.get(key);
}
@Override
public Object[] getMemberKeys() {
return properties.keySet().toArray();
}
@Override
public boolean hasMember(String key) {
return properties.containsKey(key);
}
@Override
public void putMember(String key, Value value) {
properties.put(key, value.isString() ? value.asString() : value);
}
@Override
public boolean removeMember(String key) {
return properties.remove(key) != null;
}
}
// Usage in context
try (Context context = Context.create("js")) {
PersonProxy person = new PersonProxy("Alice", 30);
context.getBindings("js").putMember("person", person);
// JavaScript can access as object
context.eval("js", "console.log(person.name)"); // "Alice"
context.eval("js", "person.age = 31"); // Modify age
context.eval("js", "person.city = 'New York'"); // Add new property
context.eval("js", "console.log(person.city)"); // "New York"
}Enable Java objects to behave like guest language arrays.
public interface ProxyArray extends ProxyIterable {
Object get(long index);
void set(long index, Value value);
long getSize();
boolean remove(long index);
}Usage:
public class ListProxy implements ProxyArray {
private final List<Object> list;
public ListProxy(List<Object> list) {
this.list = list;
}
@Override
public Object get(long index) {
return list.get((int) index);
}
@Override
public void set(long index, Value value) {
list.set((int) index, value.isString() ? value.asString() : value);
}
@Override
public long getSize() {
return list.size();
}
@Override
public boolean remove(long index) {
try {
list.remove((int) index);
return true;
} catch (IndexOutOfBoundsException e) {
return false;
}
}
@Override
public Object getIterator() {
return new IteratorProxy(list.iterator());
}
}
// Usage
try (Context context = Context.create("js")) {
ListProxy array = new ListProxy(new ArrayList<>(Arrays.asList("a", "b", "c")));
context.getBindings("js").putMember("array", array);
// JavaScript can access as array
context.eval("js", "console.log(array[0])"); // "a"
context.eval("js", "array[1] = 'modified'"); // Modify element
context.eval("js", "console.log(array.length)"); // Array size
}Enable Java objects to be called as functions.
public interface ProxyExecutable extends Proxy {
Object execute(Value... arguments);
}Usage:
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 2 arguments");
}
double a = arguments[0].asDouble();
double b = arguments[1].asDouble();
switch (operation) {
case "add": return a + b;
case "multiply": return a * b;
case "divide": return b != 0 ? a / b : Double.POSITIVE_INFINITY;
default: throw new IllegalArgumentException("Unknown operation: " + operation);
}
}
}
// Usage
try (Context context = Context.create("js")) {
MathFunction addFunc = new MathFunction("add");
context.getBindings("js").putMember("add", addFunc);
// JavaScript can call as function
Value result = context.eval("js", "add(10, 20)");
System.out.println("Result: " + result.asDouble()); // 30.0
}Enable Java objects to be used as constructors.
public interface ProxyInstantiable extends Proxy {
Object newInstance(Value... arguments);
}Usage:
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 PersonProxy(name, age);
}
}
// Usage
try (Context context = Context.create("js")) {
PersonConstructor PersonClass = new PersonConstructor();
context.getBindings("js").putMember("Person", PersonClass);
// JavaScript can use as constructor
context.eval("js", "const alice = new Person('Alice', 30)");
context.eval("js", "console.log(alice.name)"); // "Alice"
}Enable Java objects to support iteration protocols.
public interface ProxyIterable extends Proxy {
Object getIterator();
}
public interface ProxyIterator extends Proxy {
boolean hasNext();
Object getNext();
}Usage:
public class IteratorProxy implements ProxyIterator {
private final Iterator<?> iterator;
public IteratorProxy(Iterator<?> iterator) {
this.iterator = iterator;
}
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public Object getNext() {
return iterator.next();
}
}
public class RangeProxy implements ProxyIterable {
private final int start, end;
public RangeProxy(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public Object getIterator() {
return new IteratorProxy(IntStream.range(start, end).iterator());
}
}
// Usage
try (Context context = Context.create("js")) {
RangeProxy range = new RangeProxy(1, 5);
context.getBindings("js").putMember("range", range);
// JavaScript can iterate
context.eval("js", "for (const num of range) { console.log(num); }"); // 1, 2, 3, 4
}Enable Java objects to behave like guest language maps/dictionaries.
public interface ProxyHashMap extends Proxy {
long getHashSize();
boolean hasHashEntry(Value key);
Value getHashValue(Value key);
void putHashEntry(Value key, Value value);
boolean removeHashEntry(Value key);
Value getHashEntriesIterator();
Value getHashKeysIterator();
Value getHashValuesIterator();
}Usage:
public class MapProxy 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(convertKey(key));
}
@Override
public Value getHashValue(Value key) {
Object value = map.get(convertKey(key));
return value != null ? Value.asValue(value) : null;
}
@Override
public void putHashEntry(Value key, Value value) {
map.put(convertKey(key), convertValue(value));
}
@Override
public boolean removeHashEntry(Value key) {
return map.remove(convertKey(key)) != null;
}
// Helper methods
private Object convertKey(Value key) {
if (key.isString()) return key.asString();
if (key.isNumber()) return key.asInt();
return key;
}
private Object convertValue(Value value) {
if (value.isString()) return value.asString();
if (value.isNumber()) return value.asDouble();
if (value.isBoolean()) return value.asBoolean();
return value;
}
// Iterator implementations omitted for brevity
@Override
public Value getHashEntriesIterator() { /* ... */ }
@Override
public Value getHashKeysIterator() { /* ... */ }
@Override
public Value getHashValuesIterator() { /* ... */ }
}Enable Java objects to represent date and time values in guest languages.
public interface ProxyDate extends Proxy {
LocalDate asDate();
}
public interface ProxyTime extends Proxy {
LocalTime asTime();
}
public interface ProxyTimeZone extends Proxy {
ZoneId asTimeZone();
}
public interface ProxyDuration extends Proxy {
Duration asDuration();
}
public interface ProxyInstant extends ProxyDate, ProxyTime, ProxyTimeZone {
Instant asInstant();
}Usage:
public class DateTimeProxy implements ProxyInstant {
private final ZonedDateTime dateTime;
public DateTimeProxy(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();
}
@Override
public Instant asInstant() {
return dateTime.toInstant();
}
}
// Usage
try (Context context = Context.create("js")) {
DateTimeProxy now = new DateTimeProxy(ZonedDateTime.now());
context.getBindings("js").putMember("now", now);
// JavaScript can work with as date
context.eval("js", "console.log(new Date(now))");
}Implement multiple proxy interfaces for rich behavior:
public class SmartArray implements ProxyArray, ProxyObject, ProxyExecutable {
private final List<Object> data = new ArrayList<>();
// ProxyArray implementation
@Override
public Object get(long index) { return data.get((int) index); }
@Override
public void set(long index, Value value) { /* ... */ }
@Override
public long getSize() { return data.size(); }
@Override
public boolean remove(long index) { /* ... */ }
@Override
public Object getIterator() { /* ... */ }
// ProxyObject implementation (for methods like push, pop, etc.)
@Override
public Object getMember(String key) {
switch (key) {
case "push": return new PushFunction();
case "pop": return new PopFunction();
case "length": return data.size();
default: return null;
}
}
// ... other ProxyObject methods
// ProxyExecutable implementation (for functional behavior)
@Override
public Object execute(Value... arguments) {
// Array can be called as function (e.g., for filtering)
if (arguments.length == 1 && arguments[0].canExecute()) {
return data.stream()
.filter(item -> arguments[0].execute(item).asBoolean())
.collect(Collectors.toList());
}
throw new UnsupportedOperationException();
}
}Create proxies that adapt behavior based on usage:
public class DynamicProxy implements ProxyObject, ProxyExecutable {
private final Map<String, Object> properties = new HashMap<>();
private final Object target;
public DynamicProxy(Object target) {
this.target = target;
// Introspect target and populate properties
populateFromTarget();
}
@Override
public Object getMember(String key) {
// First check explicit properties
if (properties.containsKey(key)) {
return properties.get(key);
}
// Then try reflection on target
try {
Method method = target.getClass().getMethod(key);
return new MethodProxy(target, method);
} catch (NoSuchMethodException e) {
return null;
}
}
// ... implement other methods
}
class MethodProxy implements ProxyExecutable {
private final Object target;
private final Method method;
public MethodProxy(Object target, Method method) {
this.target = target;
this.method = method;
}
@Override
public Object execute(Value... arguments) {
try {
Object[] args = Arrays.stream(arguments)
.map(this::convertArgument)
.toArray();
return method.invoke(target, args);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private Object convertArgument(Value arg) {
// Convert Value to appropriate Java type
// ... implementation
}
}Proxy methods should handle errors gracefully:
public class SafeProxy implements ProxyObject {
@Override
public Object getMember(String key) {
try {
return doGetMember(key);
} catch (Exception e) {
// Log error, return null, or throw PolyglotException
throw new PolyglotException("Error accessing member: " + key, e);
}
}
// ... other methods with similar error handling
}Install with Tessl CLI
npx tessl i tessl/maven-org-graalvm-polyglot--polyglot