---
title: "Debug AEL expressions"
description: "Debug Aerospike Expression Language (AEL) parse errors, source coordinates, and filter-decision traces in the Java and Python Developer SDKs."
---

# Debug AEL expressions

> For the complete documentation index see: [llms.txt](https://aerospike.com/docs/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](https://aerospike.com/docs/develop/client/sdk/connect)
-   Familiarity with [Author AEL filter and operation expressions](https://aerospike.com/docs/develop/client/sdk/concepts/ael/authoring-ael-expressions)
-   How to opt in to error details. See [Server error details](https://aerospike.com/docs/database/reference/error-details) (verbosity ladder, operator cap, truncation).
-   Per-client accessors for traces on the Developer SDK. See [Server error details](https://aerospike.com/docs/database/reference/error-details#expression-traces). The [Java](https://aerospike.com/docs/develop/client/java) and [Go](https://aerospike.com/docs/develop/client/go) legacy clients expose the same fields through their error-handling APIs. See [Java error handling](https://aerospike.com/docs/develop/client/java/error-handling#server-error-details) and [Go error handling](https://aerospike.com/docs/develop/client/go/error-handling#server-error-details).

## 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](#parse-and-build-errors) | [AEL reference](https://aerospike.com/docs/develop/client/sdk/concepts/ael/reference) |
| Need the exact characters that failed | [Source coordinates in the trace](#source-coordinates-in-the-trace) |  |
| Filter returned nothing / `FILTERED_OUT` | [Filter-decision explainer](#filter-decision-explainer) | [Author AEL expressions](https://aerospike.com/docs/develop/client/sdk/concepts/ael/authoring-ael-expressions) |
| Which verbosity to set, byte budget, operator cap |  | [Server error details](https://aerospike.com/docs/database/reference/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 |

-   [Java](#tab-panel-6042)
-   [Python](#tab-panel-6043)

```java
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`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ErrorDetailVerbosity.html) | [`Behavior`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/policy/Behavior.html) | [`Behavior.deriveWithChanges()`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/policy/Behavior.html#deriveWithChanges%28java.lang.String,java.util.function.Consumer%29)

```python
from aerospike_sdk import Behavior, ErrorDetailVerbosity

from 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`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/error-detail-verbosity.html) | [`Behavior.derive_with_changes()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/behavior.html#aerospike%5Fsdk.behavior.Behavior.derive%5Fwith%5Fchanges) | [`Settings`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/policy.html#aerospike%5Fsdk.policy.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](https://aerospike.com/docs/database/reference/error-details#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:

```text
$.score:INT > 30 and
```

-   [Java](#tab-panel-6044)
-   [Python](#tab-panel-6045)

```java
import 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());

    }

}
```

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

```text
$.qty:INT + 'x'
```

-   [Java](#tab-panel-6046)
-   [Python](#tab-panel-6047)

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

    }

}
```

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

-   [Java](#tab-panel-6048)
-   [Python](#tab-panel-6049)

```java
try {

    session.query(users)

        .where("$.age > 30 and")

        .execute();

}

catch (AerospikeException e) {

    if (e.getResultCode() == ResultCode.PARAMETER_ERROR) {

        System.out.println(e.getMessage());

    }

}
```

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

-   [Java](#tab-panel-6050)
-   [Python](#tab-panel-6051)

```java
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();
```

```python
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](https://aerospike.com/docs/develop/client/sdk/concepts/ael/authoring-ael-expressions#filter-batch-key-reads) for the chaining pattern.

::: test before production
There is no client-side AEL parser. Use [Aerospike Voyager](https://aerospike.com/download/voyager/) or a development cluster with `MESSAGE` verbosity when authoring new filters.
:::

## 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_AEL` for AEL-authored expressions (msgpack `Exp` builders use `LANG_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.

-   [Java](#tab-panel-6052)
-   [Python](#tab-panel-6053)

```java
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());

    }

}
```

```python
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](#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).

::: build errors are not filter mismatches
Catch `PARAMETER_ERROR` (4) for malformed AEL at build time. A record that fails a filter at evaluation time arrives as `FILTERED_OUT` (27) (or, for some operation-expression faults, `OP_NOT_APPLICABLE` (26)). Code that only handles `PARAMETER_ERROR` misses every eval-phase trace.
:::

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](https://aerospike.com/docs/database/reference/error-details#expression-traces).

### Example: record rejected by a filter

Record has `score = 1`. Filter is `$.score == 99`:

-   [Java](#tab-panel-6054)
-   [Python](#tab-panel-6055)

```java
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`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/AerospikeException.FilteredException.html) | [`ExpressionTrace`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ExpressionTrace.html) | [`ChainableQueryBuilder.failOnFilteredOut()`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#failOnFilteredOut%28%29)

```python
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`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/exceptions.html#aerospike%5Fsdk.exceptions.FilteredOutError) | [`QueryBuilder.fail_on_filtered_out()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/query.html#aerospike%5Fsdk.aio.operations.query.QueryBuilder.fail%5Fon%5Ffiltered%5Fout)

### 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](https://aerospike.com/docs/database/reference/error-details#coverage): 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:

1.  Verbosity below `3`: only messages (or less) are returned.
2.  Byte budget: the server drops whole trace parts (operands first, then snippet) when detail would exceed about 1 KB per record.
3.  Operator cap: [`error-details-max-verbosity`](https://aerospike.com/docs/database/reference/config#service__error-details-max-verbosity) limits the cluster regardless of client request.
4.  Read permission: no explainer for write-only principals.

Write examples to degrade gracefully: print the message when `getExpressionTrace()` / `exp_trace` is absent.

## Verify

1.  Reproduce the failure with `errorDetailVerbosity` set to `MESSAGE` or `EXPRESSION_TRACE`.
2.  For build failures, confirm `PARAMETER_ERROR` and read the slot prefix in the message.
3.  For “why not this record?”, use single-key `.failOnFilteredOut()` and confirm `FILTERED_OUT` with an eval-phase trace when permitted.
4.  If a trace is present with `LANG_AEL`, slice your source string on UTF-8 bytes using `aelOffset` (char index) and `aelSpan` (byte width), and confirm the highlighted region matches the mistake.
5.  Read the record directly to confirm operand hints. Do not treat clipped operand strings as authoritative bin values.

## Next steps

AEL reference

Grammar, paths, and type rules for valid AEL text.

[AEL reference →](https://aerospike.com/docs/develop/client/sdk/concepts/ael/reference)

Author AEL expressions

Pass AEL to filters and operation expressions on each command type.

[Author AEL expressions →](https://aerospike.com/docs/develop/client/sdk/concepts/ael/authoring-ael-expressions)

Server error details

Verbosity levels, trace shape, truncation, and operator caps.

[Server error details →](https://aerospike.com/docs/database/reference/error-details)

SDK error handling

Execution modes, `RecordResult`, and batch failure isolation.

[SDK error handling →](https://aerospike.com/docs/develop/client/sdk/concepts/error-handling)