# Class converters

The Java SDK automatically converts between Java classes and SurrealDB values, letting you use POJOs for type-safe database operations.

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`](/docs/reference/java/api/values/value.md) objects and gives you compile-time type safety across your data layer.

## How conversion works

Class conversion happens in two directions:

- **Serialisation** - When you pass a Java object to a method like [`.create()`](/docs/reference/java/api/core/surreal.md#create), [`.insert()`](/docs/reference/java/api/core/surreal.md#insert), or [`.update()`](/docs/reference/java/api/core/surreal.md#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.

- **Deserialisation** - 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.

```java
public class Person {
    public RecordId id;
    public String name;
    public int age;

    public Person() {}
}

Person person = new Person();
person.name = "Tobie";
person.age = 33;

// Serialization: Person → SurrealDB object
db.create(new RecordId("person", "tobie"), person);

// Deserialization: SurrealDB object → 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:

1. **Public no-argument constructor** - The SDK needs to instantiate the class during deserialisation.
2. **Public fields** - Fields are matched by name to SurrealDB object keys. Private fields, getters, and setters are not used by the converter.

```java
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 and bound values are mapped to SurrealDB types based on their declared Java type:

| Java Field/Value 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`                                                                                                       |                                          |
| `Instant`                                               | [`datetime`](/docs/reference/query-language/language-primitives/data-types/datetimes.md)                         | `java.time`                              |
| `ZonedDateTime`                                         | [`datetime`](/docs/reference/query-language/language-primitives/data-types/datetimes.md)                         | `java.time`                              |
| `OffsetDateTime`                                        | [`datetime`](/docs/reference/query-language/language-primitives/data-types/datetimes.md)                         | `java.time`                              |
| `LocalDateTime`                                         | [`datetime`](/docs/reference/query-language/language-primitives/data-types/datetimes.md)                         | `java.time`                              |
| `java.util.Date`                                        | [`datetime`](/docs/reference/query-language/language-primitives/data-types/datetimes.md)                         | `java.time`                              |
| `Duration`                                              | [`duration`](/docs/reference/query-language/language-primitives/data-types/datetimes.md#durations-and-datetimes) | `java.time`                              |
| [`RecordId`](/docs/reference/java/api/values/record-id.md) | [`record`](/docs/reference/query-language/language-primitives/data-types/record-ids.md)                          | SDK class                                |
| [`Geometry`](/docs/reference/java/api/values/geometry.md)  | [`geometry`](/docs/reference/query-language/language-primitives/data-types/geometries.md)                        | SDK class                                |
| [`FileRef`](/docs/reference/java/api/values/file-ref.md)   | [`file`](/docs/reference/query-language/language-primitives/data-types/files.md)                                 | SDK class                                |

See [Value types](/docs/reference/java/concepts/value-types.md) 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.

```java
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:

```surql
{
    id: person:tobie,
    name: "Tobie",
    address: {
        street: "123 Main St",
        city: "London",
        country: "UK"
    }
}
```

## Temporal types

SurrealDB [`datetime`](/docs/reference/query-language/language-primitives/data-types/datetimes.md) values represent an absolute point in time. When serialising Java objects, the SDK accepts `Instant`, `ZonedDateTime`, `OffsetDateTime`, `LocalDateTime`, and `java.util.Date` fields. When deserialising SurrealDB `datetime` values back into POJOs, the same types are supported; `ZonedDateTime` is the usual choice when you need the stored instant with its offset.

> Note: `LocalDateTime` does not contain a time zone or offset. The SDK interprets `LocalDateTime` values as UTC during serialisation. If the value represents a user's local wall-clock time in a specific region, prefer `ZonedDateTime` or `OffsetDateTime` so the intended instant is explicit.

[`duration`](/docs/reference/query-language/language-primitives/data-types/datetimes.md#durations-and-datetimes) fields map to `java.time.Duration` in your POJOs.

```java
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.Duration;
import java.util.Date;

public class TaskWrite {
    public RecordId id;
    public String title;
    public Instant scheduledAt;
    public OffsetDateTime reviewedAt;
    public LocalDateTime localDeadline;
    public Date legacyCreatedAt;
    public Duration timeout;

    public TaskWrite() {}
}

TaskWrite task = new TaskWrite();
task.scheduledAt = Instant.parse("2026-06-06T08:00:00Z");
task.reviewedAt = OffsetDateTime.parse("2026-06-06T10:00:00+02:00");
task.localDeadline = LocalDateTime.parse("2026-06-06T10:00:00");
task.legacyCreatedAt = Date.from(task.scheduledAt);
task.timeout = Duration.ofMinutes(30);

db.create(new RecordId("task", "build"), task);
```

Use `ZonedDateTime` fields when you want the full stored instant (recommended for most read paths). `Instant`, `OffsetDateTime`, and `LocalDateTime` also work on deserialisation:

```java
import java.time.ZonedDateTime;
import java.time.Duration;

public class TaskRead {
    public RecordId id;
    public String title;
    public ZonedDateTime scheduledAt;
    public ZonedDateTime reviewedAt;
    public ZonedDateTime localDeadline;
    public ZonedDateTime legacyCreatedAt;
    public Duration timeout;

    public TaskRead() {}
}

Optional<TaskRead> task = db.select(TaskRead.class,
    new RecordId("task", "build"));
ZonedDateTime when = task.get().scheduledAt;
Duration howLong = task.get().timeout;
```

## Relation classes

Graph edges created with [`.relate()`](/docs/reference/java/api/core/surreal.md#relate) or [`.insertRelation()`](/docs/reference/java/api/core/surreal.md#insert-relation) use specialized base classes that include the standard relation fields (`id`, `in`, `out`).

### Using `Relation`

Extend [`Relation`](/docs/reference/java/api/types.md#relation) when reading or creating edges with `.relate()`. The base class provides `id`, `in`, and `out` as `RecordId` fields.

```java
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`](/docs/reference/java/api/types.md#insert-relation) when inserting edges with `.insertRelation()`. The base class provides `id` as an [`Id`](/docs/reference/java/api/values/record-id.md#id) and `in` / `out` as `RecordId` fields.

```java
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:

<table>
  <thead>
    <tr>
      <th scope="col">Method</th>
      <th scope="col">Typed variant</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row" data-label="Method">
        <a href="/docs/reference/java/api/core/surreal.md#select">
          <code>db.select(target)</code>
        </a>
      </td>
      <td scope="row" data-label="Typed variant">
        <code>db.select(Class, target)</code>
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Method">
        <a href="/docs/reference/java/api/core/surreal.md#create">
          <code>db.create(target, content)</code>
        </a>
      </td>
      <td scope="row" data-label="Typed variant">
        <code>db.create(Class, target, content)</code>
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Method">
        <a href="/docs/reference/java/api/core/surreal.md#insert">
          <code>db.insert(target, content)</code>
        </a>
      </td>
      <td scope="row" data-label="Typed variant">
        <code>db.insert(Class, target, content)</code>
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Method">
        <a href="/docs/reference/java/api/core/surreal.md#update">
          <code>db.update(target, upType, content)</code>
        </a>
      </td>
      <td scope="row" data-label="Typed variant">
        <code>db.update(Class, target, upType, content)</code>
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Method">
        <a href="/docs/reference/java/api/core/surreal.md#upsert">
          <code>db.upsert(target, upType, content)</code>
        </a>
      </td>
      <td scope="row" data-label="Typed variant">
        <code>db.upsert(Class, target, upType, content)</code>
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Method">
        <a href="/docs/reference/java/api/core/surreal.md#relate">
          <code>db.relate(from, table, to)</code>
        </a>
      </td>
      <td scope="row" data-label="Typed variant">
        <code>db.relate(Class, from, table, to)</code>
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Method">
        <a href="/docs/reference/java/api/core/response.md#take">
          <code>response.take(index)</code>
        </a>
      </td>
      <td scope="row" data-label="Typed variant">
        <code>response.take(Class, index)</code>
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Method">
        <a href="/docs/reference/java/api/values/value.md#get">
          <code>value.get(Class)</code>
        </a>
      </td>
      <td scope="row" data-label="Typed variant">
        Direct POJO conversion from a <code>Value</code>
      </td>
    </tr>
    <tr>
      <td scope="row" data-label="Method">
        <a href="/docs/reference/java/api/values/value.md#array-typed-iterator">
          <code>array.iterator(Class)</code>
        </a>
      </td>
      <td scope="row" data-label="Typed variant">
        Typed iteration over array elements
      </td>
    </tr>
  </tbody>
</table>

## 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`](/docs/reference/java/api/errors.md#serialization-exception). You can catch this specifically or handle it as part of the general [`SurrealException`](/docs/reference/java/api/errors.md) hierarchy.

```java
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](/docs/reference/java/concepts/error-handling.md) for more on the exception hierarchy.

## Learn more

- [Value types](/docs/reference/java/concepts/value-types.md) for the complete SurrealDB-to-Java type mapping
- [Data manipulation](/docs/reference/java/concepts/data-manipulation.md) for using converted types with CRUD operations
- [Value API reference](/docs/reference/java/api/values/value.md) for the `Value.get(Class)` method
- [Response API reference](/docs/reference/java/api/core/response.md) for typed response extraction
- [Java Types reference](/docs/reference/java/api/types.md) for `Relation`, `InsertRelation`, and other SDK types
- [Error handling](/docs/reference/java/concepts/error-handling.md) for serialisation error details
