Skip to content

Author AEL filter and operation expressions

For the complete documentation index see: llms.txt

All documentation pages available in markdown.

Task guide: pass Aerospike Expression Language (AEL) text to filter records, project computed read results, and persist computed write results on single-key, batch, and set-query commands in the Java and Python Developer SDKs.

Applies to

  • Aerospike Developer SDKs (Java 21+ and Python 3.10+)
  • Aerospike Database 8.2.0.0 or later, for both filter APIs (.where(...)) and operation-expression APIs (selectFrom, upsertFrom, insertFrom, updateFrom) — AEL is compiled and parsed on the server, so there is no lower version tier for either

AEL text is parsed and compiled entirely on the server — there is no client-side AEL parser in the shipping SDKs. (An ANTLR grammar, Condition.g4, exists in both SDK repos, but it is not part of the branches these SDKs ship from; it does not run for .where(), selectFrom, upsertFrom, insertFrom, or updateFrom.) That server-side AEL compiler is what the 8.2.0 requirement gates.

Audience

Application developers using the Java or Python Developer SDK (Intermediate).

Prerequisites

Outcome

You can pass AEL strings to .where(...), selectFrom(...) / select_from(...), and write-side upsertFrom / insertFrom / updateFrom builders on supported commands.

The Developer SDK accepts AEL as plain text on fluent builders. You author the expression string; the SDK sends it to Aerospike Database as-is, and the server parses, compiles, and evaluates it. For grammar details (paths, operators, let, when, and CDT selectors), see AEL reference. When a string fails to compile or a filter silently excludes records, see Debug AEL expressions.

Three roles for AEL text

RoleSDK entry pointExpression must evaluate toTypical use
Filter.where("...")BooleanSkip records that do not match
Read / projection.bin("out").selectFrom("...") / .select_from(...)Any valueReturn a computed virtual bin without storing it
Write.bin("out").upsertFrom("...") / .upsert_from(...) (and insertFrom / updateFrom)Any valueCompute on the server and persist to a bin

Filter expressions decide whether an operation runs on a record. Operation expressions compute a value, which is either returned to the client (read) or written into a bin (write).

Dynamic values in AEL text

Both languages let you pass extra arguments to .where(...) and the operation-expression builders alongside the AEL string: .where("$.age >= %d", age) in Java, .where("$.age >= %d", age) in Python. These substitute using printf/String.format-style specifiers (%s, %d, and so on) on the client, before the text is sent. This substitution does not quote or escape anything — it is no safer than building the string yourself with concatenation or an f-string. A value that contains a stray quote can still break out of a string literal and change the query, filter, or write semantics sent to the server.

For Java only, PreparedAel is a safer alternative when a value might not be fully trusted. It uses zero-based ?0, ?1, … placeholders and formats each bound value into a quoted, escaped AEL literal for you (strings are quoted and checked for embedded quote characters; numbers, lists, and maps are formatted directly) — so the same untrusted-input risk does not apply. See Reuse a filter template across calls below. Python has no equivalent safe-binding type; validate or escape untrusted values by hand before interpolating them into Python AEL text.

AEL text vs Exp builder expressions

For filter predicates, the Developer SDK accepts readable AEL strings or programmatic Exp.* builders. This guide shows AEL text; see AEL overview: AEL vs expressions for side-by-side filter examples. Canonical grammar is in AEL reference.

For operation expressions (read projection using selectFrom / select_from, write using upsertFrom / insertFrom / updateFrom), author AEL text on the Developer SDK builders in the following sections. Write expressions apply to single-key and batch operate chains, not to query projection (read-only). Legacy per-client pages document Exp / Operation builder composition in the expressions reference guide; those builders remain for advanced programmatic cases.

Filter expressions with .where()

Pass a Boolean AEL string to .where(...) on any command that supports record filters.

Filter a set query

DataSet orders = DataSet.of("test", "orders");
RecordStream stream = session.query(orders)
.where("$.status == 'open' and ($.price * $.qty) + $.shipping > 100.0")
.execute();
stream.forEach(result -> {
Record row = result.recordOrThrow();
// Process matching rows
});
stream.close();

