# Error handling

## The three execution modes

Every builder’s `.execute()` method supports three modes that control how per-record errors are surfaced:

| Mode | Java | Python | Behavior | Best for |
| --- | --- | --- | --- | --- |
| Default | `execute()` | `await … .execute()` | Single-key: **throws** immediately. Batch: errors **embedded in stream** | Simple cases |
| In-stream errors | `execute(ErrorStrategy.IN_STREAM)` | `await … .execute(on_error=ErrorStrategy.IN_STREAM)` | All errors embedded as `RecordResult` entries in the stream | Uniform handling of single + batch |
| Error callback | `execute(errorHandler)` | `await … .execute(on_error=<callable>)` | Errors dispatched to callback, **excluded** from stream | Logging, fire-and-forget |

Java exposes the same three modes via overloads of `execute()` and `executeAsync()`. The Python SDK uses `await … .execute(on_error=…)` as the entry point for the async surface (`aerospike_sdk.aio.*`); the synchronous surface (`aerospike_sdk.sync.*`) wraps the same builders behind blocking `.execute(on_error=…)` calls. Either way there is no separate `executeAsync()` — choosing async vs sync is a matter of which session/client class you import.

## Mode 1: Default (`execute()`)

**Single-key operations** throw on failure:

-   [Java](#tab-panel-2968)
-   [Python](#tab-panel-2969)

```java
try {

    session.update(users.id("no-such-key"))

        .bin("name").setTo("Alice")

        .execute();

} catch (AerospikeException e) {

    System.out.println("Failed: " + e.getResultCode()); // 2 = KEY_NOT_FOUND_ERROR

}
```

```python
from aerospike_sdk import AerospikeError

try:

    await (

        session.update(users.id("no-such-key"))

        .bin("name").set_to("Alice")

        .execute()

    )

except AerospikeError as e:

    print(f"Failed: {e.result_code}")  # 2 = KEY_NOT_FOUND_ERROR
```

**Batch operations** embed errors in the stream — they do not throw:

-   [Java](#tab-panel-2970)
-   [Python](#tab-panel-2971)

```java
RecordStream stream = session

    .update(users.id("exists"))

        .bin("count").add(1)

    .update(users.id("no-such-key"))

        .bin("count").add(1)

    .execute();

stream.forEach(result -> {

    if (result.isOk()) {

        System.out.println(result.key().userKey + ": success");

    } else {

        System.out.println(result.key().userKey + ": " + result.message());

    }

});
```

```python
stream = await (

    session.update(users.id("exists"))

    .bin("count").add(1)

    .update(users.id("no-such-key"))

    .bin("count").add(1)

    .execute()

)

try:

    async for result in stream:

        if result.is_ok:

            print(f"{result.key.value}: success")

        else:

            print(f"{result.key.value}: {result.exception or str(result.result_code)}")

finally:

    stream.close()
```

## Mode 2: `ErrorStrategy.IN_STREAM`

Forces all errors (including single-key) into the stream. Useful when you want uniform handling regardless of key count:

-   [Java](#tab-panel-2972)
-   [Python](#tab-panel-2973)

```java
RecordStream stream = session.update(users.id("no-such-key"))

    .bin("name").setTo("Alice")

    .execute(ErrorStrategy.IN_STREAM);

stream.forEach(result -> {

    if (!result.isOk()) {

        System.out.println("Error: " + result.resultCode() + " - " + result.message());

    }

});
```

```python
from aerospike_sdk import ErrorStrategy

stream = await (

    session.update(users.id("no-such-key"))

    .bin("name").set_to("Alice")

    .execute(on_error=ErrorStrategy.IN_STREAM)

)

try:

    async for result in stream:

        if not result.is_ok:

            print(f"Error: {result.result_code} - {result.exception or str(result.result_code)}")

finally:

    stream.close()
```

## Mode 3: `ErrorHandler` callback

Errors are dispatched to the handler and **excluded** from the stream. The stream contains only successful results:

-   [Java](#tab-panel-2974)
-   [Python](#tab-panel-2975)

```java
RecordStream stream = session

    .upsert(users.id("u1")).bin("name").setTo("Alice")

    .upsert(users.id("u2")).bin("name").setTo("Bob")

    .execute((key, index, exception) ->

        System.err.println("Failed key " + key.userKey +

            " at index " + index + ": " + exception.getMessage())

    );

// stream contains only successful results

stream.forEach(result -> {

    System.out.println(result.key().userKey + ": OK");

});
```

```python
def handle_error(key, index, ex):

    print(f"Failed key {key.value} at index {index}: {ex}")

stream = await (

    session.upsert(users.id("u1")).bin("name").set_to("Alice")

    .upsert(users.id("u2")).bin("name").set_to("Bob")

    .execute(on_error=handle_error)

)

# stream contains only successful results

try:

    async for result in stream:

        print(f"{result.key.value}: OK")

finally:

    stream.close()
```

## Inspecting `RecordResult`

Every result in the stream is a `RecordResult`. Key members:

| Java | Python | Returns | Throws? |
| --- | --- | --- | --- |
| `isOk()` | `is_ok` (attribute) | `true` if `result_code == OK` | No |
| `resultCode()` | `result_code` (attribute) | Integer result code | No |
| `message()` | (use `exception` / `result_code`) | Human-readable error message (or `null`/`None`) | No |
| `orThrow()` | `or_raise()` | `self` if OK, throws otherwise | Yes |
| `recordOrThrow()` | `record_or_raise()` | The `Record` if OK, throws otherwise | Yes |
| `recordOrNull()` | `record` (attribute, may be `None`) | The `Record` or `null`/`None` — does not throw | No |
| `asBoolean()` | `as_bool()` | `true` if OK, `false` if KEY\_NOT\_FOUND, throws otherwise | Conditionally |
| `key()` | `key` (attribute; user value via `key.value`) | The `Key` for this operation | No |
| `index()` | `index` (attribute) | Position in the batch (0-based) | No |
| `inDoubt()` | `in_doubt` (attribute) | Whether the write may have succeeded on the server | No |
| `exception()` | `exception` (attribute) | The `AerospikeException` / `AerospikeError` if available, or `None` | No |

## Using `failures()` to isolate errors

`RecordStream.failures()` consumes the entire source stream and returns only the failed results. In Java this is a new `RecordStream`; in Python it is an awaitable that resolves to `list[RecordResult]`.

-   [Java](#tab-panel-2976)
-   [Python](#tab-panel-2977)

```java
RecordStream stream = session

    .update(users.id("u1")).bin("count").add(1)

    .update(users.id("u2")).bin("count").add(1)

    .update(users.id("missing")).bin("count").add(1)

    .execute(ErrorStrategy.IN_STREAM);

RecordStream errors = stream.failures();

errors.forEach(failure ->

    System.err.println("Failed: " + failure.key().userKey + " → " + failure.message())

);
```

```python
from aerospike_sdk import ErrorStrategy

stream = await (

    session.update(users.id("u1")).bin("count").add(1)

    .update(users.id("u2")).bin("count").add(1)

    .update(users.id("missing")).bin("count").add(1)

    .execute(on_error=ErrorStrategy.IN_STREAM)

)

errors = await stream.failures()

for f in errors:

    print(f"Failed: {f.key.value} → {f.result_code}")

stream.close()
```

> **Note:** `failures()` is a terminal-style helper — it drains and exhausts the source stream and returns the failures fully in memory (a new `RecordStream` in Java; a `list[RecordResult]` in Python). In Python it does not call `stream.close()` for you; if you need to release client/server resources promptly, call `stream.close()` (or wrap the work in `try` / `finally`) after collecting the failures.

## Existence checks

The `asBoolean()` / `as_bool()` method translates `OK → true`, `KEY_NOT_FOUND → false`, any other error → throws:

-   [Java](#tab-panel-2978)
-   [Python](#tab-panel-2979)

```java
boolean exists = session.exists(users.id("user-1"))

    .execute()

    .getFirstBoolean()

    .orElse(false);
```

```python
stream = await session.exists(users.id("user-1")).execute()

try:

    first = await stream.first()

    exists = first.as_bool() if first else False

finally:

    stream.close()
```

## Decision guide

| Scenario | Recommended mode |
| --- | --- |
| Simple single-key read/write | `execute()` + try/catch |
| Batch where all failures are fatal | `execute()` + iterate and check `isOk()` / `is_ok` |
| Batch where you need consistent handling | `execute(ErrorStrategy.IN_STREAM)` (Java) / `execute(on_error=ErrorStrategy.IN_STREAM)` (Python) |
| Batch where failures should be logged but not block | `execute(errorHandler)` (Java) / `execute(on_error=<callable>)` (Python) |
| Need to collect all failures for retry | `IN_STREAM` mode + `.failures()` |