Class converters
The Java SDK includes a built-in class conversion system that automatically maps between Java classes and SurrealDB values. When you pass a Class<T> to SDK methods, the converter serializes Java objects into SurrealDB-compatible data on the way in and deserializes SurrealDB responses back into typed Java objects on the way out. This removes the need to manually extract fields from raw Value objects and gives you compile-time type safety across your data layer.
How conversion works
Class conversion happens in two directions:
Serialization — When you pass a Java object to a method like .create(), .insert(), or .update(), the SDK reads the object’s public fields and converts them into a SurrealDB object. Field names become object keys, and field values are converted to the corresponding SurrealDB types.
Deserialization — When you call a typed method like db.select(Person.class, ...) or use Value.get(Person.class), the SDK creates a new instance of your class and populates its public fields from the SurrealDB object, matching by field name.
public class Person {
public RecordId id;
public String name;
public int age;
public Person() {}
}
Person person = new Person();
person.name = "Tobie";
person.age = 33;
db.create(new RecordId("person", "tobie"), person);
Optional<Person> result = db.select(Person.class, new RecordId("person", "tobie"));
POJO requirements
For a Java class to work with the converter, it must satisfy two rules:
- Public no-argument constructor — The SDK needs to instantiate the class during deserialization.
- Public fields — Fields are matched by name to SurrealDB object keys. Private fields, getters, and setters are not used by the converter.
public class Product {
public RecordId id;
public String name;
public double price;
public boolean active;
public Product() {}
}
Fields that exist in the Java class but not in the SurrealDB object are left at their Java default value (null for objects, 0 for numbers, false for booleans). Fields in the SurrealDB object that have no matching Java field are silently ignored.
Field type mapping
POJO fields are mapped to SurrealDB types based on their declared Java type:
| Java Field Type | SurrealDB Type | Notes |
|---|
String | string | |
long / Long | int | |
int / Integer | int | Narrowed from SurrealDB’s 64-bit integer |
double / Double | float | |
float / Float | float | Narrowed from SurrealDB’s 64-bit float |
boolean / Boolean | bool | |
BigDecimal | decimal | java.math |
UUID | uuid | java.util |
byte[] | bytes | |
ZonedDateTime | datetime | java.time |
Duration | duration | java.time |
RecordId | record | SDK class |
Geometry | geometry | SDK class |
FileRef | file | SDK class |
See Value types for the complete type mapping reference.
Nested objects
When a POJO field is itself a class with public fields and a no-argument constructor, the converter recurses into it. This lets you model nested SurrealDB objects with nested Java classes.
public class Address {
public String street;
public String city;
public String country;
public Address() {}
}
public class Person {
public RecordId id;
public String name;
public Address address;
public Person() {}
}
Person person = new Person();
person.name = "Tobie";
person.address = new Address();
person.address.street = "123 Main St";
person.address.city = "London";
person.address.country = "UK";
db.create(new RecordId("person", "tobie"), person);
The resulting SurrealDB record contains a nested object:
{
id: person:tobie,
name: "Tobie",
address: {
street: "123 Main St",
city: "London",
country: "UK"
}
}
Temporal types
SurrealDB datetime fields map to java.time.ZonedDateTime and duration fields map to java.time.Duration in your POJOs.
import java.time.ZonedDateTime;
import java.time.Duration;
public class Task {
public RecordId id;
public String title;
public ZonedDateTime createdAt;
public Duration timeout;
public Task() {}
}
Optional<Task> task = db.select(Task.class, new RecordId("task", "build"));
ZonedDateTime when = task.get().createdAt;
Duration howLong = task.get().timeout;
Relation classes
Graph edges created with .relate() or .insertRelation() use specialized base classes that include the standard relation fields (id, in, out).
Using Relation
Extend Relation when reading or creating edges with .relate(). The base class provides id, in, and out as RecordId fields.
public class Likes extends Relation {
public String createdAt;
}
Likes like = db.relate(
Likes.class,
new RecordId("person", "alice"),
"likes",
new RecordId("post", "post1")
);
Using InsertRelation
Extend InsertRelation when inserting edges with .insertRelation(). The base class provides id as an Id and in / out as RecordId fields.
public class Follows extends InsertRelation {
public ZonedDateTime since;
public Follows() {}
}
Follows follow = new Follows();
follow.in = new RecordId("person", "alice");
follow.out = new RecordId("person", "bob");
follow.since = ZonedDateTime.now();
db.insertRelation(Follows.class, "follows", follow);
Where conversion is available
Typed conversion is available across most SDK methods. Any method that accepts a Class<T> parameter uses the converter:
| Method | Typed variant |
|---|
db.select(target) | db.select(Class, target) |
db.create(target, content) | db.create(Class, target, content) |
db.insert(target, content) | db.insert(Class, target, content) |
db.update(target, upType, content) | db.update(Class, target, upType, content) |
db.upsert(target, upType, content) | db.upsert(Class, target, upType, content) |
db.relate(from, table, to) | db.relate(Class, from, table, to) |
response.take(index) | response.take(Class, index) |
value.get(Class) | Direct POJO conversion from a Value |
array.iterator(Class) | Typed iteration over array elements |
Handling conversion errors
When the SDK cannot convert a value to the target class — for example, because a field type is incompatible or the class is missing a no-argument constructor — it throws a SerializationException. You can catch this specifically or handle it as part of the general SurrealException hierarchy.
try {
Optional<Person> person = db.select(Person.class, new RecordId("person", "tobie"));
} catch (SerializationException e) {
System.err.println("Conversion failed: " + e.getMessage());
}
See Error handling for more on the exception hierarchy.
Learn more