📖 API reference: DataSet.of(...) | Session.query(DataSet) | ChainableQueryBuilder.where(...) | ChainableQueryBuilder.execute() | RecordStream.forEach(...) | RecordResult.recordOrThrow() | RecordStream.close()

Filter batch key reads

DataSet users = DataSet.of("test", "users");
RecordStream stream = session.query(users.ids("u1", "u2", "u3"))
.where("$.active == true")
.execute();
stream.forEach(result -> {
if (result.isOk()) {
Record row = result.recordOrThrow();
// Process row
}
});
stream.close();

📖 API reference: DataSet.ids(...) | Session.query(List) | ChainableQueryBuilder.where(...) | ChainableQueryBuilder.execute() | RecordResult.isOk() | RecordResult.recordOrThrow() | RecordStream.close()

Filter conditional writes on one key

DataSet tasks = DataSet.of("test", "tasks");
session.update(tasks.id("task-1"))
.bin("status").setTo("COMPLETED")
.where("$.status == 'PENDING' and $.terminated == false")
.execute();

📖 API reference: DataSet.id(...) | Session.update(DataSet) | ChainableOperationBuilder.bin(...) | BinBuilder.setTo(...) | ChainableOperationBuilder.where(...) | ChainableOperationBuilder.execute()

When the filter is false, the server skips the mutation for that key. Combine per-operation filters with .defaultWhere(...) / .default_where(...) on mixed batch chains — see Batch operate with operation expressions.

Reuse a filter template across calls

For Java, PreparedAel stores an AEL template with ?0, ?1, … placeholders once and safely substitutes different bound values on each call — each value is formatted into a quoted, escaped AEL literal, so this is the recommended pattern when a bound value might not be fully trusted:

DataSet users = DataSet.of("test", "users");
PreparedAel activeInDept = PreparedAel.prepare("$.active == true and $.department == ?0");
RecordStream engineering = session.query(users)
.where(activeInDept, "engineering")
.execute();
engineering.forEach(result -> {
Record row = result.recordOrThrow();
// Process matching rows
});
engineering.close();
RecordStream marketing = session.query(users)
.where(activeInDept, "marketing")
.execute();
marketing.forEach(result -> {
Record row = result.recordOrThrow();
// Process matching rows
});
marketing.close();

📖 API reference: DataSet.of(...) | PreparedAel.prepare(...) | Session.query(DataSet) | ChainableQueryBuilder.where(PreparedAel, ...) | ChainableQueryBuilder.execute() | RecordStream.forEach(...) | RecordResult.recordOrThrow() | RecordStream.close()

Read operation expressions with selectFrom

Use selectFrom / select_from to evaluate an AEL expression server-side and return the result under the bin name you pass to .bin(...). The output bin does not need to exist on the record beforehand.

Single-key read

DataSet products = DataSet.of("test", "products");
Record rec = session.query(products.id("sku-42"))
.bin("lineTotal").selectFrom("$.price * $.qty.toFloat()")
.execute()
.getFirstRecord();
// price and qty are FLOAT bins in typical order fixtures
double lineTotal = rec.getDouble("lineTotal");

📖 API reference: DataSet.id(...) | Session.query(Key) | ChainableQueryBuilder.bin(...) | QueryBinBuilder.selectFrom(...) | ChainableQueryBuilder.execute() | RecordStream.getFirstRecord() | Record.getDouble(...)

Batch key read with projection

DataSet products = DataSet.of("test", "products");
RecordStream stream = session.query(products.ids("sku-1", "sku-2"))
.bin("lineTotal").selectFrom("$.price * $.qty.toFloat()")
.execute();
stream.forEach(result -> {
if (result.isOk()) {
Record rec = result.recordOrThrow();
// rec.getDouble("lineTotal")
}
});
stream.close();

📖 API reference: DataSet.ids(...) | Session.query(List) | ChainableQueryBuilder.bin(...) | QueryBinBuilder.selectFrom(...) | ChainableQueryBuilder.execute() | RecordResult.isOk() | RecordStream.close()

Project during a filtered set query

Both Java and Python support read-side operation expressions (selectFrom / select_from) on filtered set queries, not just single-key and batch key reads.

