CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-graalvm-polyglot--graalvm-sdk

GraalVM Polyglot API for multi-language runtime environments with host-guest interoperability and security controls.

Pending
Overview
Eval results
Files

proxy-system.mddocs/

Proxy System for Custom Objects

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.

Base Proxy Interface

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.

Object-Like Behavior

ProxyObject Interface

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
    """);

Array-Like Behavior

ProxyArray Interface

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

Function-Like Behavior

ProxyExecutable Interface

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

ProxyInstantiable Interface

ProxyInstantiable 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"
    """);

Iteration Support

ProxyIterable Interface

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 Interface

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]
    """);

Map-Like Behavior

ProxyHashMap Interface

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}`);
    }
    """);

Temporal Proxy Interfaces

The polyglot API provides specialized proxy interfaces for temporal (date/time) values.

ProxyDate Interface

public interface ProxyDate extends Proxy {
    /**
     * Returns the date as a LocalDate.
     * @return the date
     */
    LocalDate asDate();
}

ProxyTime Interface

public interface ProxyTime extends Proxy {
    /**
     * Returns the time as a LocalTime.
     * @return the time
     */
    LocalTime asTime();
}

ProxyTimeZone Interface

public interface ProxyTimeZone extends Proxy {
    /**
     * Returns the timezone as a ZoneId.
     * @return the timezone
     */
    ZoneId asTimeZone();
}

ProxyDuration Interface

public interface ProxyDuration extends Proxy {
    /**
     * Returns the duration.
     * @return the duration
     */
    Duration asDuration();
}

ProxyInstant Interface

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
    """);

Native Pointer Support

ProxyNativeObject Interface

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

Composite Proxy Objects

You can implement multiple proxy interfaces to create objects with rich behavior.

Multi-Interface Proxy Example

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

Error Handling in Proxies

Exception Handling

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

Performance Best Practices

Efficient Proxy Implementation

  1. Minimize Object Creation: Reuse objects where possible
  2. Cache Computed Values: Store expensive calculations
  3. Use Appropriate Data Structures: Choose efficient backing collections
  4. Implement Only Needed Interfaces: Don't implement unused proxy interfaces
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

docs

context-management.md

index.md

io-filesystem.md

language-execution.md

monitoring-management.md

proxy-system.md

security-access.md

value-interop.md

tile.json