0
# Java-Python Interoperability
1
2
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.
3
4
## Capabilities
5
6
### Object Proxies
7
8
Create Java objects that behave like Python objects with member access capabilities.
9
10
```java { .api }
11
/**
12
* Base interface for all proxy implementations
13
*/
14
public interface Proxy {
15
}
16
17
/**
18
* Proxy interface for objects with named members (like Python objects/dicts)
19
*/
20
public interface ProxyObject extends Proxy {
21
/**
22
* Gets the value of a member by key
23
* @param key the member name
24
* @return the member value
25
*/
26
Object getMember(String key);
27
28
/**
29
* Gets all member keys as an array or iterable
30
* @return object representing the available keys
31
*/
32
Object getMemberKeys();
33
34
/**
35
* Checks if a member with the given key exists
36
* @param key the member name to check
37
* @return true if the member exists
38
*/
39
boolean hasMember(String key);
40
41
/**
42
* Sets the value of a member
43
* @param key the member name
44
* @param value the value to set
45
*/
46
void putMember(String key, Value value);
47
48
/**
49
* Removes a member
50
* @param key the member name to remove
51
* @return true if the member was removed
52
*/
53
boolean removeMember(String key);
54
55
/**
56
* Creates a ProxyObject from a Java Map
57
* @param values the map to wrap
58
* @return ProxyObject representing the map
59
*/
60
static ProxyObject fromMap(Map<String, Object> values);
61
}
62
```
63
64
**Usage Examples:**
65
66
```java
67
// Create a Java object that acts like a Python dict
68
Map<String, Object> dataMap = new HashMap<>();
69
dataMap.put("name", "Alice");
70
dataMap.put("age", 30);
71
dataMap.put("city", "New York");
72
73
ProxyObject dictProxy = ProxyObject.fromMap(dataMap);
74
context.getPolyglotBindings().putMember("person", dictProxy);
75
76
// Access from Python like a normal dict
77
Value result = context.eval("python", """
78
print(person['name']) # Alice
79
print(person['age']) # 30
80
person['email'] = 'alice@example.com'
81
print(len(person)) # 4
82
list(person.keys()) # ['name', 'age', 'city', 'email']
83
""");
84
85
// Custom ProxyObject implementation
86
public class ConfigObject implements ProxyObject {
87
private final Map<String, Object> config = new HashMap<>();
88
89
@Override
90
public Object getMember(String key) {
91
return config.get(key);
92
}
93
94
@Override
95
public Object getMemberKeys() {
96
return config.keySet().toArray(new String[0]);
97
}
98
99
@Override
100
public boolean hasMember(String key) {
101
return config.containsKey(key);
102
}
103
104
@Override
105
public void putMember(String key, Value value) {
106
config.put(key, value.isString() ? value.asString() : value.as(Object.class));
107
}
108
109
@Override
110
public boolean removeMember(String key) {
111
return config.remove(key) != null;
112
}
113
114
// Custom method for validation
115
public void validate() {
116
System.out.println("Config validated with " + config.size() + " entries");
117
}
118
}
119
120
ConfigObject config = new ConfigObject();
121
context.getPolyglotBindings().putMember("config", config);
122
123
context.eval("python", """
124
config['database_url'] = 'postgresql://localhost:5432/db'
125
config['max_connections'] = 100
126
config['debug'] = True
127
128
# Access like Python dict
129
for key in config:
130
print(f"{key}: {config[key]}")
131
""");
132
133
config.validate(); // Call Java method
134
```
135
136
### Array Proxies
137
138
Enable Java objects to behave like Python lists and sequences.
139
140
```java { .api }
141
/**
142
* Proxy interface for array-like objects (like Python lists/tuples)
143
*/
144
public interface ProxyArray extends Proxy {
145
/**
146
* Gets an element at the specified index
147
* @param index the element index
148
* @return the element at the index
149
*/
150
Object get(long index);
151
152
/**
153
* Sets an element at the specified index
154
* @param index the element index
155
* @param value the value to set
156
*/
157
void set(long index, Value value);
158
159
/**
160
* Removes an element at the specified index
161
* @param index the element index to remove
162
* @return true if the element was removed
163
*/
164
boolean remove(long index);
165
166
/**
167
* Gets the size of the array
168
* @return number of elements
169
*/
170
long getSize();
171
172
/**
173
* Creates a ProxyArray from a Java array
174
* @param values the array to wrap
175
* @return ProxyArray representing the array
176
*/
177
static ProxyArray fromArray(Object... values);
178
179
/**
180
* Creates a ProxyArray from a Java List
181
* @param values the list to wrap
182
* @return ProxyArray representing the list
183
*/
184
static ProxyArray fromList(List<Object> values);
185
}
186
```
187
188
**Usage Examples:**
189
190
```java
191
// Create array proxy from Java array
192
ProxyArray arrayProxy = ProxyArray.fromArray("apple", "banana", "cherry", "date");
193
context.getPolyglotBindings().putMember("fruits", arrayProxy);
194
195
// Access from Python like a normal list
196
context.eval("python", """
197
print(fruits[0]) # apple
198
print(len(fruits)) # 4
199
fruits[1] = 'blueberry' # Modify element
200
201
# Iterate like Python list
202
for i, fruit in enumerate(fruits):
203
print(f"{i}: {fruit}")
204
205
# List comprehension
206
upper_fruits = [fruit.upper() for fruit in fruits]
207
print(upper_fruits)
208
""");
209
210
// Dynamic list proxy
211
public class DynamicList implements ProxyArray {
212
private final List<Object> items = new ArrayList<>();
213
214
@Override
215
public Object get(long index) {
216
if (index < 0 || index >= items.size()) {
217
return null; // Python None
218
}
219
return items.get((int) index);
220
}
221
222
@Override
223
public void set(long index, Value value) {
224
while (items.size() <= index) {
225
items.add(null); // Extend list if needed
226
}
227
items.set((int) index, value.as(Object.class));
228
}
229
230
@Override
231
public boolean remove(long index) {
232
if (index >= 0 && index < items.size()) {
233
items.remove((int) index);
234
return true;
235
}
236
return false;
237
}
238
239
@Override
240
public long getSize() {
241
return items.size();
242
}
243
244
// Java-specific methods
245
public void clear() {
246
items.clear();
247
}
248
249
public List<Object> getJavaList() {
250
return new ArrayList<>(items);
251
}
252
}
253
254
DynamicList dynamicList = new DynamicList();
255
context.getPolyglotBindings().putMember("dynamic_list", dynamicList);
256
257
context.eval("python", """
258
# Use like Python list
259
dynamic_list[0] = "first"
260
dynamic_list[1] = "second"
261
dynamic_list[5] = "sixth" # Auto-extends with None values
262
263
print(f"Length: {len(dynamic_list)}") # 6
264
print(f"Item 2: {dynamic_list[2]}") # None
265
print(f"Item 5: {dynamic_list[5]}") # sixth
266
""");
267
268
// Get Java list back
269
List<Object> javaList = dynamicList.getJavaList();
270
System.out.println("Java list: " + javaList);
271
```
272
273
### Executable Proxies
274
275
Make Java objects callable from Python like functions.
276
277
```java { .api }
278
/**
279
* Proxy interface for executable/callable objects (like Python functions)
280
*/
281
public interface ProxyExecutable extends Proxy {
282
/**
283
* Executes this object with the given arguments
284
* @param arguments the arguments to pass to execution
285
* @return the execution result
286
*/
287
Object execute(Value... arguments);
288
}
289
290
/**
291
* Proxy interface for instantiable objects (like Python classes/constructors)
292
*/
293
public interface ProxyInstantiable extends Proxy {
294
/**
295
* Creates a new instance using this object as constructor
296
* @param arguments the constructor arguments
297
* @return the new instance
298
*/
299
Object newInstance(Value... arguments);
300
}
301
```
302
303
**Usage Examples:**
304
305
```java
306
// Simple function proxy
307
public class MathFunctions implements ProxyExecutable {
308
@Override
309
public Object execute(Value... arguments) {
310
if (arguments.length != 2) {
311
throw new IllegalArgumentException("Expected 2 arguments");
312
}
313
314
double a = arguments[0].asDouble();
315
double b = arguments[1].asDouble();
316
return a + b; // Simple addition
317
}
318
}
319
320
context.getPolyglotBindings().putMember("add", new MathFunctions());
321
322
Value result = context.eval("python", """
323
result = add(5.5, 3.2) # Call like Python function
324
print(f"Result: {result}") # 8.7
325
result
326
""");
327
328
// Multi-function proxy with operation selection
329
public class Calculator implements ProxyExecutable {
330
@Override
331
public Object execute(Value... arguments) {
332
if (arguments.length < 3) {
333
throw new IllegalArgumentException("Expected: operation, a, b");
334
}
335
336
String operation = arguments[0].asString();
337
double a = arguments[1].asDouble();
338
double b = arguments[2].asDouble();
339
340
return switch (operation) {
341
case "add" -> a + b;
342
case "subtract" -> a - b;
343
case "multiply" -> a * b;
344
case "divide" -> b != 0 ? a / b : Double.NaN;
345
default -> throw new IllegalArgumentException("Unknown operation: " + operation);
346
};
347
}
348
}
349
350
context.getPolyglotBindings().putMember("calc", new Calculator());
351
352
context.eval("python", """
353
print(calc("add", 10, 5)) # 15.0
354
print(calc("multiply", 3, 4)) # 12.0
355
print(calc("divide", 15, 3)) # 5.0
356
""");
357
358
// Class-like instantiable proxy
359
public class PersonFactory implements ProxyInstantiable {
360
@Override
361
public Object newInstance(Value... arguments) {
362
String name = arguments.length > 0 ? arguments[0].asString() : "Unknown";
363
int age = arguments.length > 1 ? arguments[1].asInt() : 0;
364
365
Map<String, Object> person = new HashMap<>();
366
person.put("name", name);
367
person.put("age", age);
368
person.put("greet", (ProxyExecutable) args -> "Hello, I'm " + name);
369
370
return ProxyObject.fromMap(person);
371
}
372
}
373
374
context.getPolyglotBindings().putMember("Person", new PersonFactory());
375
376
context.eval("python", """
377
# Create instances like Python classes
378
alice = Person("Alice", 30)
379
bob = Person("Bob", 25)
380
381
print(alice['name']) # Alice
382
print(alice['age']) # 30
383
greeting = alice['greet']() # Call method
384
print(greeting) # Hello, I'm Alice
385
""");
386
```
387
388
### Iterator Proxies
389
390
Support Python iteration protocols with Java objects.
391
392
```java { .api }
393
/**
394
* Proxy interface for iterable objects (like Python iterables)
395
*/
396
public interface ProxyIterable extends Proxy {
397
/**
398
* Gets an iterator for this iterable
399
* @return the iterator object
400
*/
401
Object getIterator();
402
}
403
404
/**
405
* Proxy interface for iterator objects (like Python iterators)
406
*/
407
public interface ProxyIterator extends Proxy {
408
/**
409
* Checks if there are more elements
410
* @return true if more elements are available
411
*/
412
boolean hasNext();
413
414
/**
415
* Gets the next element from the iterator
416
* @return the next element
417
* @throws NoSuchElementException if no more elements
418
*/
419
Object getNext();
420
}
421
```
422
423
**Usage Examples:**
424
425
```java
426
// Custom iterable that generates Fibonacci numbers
427
public class FibonacciSequence implements ProxyIterable {
428
private final int maxCount;
429
430
public FibonacciSequence(int maxCount) {
431
this.maxCount = maxCount;
432
}
433
434
@Override
435
public Object getIterator() {
436
return new FibonacciIterator(maxCount);
437
}
438
439
private static class FibonacciIterator implements ProxyIterator {
440
private final int maxCount;
441
private int count = 0;
442
private long a = 0, b = 1;
443
444
FibonacciIterator(int maxCount) {
445
this.maxCount = maxCount;
446
}
447
448
@Override
449
public boolean hasNext() {
450
return count < maxCount;
451
}
452
453
@Override
454
public Object getNext() {
455
if (!hasNext()) {
456
throw new NoSuchElementException();
457
}
458
459
long result = a;
460
long next = a + b;
461
a = b;
462
b = next;
463
count++;
464
return result;
465
}
466
}
467
}
468
469
context.getPolyglotBindings().putMember("fibonacci", new FibonacciSequence(10));
470
471
context.eval("python", """
472
# Use in for loop like Python iterable
473
for i, fib in enumerate(fibonacci):
474
print(f"Fibonacci {i}: {fib}")
475
476
# Convert to list
477
fib_list = list(fibonacci) # Note: creates new iterator
478
print(f"First 10 Fibonacci numbers: {fib_list}")
479
480
# Use in comprehensions
481
even_fibs = [f for f in fibonacci if f % 2 == 0]
482
print(f"Even Fibonacci numbers: {even_fibs}")
483
""");
484
485
// Range-like iterable
486
public class JavaRange implements ProxyIterable {
487
private final int start, stop, step;
488
489
public JavaRange(int stop) {
490
this(0, stop, 1);
491
}
492
493
public JavaRange(int start, int stop, int step) {
494
this.start = start;
495
this.stop = stop;
496
this.step = step;
497
}
498
499
@Override
500
public Object getIterator() {
501
return new RangeIterator();
502
}
503
504
private class RangeIterator implements ProxyIterator {
505
private int current = start;
506
507
@Override
508
public boolean hasNext() {
509
return step > 0 ? current < stop : current > stop;
510
}
511
512
@Override
513
public Object getNext() {
514
if (!hasNext()) {
515
throw new NoSuchElementException();
516
}
517
int result = current;
518
current += step;
519
return result;
520
}
521
}
522
}
523
524
// Create range function
525
context.getPolyglotBindings().putMember("jrange", (ProxyExecutable) args -> {
526
if (args.length == 1) {
527
return new JavaRange(args[0].asInt());
528
} else if (args.length == 2) {
529
return new JavaRange(args[0].asInt(), args[1].asInt(), 1);
530
} else if (args.length == 3) {
531
return new JavaRange(args[0].asInt(), args[1].asInt(), args[2].asInt());
532
}
533
throw new IllegalArgumentException("Expected 1-3 arguments");
534
});
535
536
context.eval("python", """
537
# Use like Python range
538
for i in jrange(5):
539
print(i) # 0, 1, 2, 3, 4
540
541
for i in jrange(2, 8, 2):
542
print(i) # 2, 4, 6
543
544
# Convert to list
545
numbers = list(jrange(10, 20))
546
print(numbers) # [10, 11, 12, ..., 19]
547
""");
548
```
549
550
### Hash Map Proxies
551
552
Support Python dictionary-like operations with Java objects.
553
554
```java { .api }
555
/**
556
* Proxy interface for hash map objects (like Python dicts with arbitrary keys)
557
*/
558
public interface ProxyHashMap extends Proxy {
559
/**
560
* Gets the size of the hash map
561
* @return number of key-value pairs
562
*/
563
long getHashSize();
564
565
/**
566
* Checks if the hash map contains the specified key
567
* @param key the key to check
568
* @return true if the key exists
569
*/
570
boolean hasHashEntry(Value key);
571
572
/**
573
* Gets the value for the specified key
574
* @param key the key to look up
575
* @return the value, or null if key doesn't exist
576
*/
577
Value getHashValue(Value key);
578
579
/**
580
* Sets the value for the specified key
581
* @param key the key
582
* @param value the value to set
583
*/
584
void putHashEntry(Value key, Value value);
585
586
/**
587
* Removes the entry for the specified key
588
* @param key the key to remove
589
* @return true if the entry was removed
590
*/
591
boolean removeHashEntry(Value key);
592
593
/**
594
* Gets an iterator for the hash entries
595
* @return iterator over key-value pairs
596
*/
597
Value getHashEntriesIterator();
598
}
599
```
600
601
**Usage Examples:**
602
603
```java
604
// Custom hash map implementation
605
public class JavaHashMap implements ProxyHashMap {
606
private final Map<Object, Object> map = new HashMap<>();
607
608
@Override
609
public long getHashSize() {
610
return map.size();
611
}
612
613
@Override
614
public boolean hasHashEntry(Value key) {
615
return map.containsKey(keyToJava(key));
616
}
617
618
@Override
619
public Value getHashValue(Value key) {
620
Object value = map.get(keyToJava(key));
621
return value != null ? context.asValue(value) : null;
622
}
623
624
@Override
625
public void putHashEntry(Value key, Value value) {
626
map.put(keyToJava(key), value.as(Object.class));
627
}
628
629
@Override
630
public boolean removeHashEntry(Value key) {
631
return map.remove(keyToJava(key)) != null;
632
}
633
634
@Override
635
public Value getHashEntriesIterator() {
636
List<ProxyArray> entries = new ArrayList<>();
637
for (Map.Entry<Object, Object> entry : map.entrySet()) {
638
entries.add(ProxyArray.fromArray(entry.getKey(), entry.getValue()));
639
}
640
return context.asValue(ProxyArray.fromList(entries));
641
}
642
643
private Object keyToJava(Value key) {
644
if (key.isString()) return key.asString();
645
if (key.isNumber()) return key.asLong();
646
return key.as(Object.class);
647
}
648
}
649
650
context.getPolyglotBindings().putMember("java_dict", new JavaHashMap());
651
652
context.eval("python", """
653
# Use like Python dict with any key types
654
java_dict["string_key"] = "string_value"
655
java_dict[42] = "number_key_value"
656
java_dict[(1, 2)] = "tuple_key_value"
657
658
print(len(java_dict)) # 3
659
print(java_dict["string_key"]) # string_value
660
print(java_dict[42]) # number_key_value
661
662
# Check existence
663
print("string_key" in java_dict) # True
664
print("missing" in java_dict) # False
665
666
# Iterate over items
667
for key, value in java_dict.items():
668
print(f"{key}: {value}")
669
""");
670
```
671
672
### Temporal Proxies
673
674
Support Python date/time objects with Java temporal types.
675
676
```java { .api }
677
/**
678
* Proxy interface for date objects
679
*/
680
public interface ProxyDate extends Proxy {
681
/**
682
* Converts to Java LocalDate
683
* @return LocalDate representation
684
*/
685
LocalDate asDate();
686
}
687
688
/**
689
* Proxy interface for time objects
690
*/
691
public interface ProxyTime extends Proxy {
692
/**
693
* Converts to Java LocalTime
694
* @return LocalTime representation
695
*/
696
LocalTime asTime();
697
}
698
699
/**
700
* Proxy interface for timezone objects
701
*/
702
public interface ProxyTimeZone extends Proxy {
703
/**
704
* Converts to Java ZoneId
705
* @return ZoneId representation
706
*/
707
ZoneId asTimeZone();
708
}
709
710
/**
711
* Proxy interface for duration objects
712
*/
713
public interface ProxyDuration extends Proxy {
714
/**
715
* Converts to Java Duration
716
* @return Duration representation
717
*/
718
Duration asDuration();
719
}
720
721
/**
722
* Proxy interface for instant/timestamp objects
723
*/
724
public interface ProxyInstant extends Proxy {
725
/**
726
* Converts to Java Instant
727
* @return Instant representation
728
*/
729
Instant asInstant();
730
}
731
```
732
733
**Usage Examples:**
734
735
```java
736
// Custom date proxy
737
public class JavaDate implements ProxyDate {
738
private final LocalDate date;
739
740
public JavaDate(LocalDate date) {
741
this.date = date;
742
}
743
744
@Override
745
public LocalDate asDate() {
746
return date;
747
}
748
749
// Additional methods accessible from Python
750
public int getYear() { return date.getYear(); }
751
public int getMonth() { return date.getMonthValue(); }
752
public int getDay() { return date.getDayOfMonth(); }
753
public String format(String pattern) {
754
return date.format(DateTimeFormatter.ofPattern(pattern));
755
}
756
}
757
758
// Date factory function
759
context.getPolyglotBindings().putMember("create_date", (ProxyExecutable) args -> {
760
int year = args[0].asInt();
761
int month = args[1].asInt();
762
int day = args[2].asInt();
763
return new JavaDate(LocalDate.of(year, month, day));
764
});
765
766
context.eval("python", """
767
# Create and use date objects
768
date = create_date(2023, 12, 25)
769
print(f"Year: {date.getYear()}") # 2023
770
print(f"Month: {date.getMonth()}") # 12
771
print(f"Day: {date.getDay()}") # 25
772
print(date.format("yyyy-MM-dd")) # 2023-12-25
773
""");
774
775
// Duration proxy for time calculations
776
public class JavaDuration implements ProxyDuration {
777
private final Duration duration;
778
779
public JavaDuration(Duration duration) {
780
this.duration = duration;
781
}
782
783
@Override
784
public Duration asDuration() {
785
return duration;
786
}
787
788
public long getSeconds() { return duration.getSeconds(); }
789
public long getMillis() { return duration.toMillis(); }
790
public String toString() { return duration.toString(); }
791
}
792
793
context.getPolyglotBindings().putMember("duration_from_seconds", (ProxyExecutable) args -> {
794
long seconds = args[0].asLong();
795
return new JavaDuration(Duration.ofSeconds(seconds));
796
});
797
798
context.eval("python", """
799
# Create and use duration objects
800
duration = duration_from_seconds(3661) # 1 hour, 1 minute, 1 second
801
print(f"Seconds: {duration.getSeconds()}") # 3661
802
print(f"Millis: {duration.getMillis()}") # 3661000
803
print(f"String: {duration.toString()}") # PT1H1M1S
804
""");
805
```
806
807
## Types
808
809
```java { .api }
810
/**
811
* Exception thrown when proxy operations fail
812
*/
813
public final class ProxyException extends RuntimeException {
814
public ProxyException(String message);
815
public ProxyException(String message, Throwable cause);
816
}
817
818
/**
819
* Interface for native pointer objects
820
*/
821
public interface ProxyNativeObject extends Proxy {
822
/**
823
* Gets the native pointer address
824
* @return native pointer as long
825
*/
826
long asPointer();
827
}
828
829
/**
830
* Utility class for creating common proxy objects
831
*/
832
public final class ProxyUtils {
833
/**
834
* Creates a simple executable proxy from a lambda
835
* @param function the function to execute
836
* @return ProxyExecutable implementation
837
*/
838
public static ProxyExecutable executable(Function<Value[], Object> function);
839
840
/**
841
* Creates a proxy object from method references
842
* @param object the target object
843
* @return ProxyObject with methods exposed
844
*/
845
public static ProxyObject fromObject(Object object);
846
}
847
```