Debug AEL expressions
For the complete documentation index see: llms.txt
All documentation pages available in markdown.
Task guide: move from a failed AEL string or a silent filter mismatch to a concrete cause using server error details.
Applies to
- Aerospike Developer SDKs
- Aerospike Database 8.2.0.0 and later (server-side AEL compilation and structured error details)
Audience
Developers authoring AEL filter or operation-expression text (Intermediate).
Prerequisites
- A connected session from Connect to Aerospike
- Familiarity with Author AEL filter and operation expressions
- How to opt in to error details. See Server error details (verbosity ladder, operator cap, truncation).
- Per-client accessors for traces on the Developer SDK. See Server error details. The Java and Go legacy clients expose the same fields through their error-handling APIs. See Java error handling and Go error handling.
Outcome
You can read a parse or build failure in your own AEL source, use aelOffset and aelSpan to highlight the error,
and interpret why a single-record filter rejected a record without assuming a trace is always present.
When to use this page
| Symptom | Start here | Also see |
|---|---|---|
| Malformed AEL, wrong types at compile time | Parse and build errors | AEL reference |
| Need the exact characters that failed | Source coordinates in the trace | |
Filter returned nothing / FILTERED_OUT | Filter-decision explainer | Author AEL expressions |
| Which verbosity to set, byte budget, operator cap | Server error details |
Neither SDK parses AEL locally. Every .where(...), selectFrom(...) / select_from(...), and write-side *From(...) call sends your string to the server. Diagnostics come back on the failure response when you opt in.
Enable diagnostics
Set errorDetailVerbosity on the session Behavior before you run the failing command:
| Goal | Verbosity | What you get for AEL |
|---|---|---|
| Contextual build message (includes line/column for parse errors) | MESSAGE (2) | server_message / exception message text |
| Build trace or filter explainer | EXPRESSION_TRACE (3) | Structured ExpressionTrace (Java) or exp_trace (Python) when the server attaches one |
import com.aerospike.client.sdk.ErrorDetailVerbosity;import com.aerospike.client.sdk.policy.Behavior;
Behavior withDetails = Behavior.DEFAULT.deriveWithChanges("debugAel", builder -> builder .on(Behavior.Selectors.all(), ops -> ops .errorDetailVerbosity(ErrorDetailVerbosity.EXPRESSION_TRACE) ));Session session = cluster.createSession(withDetails);📖 API reference:
ErrorDetailVerbosity|Behavior|Behavior.deriveWithChanges()
from aerospike_sdk import Behavior, ErrorDetailVerbosityfrom aerospike_sdk.policy import Settings
behavior = Behavior.DEFAULT.derive_with_changes( "debug_ael", all=Settings(error_detail_verbosity=ErrorDetailVerbosity.EXPRESSION_TRACE),)session = cluster.create_session(behavior)📖 API reference:
ErrorDetailVerbosity|Behavior.derive_with_changes()|Settings
Use verbosity 3 only while debugging. Keep production code at 0 or 1 unless you have a specific reason to run higher. See Choosing a verbosity.
Parse and build errors
A malformed or ill-typed AEL string fails at build time on the server. The client surfaces PARAMETER_ERROR (4). That is a build-time failure, not a filter mismatch.
The server’s message names which slot held the expression, then folds in the AEL compiler diagnostic. For a parse error, line and column appear inside that message string. Read the message text. Do not look for them in the structured trace.
| Slot | Typical message prefix | SDK entry point |
|---|---|---|
| Filter on a single-key command | invalid filter expression in request | .where(...) on .query(key) or .update(key) |
| Filter in a batch request | invalid filter expression (batch context in the full message) | Per-key .where(...) in a multi-key .query(...) chain |
| Filter on a set query | invalid filter expression in query | .where(...) on .query(dataset) (fails before the query runs) |
| Operation expression | invalid expression in operation request | .selectFrom(...) / .select_from(...), upsertFrom, insertFrom, and updateFrom |
For a parse failure, the folded diagnostic after the prefix usually includes line and column, for example unexpected end of expression at line 1 col 15 for a trailing and with no right-hand operand.
Example: filter parse error
Trailing and with no right-hand operand is a parse failure:
$.score:INT > 30 andimport com.aerospike.client.sdk.AerospikeException;import com.aerospike.client.sdk.ResultCode;
String ael = "$.score:INT > 30 and";
try { session.query(users.id("user-1")).where(ael).execute();}catch (AerospikeException e) { if (e.getResultCode() == ResultCode.PARAMETER_ERROR) { // e.getMessage() / getBaseMessage(): slot prefix + AEL diagnostic (line/column for parse errors) System.out.println(e.getMessage()); }}from aerospike_sdk import AerospikeError, ResultCode
ael = "$.score:INT > 30 and"
try: await session.query(users.id("user-1")).where(ael).execute()except AerospikeError as e: if e.result_code == ResultCode.PARAMETER_ERROR: print(e.server_message or e)Example: operation-expression type error
Integer bin plus string literal fails at compile time:
$.qty:INT + 'x'try { session.query(orders.id("order-1")) .bin("bad") .selectFrom("$.qty:INT + 'x'") .execute();}catch (AerospikeException e) { if (e.getResultCode() == ResultCode.PARAMETER_ERROR) { System.out.println(e.getMessage()); // expect "invalid expression in operation request" prefix }}try: await ( session.query(orders.id("order-1")) .bin("bad") .select_from("$.qty:INT + 'x'") .execute() )except AerospikeError as e: if e.result_code == ResultCode.PARAMETER_ERROR: print(e.server_message or e)Example: set-query filter build failure
A malformed filter on .query(dataset) fails while the server plans the query, before any records are read. The message prefix is invalid filter expression in query:
try { session.query(users) .where("$.age > 30 and") .execute();}catch (AerospikeException e) { if (e.getResultCode() == ResultCode.PARAMETER_ERROR) { System.out.println(e.getMessage()); }}try: await session.query(users).where("$.age > 30 and").execute()except AerospikeError as e: if e.result_code == ResultCode.PARAMETER_ERROR: print(e.server_message or e)Example: batch filter build failure
In a multi-key batch read, each key can carry its own filter. A bad AEL string on one key returns PARAMETER_ERROR on that row only. Siblings with valid filters still succeed. Inspect RecordResult / stream rows rather than expecting a top-level exception:
import com.aerospike.client.sdk.ExpressionTrace;import com.aerospike.client.sdk.RecordStream;import com.aerospike.client.sdk.ResultCode;
RecordStream stream = session .query(users.id("u1")).where("$.score == 1") .query(users.id("u2")).where("$.score:INT > 30 and") .execute();
stream.forEach(result -> { if (!result.isOk() && result.getResultCode() == ResultCode.PARAMETER_ERROR) { System.out.println(result.getKey().userKey + ": " + result.getMessage()); ExpressionTrace trace = result.getExpressionTrace(); if (trace != null) { System.out.println(trace); } }});stream.close();stream = await ( session.query(users.id("u1")).where("$.score == 1") .query(users.id("u2")).where("$.score:INT > 30 and") .execute())try: async for row in stream: if not row.is_ok and row.result_code == ResultCode.PARAMETER_ERROR: print(row.key.value, row.server_message) if row.exp_trace is not None: print(row.exp_trace)finally: stream.close()See Filter batch key reads for the chaining pattern.
Source coordinates in the trace
At verbosity EXPRESSION_TRACE, a build-phase failure can include a structured trace with a source-language marker and coordinates into your AEL string:
lang:LANG_AELfor AEL-authored expressions (msgpackExpbuilders useLANG_MSGPACK).aelOffset/aelSpan(Java:getAelOffset(),getAelSpan()): character offset and byte width of the offending region in the AEL source text you passed to the SDK.snippet: An AEL fragment and location marker showing the part of the AEL which caused the failure
Two coordinate spaces
| Field | Indexes |
|---|---|
aelOffset + aelSpan | Your AEL source text: character offset plus UTF-8 byte span of the offending region |
byteOffset | The compiled msgpack expression payload on the wire (not useful for AEL authors) |
Line and column are not structured trace fields. Wire keys for ael_line and ael_col are reserved and are not emitted. For parse failures, read line and column from the message at verbosity 2 (previous section). An expected[] list of valid productions does not ship.
Highlight the offending region
For a simple way to show the part of the AEL which caused the failure including a marker of the error in the fragment, using snippet. Fro more complex processing, keep the exact AEL string in a variable so you can slice it when a trace is present. aelOffset is a character index. aelSpan is a byte width in UTF-8. Do not pass start + span to substring() or string slicing when using mult-byte characters.
import java.nio.charset.StandardCharsets;
import com.aerospike.client.sdk.AerospikeException;import com.aerospike.client.sdk.ExpressionTrace;
String ael = "$.score:INT > 30 and";
try { session.query(users.id("user-1")).where(ael).execute();}catch (AerospikeException e) { ExpressionTrace trace = e.getExpressionTrace(); if (trace != null && trace.getLang() == ExpressionTrace.LANG_AEL && trace.getSnippet()!= null) { System.out.println("Near: " + trace.getSnippet()); } else { System.out.println(e.getMessage()); }}from aerospike_sdk import ExpressionTrace
ael = "$.score:INT > 30 and"
try: await session.query(users.id("user-1")).where(ael).execute()except AerospikeError as e: trace = e.exp_trace if ( trace is not None and trace.lang == ExpressionTrace.LANG_AEL and trace.snippet is not None ): print("Near:", trace.snippet) else: print(e.server_message or e)Always branch on trace != null (or equivalent). See When the trace is missing.
Filter-decision explainer
When a filter evaluates to false (or faults, or hits absent data), a query or read normally omits the record with no error. To surface the decision, call .failOnFilteredOut() / .fail_on_filtered_out() and handle FILTERED_OUT (27).
At verbosity EXPRESSION_TRACE, an eval-phase trace can explain the decision:
outcome | Meaning |
|---|---|
OUTCOME_FALSE | Filter ran. The record did not match. |
OUTCOME_ABSENT | A referenced bin or key was absent |
OUTCOME_FAULT | Evaluation fault (type mismatch or divide-by-zero) |
For a decisive comparison, operands carries the two values that decided the outcome (subject to clipping and truncation). See Expression traces.
Example: record rejected by a filter
Record has score = 1. Filter is $.score == 99:
import com.aerospike.client.sdk.AerospikeException;import com.aerospike.client.sdk.ExpressionTrace;
try { session.query(users.id("user-1")) .where("$.score == 99") .failOnFilteredOut() .execute();}catch (AerospikeException.FilteredException e) { ExpressionTrace trace = e.getExpressionTrace(); if (trace != null) { System.out.println("phase=" + trace.getPhase() + " outcome=" + trace.getOutcome()); if (trace.getOutcome() == ExpressionTrace.OUTCOME_FALSE) { System.out.println(" Operands:" + java.util.Arrays.toString(trace.getOperands())); } }}📖 API reference:
AerospikeException.FilteredException|ExpressionTrace|ChainableQueryBuilder.failOnFilteredOut()
from aerospike_sdk import ExpressionTrace, FilteredOutError
try: await ( session.query(users.id("user-1")) .where("$.score == 99") .fail_on_filtered_out() .execute() )except FilteredOutError as e: trace = e.exp_trace if trace is not None: print(f"phase={trace.phase} outcome={trace.outcome}") if trace.outcome == ExpressionTrace.OUTCOME_FALSE: print(" Operands:", trace.operands)📖 API reference:
FilteredOutError|QueryBuilder.fail_on_filtered_out()
Limits on the explainer
- Read permission: Operand values are bin contents. A principal without read access does not receive filter explanations.
- Query path: Queries filter many records per request. You get build-time traces when a query’s filter fails to compile. Per-record filter explainers are not returned on the query path (Server error details: queries return details only when the query fails to start). Use a single-key read (or batch key operation) with
.failOnFilteredOut()/.fail_on_filtered_out()to debug why one record did not match.
When the trace is missing
A null trace is normal. Four independent mechanisms can remove it:
- Verbosity below
3: only messages (or less) are returned. - Byte budget: the server drops whole trace parts (operands first, then snippet) when detail would exceed about 1 KB per record.
- Operator cap:
error-details-max-verbositylimits the cluster regardless of client request. - Read permission: no explainer for write-only principals.
Write examples to degrade gracefully: print the message when getExpressionTrace() / exp_trace is absent.
Verify
- Reproduce the failure with
errorDetailVerbosityset toMESSAGEorEXPRESSION_TRACE. - For build failures, confirm
PARAMETER_ERRORand read the slot prefix in the message. - For “why not this record?”, use single-key
.failOnFilteredOut()and confirmFILTERED_OUTwith an eval-phase trace when permitted. - If a trace is present with
LANG_AEL, slice your source string on UTF-8 bytes usingaelOffset(char index) andaelSpan(byte width), and confirm the highlighted region matches the mistake. - Read the record directly to confirm operand hints. Do not treat clipped operand strings as authoritative bin values.
Next steps
Pass AEL to filters and operation expressions on each command type.
Verbosity levels, trace shape, truncation, and operator caps.