Skip to content

Error handling

For the complete documentation index see: llms.txt

All documentation pages available in markdown.

The three execution modes

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

ModeJavaPythonBehaviorBest for
Defaultexecute()await … .execute()Single-key: throws immediately. Batch: errors embedded in streamSimple cases
In-stream errorsexecute(ErrorStrategy.IN_STREAM)await … .execute(on_error=ErrorStrategy.IN_STREAM)All errors embedded as RecordResult entries in the streamUniform handling of single + batch
Error callbackexecute(errorHandler)await … .execute(on_error=<callable>)Errors dispatched to callback, excluded from streamLogging, 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:

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
}

📖 API reference: DataSet.id(...) | Session.update(DataSet) | ChainableOperationBuilder.bin(...) | ChainableQueryBuilder.bin(...) | ChainableQueryBuilder.execute() | AerospikeException

Batch operations embed errors in the stream:

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.getKey().userKey + ": success");
} else {
System.out.println(result.getKey().userKey + ": " + result.getMessage());
}
});

📖 API reference: DataSet.id(...) | ChainableQueryBuilder.bin(...) | BinBuilder.add(...) | ChainableQueryBuilder.execute() | RecordStream.forEach(...) | RecordResult.isOk()

Mode 2: ErrorStrategy.IN_STREAM

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

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.getResultCode() + " - " + result.getMessage());
}
});

📖 API reference: DataSet.id(...) | Session.update(DataSet) | ChainableOperationBuilder.bin(...) | ChainableQueryBuilder.bin(...) | ChainableQueryBuilder.execute() | RecordStream.forEach(...) | RecordResult.isOk() | ErrorStrategy | ErrorStrategy.IN_STREAM

Mode 3: ErrorHandler callback

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

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.getKey().userKey + ": OK");
});

📖 API reference: DataSet.id(...) | ChainableQueryBuilder.bin(...) | ChainableQueryBuilder.execute() | RecordStream.forEach(...)

Inspecting RecordResult

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

JavaPythonReturnsThrows?
isOk()is_ok (attribute)true if result_code == OKNo
resultCode()result_code (attribute)Integer result codeNo
message()(use exception / result_code)Human-readable error message (or null/None)No
orThrow()or_raise()self if OK, throws otherwiseYes
recordOrThrow()record_or_raise()The Record if OK, throws otherwiseYes
recordOrNull()record (attribute, may be None)The Record or null/None — does not throwNo
asBoolean()as_bool()true if OK, false if KEY_NOT_FOUND, throws otherwiseConditionally
key()key (attribute; user value via key.value)The Key for this operationNo
index()index (attribute)Position in the batch (0-based)No
inDoubt()in_doubt (attribute)Whether the write may have succeeded on the serverNo
exception()exception (attribute)The AerospikeException / AerospikeError if available, or NoneNo

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].

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.getKey().userKey + " → " + failure.getMessage())
);

📖 API reference: DataSet.id(...) | ChainableQueryBuilder.bin(...) | BinBuilder.add(...) | ChainableQueryBuilder.execute() | RecordStream.forEach(...) | ErrorStrategy | ErrorStrategy.IN_STREAM

Existence checks

The asBoolean() / as_bool() method translates as follows:

  • OK → true
  • KEY_NOT_FOUND → false
  • any other error → throws:
boolean exists = session.exists(users.id("user-1"))
.execute()
.getFirstBoolean()
.orElse(false);

📖 API reference: DataSet.id(...) | Session.exists(Key) | ChainableQueryBuilder.execute()

Decision guide

ScenarioRecommended mode
Simple single-key read/writeexecute() + try/catch
Batch where all failures are fatalexecute() + iterate and check isOk() / is_ok
Batch where you need consistent handlingexecute(ErrorStrategy.IN_STREAM) (Java) / execute(on_error=ErrorStrategy.IN_STREAM) (Python)
Batch where failures should be logged but not blockexecute(errorHandler) (Java) / execute(on_error=<callable>) (Python)
Need to collect all failures for retryIN_STREAM mode + .failures()

Malformed AEL text

AEL strings must conform to the AEL reference grammar, but neither SDK parses or validates AEL text locally. .where(...), selectFrom(...) / select_from(...), and the write-side *From(...) builders send the AEL string to the server as-is; the server parses and compiles it as part of each request. There is no local syntax check before sending, and no dedicated exception type for an AEL syntax error — a malformed expression comes back as an ordinary AerospikeException (Java) / AerospikeError (Python) from that request, typically carrying ResultCode.PARAMETER_ERROR.

try {
// Single-quoted strings and '==' are required; '=' is a parse error
session.query(users).where("$.name = 'Alice'").execute();
} catch (AerospikeException e) {
if (e.getResultCode() == ResultCode.PARAMETER_ERROR) {
// Malformed AEL syntax, or another rejected argument
}
}

📖 API reference: AerospikeException | AerospikeException.getResultCode() | ResultCode | Cluster.supportsAel()

PARAMETER_ERROR covers any rejected argument, not only AEL syntax, and the server does not always send a specific reason unless asked. Set errorDetailVerbosity / error_detail_verbosity to MESSAGE on the session’s Behavior to have the server’s explanation returned with the error. For parse coordinates, build traces, and filter-decision explainers specific to AEL text, see Debug AEL expressions. To catch AEL mistakes before they reach production, test the expression against Aerospike Voyager or a live server — there is no local syntax check to catch them earlier.

On a cluster that doesn’t support server-side AEL compilation, a string .where() call raises the same exception type instead, carrying ResultCode.OP_NOT_APPLICABLE — check Cluster.supportsAel() (Java) / await cluster.supports_ael() (Python) up front, or fall back to the programmatic expression builder, to avoid depending on string AEL there.

Next steps

Query records

Build queries and AEL filters that pair with these execution modes.

Query records →