DataSet users = DataSet.of("test", "users");
RecordStream stream = session.query(users)
.where("$.status == 'active'")
.bin("ageIn10Years").selectFrom("$.age + 10")
.execute();
stream.forEach(result -> {
Record row = result.recordOrThrow();
// Process matching rows
});
stream.close();

📖 API reference: DataSet.of(...) | Session.query(DataSet) | ChainableQueryBuilder.where(...) | QueryBinBuilder.selectFrom(...) | ChainableQueryBuilder.execute() | RecordStream.close()

See Project reads with ops projection for platform differences and server version notes.

Tolerate evaluation failures on read

When an expression cannot evaluate (for example, a missing bin or divide-by-zero), the operation fails unless you opt out. In raw AEL, attach the :NO_FAIL postfix to path terminals (see Postfix flags). The Developer SDK exposes the same behavior through builder options:

A .where(...) filter expression that fails to evaluate behaves differently: there is no ignoreEvalFailure() / ignore_eval_failure=True option for filters. Instead, the record is excluded from the result set with no error, as if the filter had evaluated to false.

DataSet users = DataSet.of("test", "users");
RecordStream stream = session.query(users.id("user-1"))
.bin("ratio").selectFrom("$.numerator:INT / $.denominator", opt -> opt.ignoreEvalFailure())
.execute();
stream.close();

📖 API reference: DataSet.id(...) | Session.query(Key) | QueryBinBuilder.selectFrom(...) | ChainableQueryBuilder.execute() | RecordStream.close()

On read projection, ignoreEvalFailure() / ignore_eval_failure=True prevents the request from failing when the expression cannot evaluate. Java omits the record from results; Python returns None for the projected bin.

Write operation expressions

Write-side AEL computes a value on the server and stores it in the target bin. Choose the method by existence semantics:

MethodBehavior if bin existsBehavior if bin missing
upsertFrom / upsert_fromOverwritesCreates
insertFrom / insert_fromFails (BIN_EXISTS_ERROR)Creates
updateFrom / update_fromOverwritesFails (BIN_NOT_FOUND)

Single-record write

DataSet orders = DataSet.of("test", "orders");
session.upsert(orders.id("order-1"))
.bin("lineTotal").upsertFrom("$.price:INT * $.qty")
.bin("discount").insertFrom("$.coupon_value:INT")
.execute();

📖 API reference: DataSet.id(...) | Session.upsert(Key) | ChainableOperationBuilder.bin(...) | BinBuilder.upsertFrom(...) | BinBuilder.insertFrom(...) | ChainableOperationBuilder.execute()

Write options

DataSet orders = DataSet.of("test", "orders");
session.upsert(orders.id("order-1"))
.bin("discount").upsertFrom("$.coupon_value:INT", opt -> opt
.ignoreEvalFailure() // skip mutation for this key when expression cannot evaluate
.deleteIfNull()) // remove bin when expression returns null
.bin("bonus").insertFrom("$.base * 2", opt -> opt
.ignoreOpFailure()) // skip when bin already exists (insertFrom)
.execute();

📖 API reference: Session.upsert(Key) | BinBuilder.upsertFrom(...) | BinBuilder.insertFrom(...) | ChainableOperationBuilder.execute()

On writes, ignoreEvalFailure() / ignore_eval_failure=True skips the mutation for that key when the expression cannot evaluate (for example, a missing bin or divide-by-zero). Use ignoreOpFailure() / ignore_op_failure=True on insertFrom / insert_from to skip when the target bin already exists.

Combine write expressions with a filter so the mutation runs only when the record matches:

DataSet orders = DataSet.of("test", "orders");
session.upsert(orders.id("order-1"))
.bin("lineTotal").upsertFrom("$.price:INT * $.qty")
.where("$.status == 'open'")
.execute();

📖 API reference: Session.upsert(Key) | BinBuilder.upsertFrom(...) | ChainableOperationBuilder.where(...) | ChainableOperationBuilder.execute()

Batch operate with operation expressions

Chain multiple keys in one request. Each key can mix filters, read projections, and write expressions.

