Skip to content

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.time is not converted)
  • Decimal Precision: High-precision decimal numbers
  • Binary Data: bytes and bytearray are stored as Java byte[] (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, and datetime as below from 26.10.1; before, a Decimal reached the engine as a Double and kept only about 16 significant digits, and a date or datetime was 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:

  • datetime is converted to a LocalDateTime with its microseconds. The engine stores DATETIME as a UTC wall clock, whatever the host's or the database's time zone, and reads it back as a naive datetime. 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:00 is stored and read back as 12:34.
  • For the current time, store datetime.now(timezone.utc). A naive datetime.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 datetime crossed as a java.util.Date, which read a naive value as local time and kept milliseconds, so DATETIME_MICROS and DATETIME_NANOS lost the rest and a lookup by the same value found nothing.
  • date is converted to a LocalDate. If the Java types are unavailable it is combined with time.min and converted as a datetime.
  • Collection elements, set members, and map keys/values are converted recursively.
  • A list or tuple whose elements are all int, float, str, bool, or None crosses into the JVM as one Object[] rather than one call per element, with the same element types (Long, Double, String, Boolean, null). For a numeric vector a NumPy array through to_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, and OffsetDateTime are returned as timezone-aware datetime objects 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