Type Conversion API¶
The type_conversion module provides utilities for converting between Python and Java types when working with ArcadeDB's Java backend.
Overview¶
The type_conversion module enables:
- Automatic Conversion: Seamless Python ↔ Java type conversion
- Collection Handling: Lists, sets, maps, and nested structures
- Date/Time Support: datetime and date objects (
datetime.timeis not converted) - Decimal Precision: High-precision decimal numbers
- Binary Data:
bytesandbytearrayare stored as Javabyte[](from 26.10.1) - Type Safety: Validation and error handling
Why Type Conversion?¶
ArcadeDB Python bindings wrap a Java database engine. When you:
- Set properties on records → Python values converted to Java
- Read properties from records → Java values converted to Python
- Pass query parameters → Python values converted to Java (positional parameters
convert
Decimal,date, anddatetimeas below from 26.10.1; before, aDecimalreached the engine as aDoubleand kept only about 16 significant digits, and adateordatetimewas refused with "No matching overloads") - Receive query results → Java values converted to Python
The type_conversion module handles this automatically.
Conversion Functions¶
convert_python_to_java¶
from arcadedb_embedded.type_conversion import convert_python_to_java
java_value = convert_python_to_java(python_value)
Convert Python value to Java object for ArcadeDB.
This function only converts the types listed below explicitly. All other Python
values (bool, int, float, str, and any Java objects) are returned as-is so
that JPype performs the conversion automatically (a Python int reaches Java as a
Long). A numpy.bool_ is converted to a Python bool first: it is not a bool
subclass, and JPype would store it as the Double 1.0 or 0.0.
Supported Conversions:
| Python Type | Java Type |
|---|---|
None |
null |
Decimal |
BigDecimal |
set |
HashSet (stored as a list, see below) |
dict |
HashMap |
list |
ArrayList |
tuple |
ArrayList |
datetime |
java.time.LocalDateTime (a UTC wall clock) |
date |
LocalDate |
bytes, bytearray |
byte[] |
Notes:
datetimeis converted to aLocalDateTimewith its microseconds. The engine storesDATETIMEas a UTC wall clock, whatever the host's or the database's time zone, and reads it back as a naivedatetime. A naive value is taken as that wall clock as it stands, so it reads back unchanged on every host. A timezone-aware value is converted to UTC first and keeps its instant:21:34+09:00is stored and read back as12:34.- For the current time, store
datetime.now(timezone.utc). A naivedatetime.now()is the host's local wall clock, which the engine then reads as UTC: on a UTC+9 host it lands nine hours after the real instant. - Before 26.10.1 a
datetimecrossed as ajava.util.Date, which read a naive value as local time and kept milliseconds, soDATETIME_MICROSandDATETIME_NANOSlost the rest and a lookup by the same value found nothing. dateis converted to aLocalDate. If the Java types are unavailable it is combined withtime.minand converted as adatetime.- Collection elements, set members, and map keys/values are converted recursively.
- A
listortuplewhose elements are allint,float,str,bool, orNonecrosses into the JVM as oneObject[]rather than one call per element, with the same element types (Long,Double,String,Boolean, null). For a numeric vector a NumPy array throughto_java_float_array()is still faster: it is copied as one buffer and needs no boxing.
Example:
from arcadedb_embedded.type_conversion import convert_python_to_java
from datetime import datetime, timezone
from decimal import Decimal
# Primitive types
java_int = convert_python_to_java(42)
java_str = convert_python_to_java("hello")
java_bool = convert_python_to_java(True)
# Date/time
java_datetime = convert_python_to_java(datetime.now(timezone.utc))
# Decimal
java_decimal = convert_python_to_java(Decimal("123.456"))
# Collections
java_list = convert_python_to_java([1, 2, 3])
java_dict = convert_python_to_java({"key": "value"})
# Nested structures
java_nested = convert_python_to_java({
"users": [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25}
]
})
convert_java_to_python¶
from arcadedb_embedded.type_conversion import convert_java_to_python
python_value = convert_java_to_python(java_value)
Convert Java object to Python value.
Supported Conversions:
| Java Type | Python Type |
|---|---|
null |
None |
Boolean |
bool |
String, Character |
str |
Integer, Long, Short, Byte |
int |
Float, Double |
float |
BigDecimal |
Decimal |
BigInteger |
int |
java.util.Date |
datetime |
LocalDate |
date |
LocalDateTime |
datetime |
Instant, ZonedDateTime, OffsetDateTime |
datetime (UTC) |
Map |
dict |
Set |
set (a stored property is never a Set: it was written as a list) |
List, Collection |
list |
Notes:
Instant,ZonedDateTime, andOffsetDateTimeare returned as timezone-awaredatetimeobjects in UTC.- Other indexable Java objects (such as primitive arrays) are converted element-by-element to a
list. - Unrecognized Java objects (for example
Vertex,Edge,Document) are returned unchanged.
Example:
from arcadedb_embedded.type_conversion import convert_java_to_python
# Read from database (automatic conversion)
result = list(db.query("sql", "SELECT FROM User WHERE username = 'alice'"))[0]
# Get Python properties (automatically converted)
username = result.get("username") # str
age = result.get("age") # int
tags = result.get("tags") # list
profile = result.get("profile") # dict
# Manual conversion (if needed for custom types)
element = result.get_element()
java_property = element.get("username") # Uses wrapper method
# Properties are automatically converted
Type-Specific Conversions¶
Integers¶
from arcadedb_embedded.type_conversion import convert_python_to_java
# A Python int is returned unchanged; JPype passes it to Java as a Long
value = convert_python_to_java(42) # 42 (Python int)
value = convert_python_to_java(2**40) # 1099511627776 (Python int)
# Reading integers with SQL parameter binding
with db.transaction():
db.command("sql", "INSERT INTO User SET age = ?", 30) # Python int → Java Long
age = db.query("sql", "SELECT age FROM User LIMIT 1").first().get("age")
Floats and Decimals¶
from arcadedb_embedded.type_conversion import convert_python_to_java
from decimal import Decimal
# Float → Double
java_double = convert_python_to_java(3.14159)
# Decimal → BigDecimal (for precision)
java_decimal = convert_python_to_java(Decimal("123.456789"))
# Reading with SQL parameter binding
with db.transaction():
db.command(
"sql",
"INSERT INTO Product SET price = ?, tax = ?",
19.99,
Decimal("1.23"),
)
row = db.query("sql", "SELECT price, tax FROM Product LIMIT 1").first()
price = row.get("price") # Double → float
tax = row.get("tax") # BigDecimal → Decimal
Strings¶
# String conversion with SQL parameter binding
with db.transaction():
db.command(
"sql",
"INSERT INTO User SET name = ?, emoji = ?",
"Alice",
"Hello 👋 World 🌍",
)
row = db.query("sql", "SELECT name, emoji FROM User LIMIT 1").first()
name = row.get("name") # String → str
emoji = row.get("emoji") # Full Unicode support
Dates and Times¶
from datetime import date, datetime, timezone
# datetime/date with SQL parameter binding
with db.transaction():
db.command(
"sql",
"INSERT INTO Event SET timestamp = ?, birthDate = ?",
datetime.now(timezone.utc),
date(1990, 1, 15),
)
row = db.query("sql", "SELECT timestamp, birthDate FROM Event LIMIT 1").first()
timestamp = row.get("timestamp") # datetime
birth_date = row.get("birthDate") # date
A datetime.time value is not converted: set() raises TypeError and a
bound SQL parameter raises ArcadeDBError. Store a time of day as a string
or as seconds since midnight.
Why some examples use set()
This page documents record property conversion behavior directly, so some examples use wrapper/property APIs to make the conversion boundary explicit. For normal application CRUD, prefer SQL/OpenCypher with bound parameters.
Binary Data¶
import arcadedb_embedded as arcadedb
# bytes → byte[] (requires an active transaction)
binary_data = b"Hello World"
with db.transaction():
vertex = db.new_vertex("File")
vertex.set("data", binary_data)
# Reading back: a byte[] comes back as a list of signed ints
data = vertex.get("data") # [72, 101, 108, ...]
print(bytes(b & 0xFF for b in data)) # b"Hello World"
bytes and bytearray are stored as byte[] both through set() and as a
bound SQL parameter. Before 26.10.1 they reached Java as a String: text bytes
came back as str, and bytes that are not valid UTF-8 were stored as an empty
string without an error. On those wheels, wrap the value in
arcadedb.to_java_byte_array(), which stores the same byte[].
Lists and Sets¶
# list → ArrayList (requires an active transaction)
with db.transaction():
vertex = db.new_vertex("User")
vertex.set("tags", ["python", "java", "database"])
tags = vertex.get("tags") # list
# set → HashSet, stored as a list: a set is a set only until the commit
vertex.set("roles", {"admin", "user"})
roles = vertex.get("roles") # set, in this transaction only
# Nested lists
vertex.set("matrix", [[1, 2], [3, 4]])
matrix = vertex.get("matrix") # [[1, 2], [3, 4]]
Dictionaries (Maps)¶
# dict → HashMap (requires an active transaction)
with db.transaction():
vertex = db.new_vertex("User")
vertex.set("profile", {
"firstName": "Alice",
"lastName": "Smith",
"address": {
"city": "New York",
"zip": "10001"
}
})
# Reading back
profile = vertex.get("profile") # dict
print(profile["firstName"]) # "Alice"
print(profile["address"]["city"]) # "New York"
Query Parameter Conversion¶
from datetime import datetime
# Query parameters are automatically converted
results = db.query(
"sql",
"SELECT FROM User WHERE age > ? AND createdAt > ?",
25, datetime(2024, 1, 1) # Python types converted to Java
)
results = db.query(
"sql",
"SELECT FROM User WHERE age IN :ages",
{"ages": [25, 30, 35]} # dict/list converted to Java Map/List
)
# Reading results (automatically converted back to Python)
for result in results:
name = result.get("name") # str
age = result.get("age") # int
created = result.get("createdAt") # datetime
Collection Type Preservation¶
# Lists preserve order (requires an active transaction)
with db.transaction():
vertex = db.new_vertex("Sequence")
vertex.set("numbers", [3, 1, 4, 1, 5, 9])
numbers = vertex.get("numbers") # [3, 1, 4, 1, 5, 9]
# Sets remove duplicates (in this transaction: see the note below)
vertex.set("unique", {3, 1, 4, 1, 5, 9})
unique = vertex.get("unique") # {1, 3, 4, 5, 9}
# Dicts preserve keys
vertex.set("mapping", {"a": 1, "b": 2, "c": 3})
mapping = vertex.get("mapping") # {"a": 1, "b": 2, "c": 3}
A set reads back as a list. The engine has no set type (its Type enum has
LIST and MAP), so a HashSet is serialized as a list when the transaction
commits. The record you set it on still holds the HashSet until then, which is why
vertex.get("roles") returns a set inside the transaction. After the commit every
read (lookup_by_rid(), a query row's get(), to_list()) returns a list, in no
particular order. Duplicates are still removed. If your code needs a set, convert on
read: set(vertex.get("roles")).
Complete Example¶
import arcadedb_embedded as arcadedb
from datetime import date, datetime, timezone
from decimal import Decimal
# Create database
db = arcadedb.create_database("./type_demo")
# Create schema (applies immediately)
db.command("sql", "CREATE VERTEX TYPE Product")
# Test all type conversions
with db.transaction():
product = db.new_vertex("Product")
# Primitives
product.set("productId", 12345) # int
product.set("name", "Laptop") # str
product.set("inStock", True) # bool
product.set("price", 999.99) # float
product.set("tax", Decimal("50.00")) # Decimal
# Date/time
product.set("createdAt", datetime.now(timezone.utc)) # datetime
product.set("releaseDate", date(2024, 1, 15)) # date
# Collections
product.set("tags", ["electronics", "laptop"]) # list
product.set("categories", {"computers", "tech"}) # set, a list after the commit
# Nested structures
product.set("specs", {
"cpu": "Intel i7",
"ram": "16GB",
"storage": ["512GB SSD", "1TB HDD"]
}) # dict
# Binary
product.set("thumbnail", b"PNG\x89...") # bytes
product.save()
# Read back and verify types
results = list(db.query("sql", "SELECT FROM Product"))
product = results[0]
print(f"Product ID: {product.get('productId')} ({type(product.get('productId')).__name__})")
print(f"Name: {product.get('name')} ({type(product.get('name')).__name__})")
print(f"In Stock: {product.get('inStock')} ({type(product.get('inStock')).__name__})")
print(f"Price: {product.get('price')} ({type(product.get('price')).__name__})")
print(f"Tax: {product.get('tax')} ({type(product.get('tax')).__name__})")
print(f"Created At: {product.get('createdAt')} ({type(product.get('createdAt')).__name__})")
print(f"Tags: {product.get('tags')} ({type(product.get('tags')).__name__})")
db.close()
Output:
Product ID: 12345 (int)
Name: Laptop (str)
In Stock: True (bool)
Price: 999.99 (float)
Tax: 50.00 (Decimal)
Created At: 2024-01-15 10:30:45.123000 (datetime)
Tags: ['electronics', 'laptop'] (list)
Manual Conversion¶
Most of the time, conversions happen automatically. But you can manually convert when needed:
from arcadedb_embedded.type_conversion import convert_python_to_java, convert_java_to_python
# Manual Python → Java
python_data = {"name": "Alice", "age": 30}
java_map = convert_python_to_java(python_data)
# Use Java object directly (requires an active transaction)
with db.transaction():
java_record = db.new_vertex("User").get_java_document()
java_record.set("profile", java_map)
# Manual Java → Python
java_property = java_record.get("profile")
python_dict = convert_java_to_python(java_property)
print(python_dict) # {"name": "Alice", "age": 30}
Best Practices¶
1. Use Native Python Types¶
# ✅ Good: Use Python types
vertex.set("tags", ["python", "java"])
vertex.set("count", 42)
vertex.set("timestamp", datetime.now(timezone.utc))
# ❌ Bad: Manually convert (unnecessary)
from arcadedb_embedded.type_conversion import convert_python_to_java
vertex.set("tags", convert_python_to_java(["python", "java"])) # Redundant
2. Use Decimal for Money¶
from decimal import Decimal
# ✅ Good: Decimal for precise values
vertex.set("price", Decimal("19.99"))
vertex.set("tax", Decimal("1.60"))
# ❌ Bad: Float for money (precision loss)
vertex.set("price", 19.99) # May lose precision
3. Consistent Collection Types¶
# ✅ Good: Consistent types
vertex.set("tags", ["tag1", "tag2", "tag3"]) # All strings
# ⚠️ Mixed types work but can be confusing
vertex.set("mixed", [1, "two", 3.0, True])
4. Handle None Values¶
# ✅ Good: Check for None
age = vertex.get("age")
if age is not None:
print(f"Age: {age}")
else:
print("Age not set")
# ❌ Bad: Assume value exists
print(f"Age: {vertex.get('age')}") # May be None
Type Conversion Limitations¶
1. Custom Python Classes¶
# ❌ Custom classes not supported
class MyClass:
def __init__(self, value):
self.value = value
vertex.set("custom", MyClass(42)) # ❌ Error
# ✅ Convert to dict first
vertex.set("custom", {"value": 42}) # ✅ Works
2. Complex Nested Structures¶
# ✅ Reasonable nesting works
vertex.set("data", {
"level1": {
"level2": {
"level3": [1, 2, 3]
}
}
})
# ⚠️ Very deep nesting may impact performance
3. Large Binary Data¶
# ✅ Small binary data
vertex.set("icon", b"PNG\x89...") # OK
# ⚠️ Large binary data (consider file storage)
with open("large_file.bin", "rb") as f:
data = f.read() # May be very large
vertex.set("file", data) # May impact memory
Troubleshooting¶
Type Mismatch Errors¶
# ❌ Error: Expected list, got dict
vertex.set("tags", {"tag1": 1}) # Wrong type
# ✅ Fix: Use correct type
vertex.set("tags", ["tag1"])
Precision Loss with Floats¶
# ⚠️ Float precision issues
price = 19.99
tax = 0.01
total = price + tax # May not be exactly 20.00
# ✅ Use Decimal
from decimal import Decimal
price = Decimal("19.99")
tax = Decimal("0.01")
total = price + tax # Exactly 20.00
Date/Time Timezone Issues¶
from datetime import datetime, timezone
# ⚠️ Naive local time: stored as if it were UTC, so off by the host's offset
now = datetime.now()
# ✅ Aware UTC time: stored as the real instant
now_utc = datetime.now(timezone.utc)
# ✅ A naive value you mean as a UTC wall clock reads back unchanged on any host
meeting = datetime(2026, 10, 1, 12, 34)
See Also¶
- Database API - Database operations
- Queries Guide - Query parameters and usage
- Example 01: Document Store - Basic type usage
- Python JPype Documentation - Python-Java bridge