defaultWhere / default_where applies only to keys that don’t have their own .where(...) clause. In the example below, sku-1 has its own filter ($.on_hand:INT > $.available), so defaultWhere does not apply to it. Only sku-2, which has no per-key filter, is gated by $.active == true.

DataSet inventory = DataSet.of("test", "inventory");
session
.upsert(inventory.id("sku-1"))
.bin("reserved").upsertFrom("$.on_hand:INT - $.available")
.where("$.on_hand:INT > $.available")
.upsert(inventory.id("sku-2"))
.bin("reserved").upsertFrom("$.on_hand:INT - $.available")
.defaultWhere("$.active == true")
.execute();

📖 API reference: DataSet.id(...) | Session.upsert(Key) | BinBuilder.upsertFrom(...) | ChainableOperationBuilder.where(...) | ChainableOperationBuilder.defaultWhere(...) | ChainableOperationBuilder.execute()

Batch read with computed projection

DataSet inventory = DataSet.of("test", "inventory");
RecordStream stream = session.query(inventory.ids("sku-1", "sku-2"))
.bin("availablePct").selectFrom("($.available * 100) / $.on_hand")
.execute();
stream.close();

📖 API reference: DataSet.ids(...) | Session.query(List) | QueryBinBuilder.selectFrom(...) | ChainableQueryBuilder.execute() | RecordStream.close()

See Batch operations for mixed read/write/delete chains and partial-failure handling.

Verify your expressions

Aerospike Voyager is the recommended way to build and verify AEL text: author the expression against real data, see errors immediately, and copy the working AEL string directly into your Java or Python code.

To verify an expression from within application code:

  1. Start with a filter-only query (.where(...)) on a small test set to confirm Boolean logic before adding operation expressions.
  2. For read projections, read back the virtual bin and compare against an expected value computed in application code.
  3. For write expressions, query the persisted bin after execute() completes.
  4. If a filter matches zero rows unexpectedly, check Type consistency in let bindings (let bindings and mixed INT/FLOAT arithmetic can return zero matches with no error).

The server parses AEL text as part of each request; there is no local syntax check before sending. A malformed expression comes back as an AerospikeError from that request — typically ResultCode.PARAMETER_ERROR for a syntax error, or ResultCode.OP_NOT_APPLICABLE on a cluster that doesn’t support server-side AEL compilation. See Handle errors gracefully.

Troubleshoot

SymptomLikely causeFix
Filter returns zero rowsINT/FLOAT type mismatch in the expressionUse matching literal types (for example 100.0 for FLOAT bins) or .toFloat() / .toInt()
AerospikeError with ResultCode.PARAMETER_ERRORMalformed AEL syntax; the server parses AEL text as part of the request and returns the errorUse == for equality; use Aerospike Voyager to quickly test and validate AEL syntax, then paste it into your code
Write expression rejected in a query projection chainWrite-side methods (upsertFrom, insertFrom, updateFrom) aren’t valid on query commandsUse session.upsert(...), batch upsert, or update builders to persist computed values
Write expression skipped for a keyFilter evaluated false for that recordConfirm .where(...) logic; check per-key filters vs .defaultWhere(...) on batch chains

API reference summary

MethodDescriptionAPI reference
.where(...)Attach a Boolean AEL filterJava | Python
.selectFrom(...) / .select_from(...)Read-side operation expression (projection)Java | Python
.upsertFrom(...) / .upsert_from(...)Write expression; create or overwrite binJava | Python
.insertFrom(...) / .insert_from(...)Write expression; create bin onlyJava | Python
.updateFrom(...) / .update_from(...)Write expression; update existing bin onlyJava | Python
.defaultWhere(...) / .default_where(...)Default filter for mixed batch chainsJava | Python
PreparedAel (Java only)Reusable AEL template with safe ?0, ?1, … placeholder bindingJava

Next steps

AEL reference

Canonical grammar, CDT path patterns, metadata functions, and let / when syntax.

AEL reference →

AEL overview

Comparison operators, nested data access, and performance tips for filters.

Overview →

Query records

Set queries, limits, bin projection, and streaming results.

Query records →

Read records

Ops projection with selectFrom and CDT path reads.

Read records →