Query records
For the complete documentation index see: llms.txt
All documentation pages available in markdown.
Applies to
- Aerospike Developer SDK preview (Java 21+ and Python 3.10+)
- Aerospike Database 6.0 or later unless a section states otherwise
Learn how to find records in Aerospike using the Developer SDK’s AEL (Aerospike Expression Language) queries. AEL provides a readable, SQL-like syntax for filtering data.
Except where noted, snippets on this page use the imports below. A snippet lists additional import lines only when it needs a type not shown here. When this page includes a Complete example section, that block is fully self-contained with every import required to run it.
import com.aerospike.client.sdk.DataSet;import com.aerospike.client.sdk.Record;import com.aerospike.client.sdk.RecordResult;import com.aerospike.client.sdk.RecordStream;
import java.util.ArrayList;import java.util.List;from aerospike_sdk import DataSetBasic query
All operations which read data are queries, regardless of whether you are reading one record, 100 records, or performing an index query. Use query() to find records, and a where clause to provide filtering if desired:
DataSet users = DataSet.of("test", "users");
RecordStream stream = session.query(users) .where("$.status == 'active'") .execute();stream.forEach(result -> { Record user = result.recordOrThrow(); System.out.println("Name: " + user.getString("name"));});📖 API reference:
DataSet.of(...)|Session.query(...)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()|RecordStream.forEach(...)|RecordResult.recordOrThrow()|Record.getString(...)
users = DataSet.of("test", "users")
stream = await session.query(users).where("$.status == 'active'").execute()async for row in stream: record = row.record_or_raise() print(f"Name: {record.bins['name']}")stream.close()📖 API reference:
DataSet.of()|Session.query()|QueryBuilder.where()|QueryBuilder.execute()|RecordStream.close()|RecordResult.record_or_raise()|RecordStream.close()
AEL operators
Use $.binName to get the contents of a bin called binName. String literals can be in either single or double quotes.
Floats are numbers with a decimal point and integers are just digits.
Comparison operators
| Operator | Description | Example |
|---|---|---|
== | Equal | "$.status == 'active'" |
!= | Not equal | "$.status != 'inactive'" |
> | Greater than | "$.age > 21" |
>= | Greater than or equal | "$.age >= 18" |
< | Less than | "$.balance < 0" |
<= | Less than or equal | "$.score <= 100" |
See the AEL reference for a complete list of AEL operators.
DataSet users = DataSet.of("test", "users");
// Numeric comparisonsRecordStream s1 = session.query(users).where("$.age >= 18").execute();s1.forEach((RecordResult result) -> { /* use result.recordOrThrow() */ });
// String equalityRecordStream s2 = session.query(users).where("$.role == 'admin'").execute();s2.forEach((RecordResult result) -> { /* ... */ });
// Not equalRecordStream s3 = session.query(users).where("$.deleted != true").execute();s3.forEach((RecordResult result) -> { /* ... */ });📖 API reference:
Session.query(...)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()|RecordStream.forEach(...)|RecordResult.recordOrThrow()
users = DataSet.of("test", "users")
# Numeric comparisonss1 = await session.query(users).where("$.age >= 18").execute()async for row in s1: pass # use row.record_or_raise()s1.close()
# String equalitys2 = await session.query(users).where("$.role == 'admin'").execute()async for row in s2: pass # ...s2.close()
# Not equals3 = await session.query(users).where("$.deleted != true").execute()async for row in s3: pass # ...s3.close()📖 API reference:
DataSet.of()|Session.query()|QueryBuilder.where()|QueryBuilder.execute()|RecordStream.close()
Logical operators
Combine conditions with and, or, and not:
DataSet users = DataSet.of("test", "users");
// ANDRecordStream a = session.query(users) .where("$.status == 'active' and $.age >= 18") .execute();a.forEach((RecordResult result) -> { /* ... */ });
// ORRecordStream b = session.query(users) .where("$.role == 'admin' or $.role == 'moderator'") .execute();b.forEach((RecordResult result) -> { /* ... */ });
// NOTRecordStream c = session.query(users) .where("not($.status == 'banned')") .execute();c.forEach((RecordResult result) -> { /* ... */ });
// Complex combinationsRecordStream d = session.query(users) .where("($.status == 'active' or $.status == 'pending') and $.age >= 18") .execute();d.forEach((RecordResult result) -> { /* ... */ });📖 API reference:
Session.query(...)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()|RecordStream.forEach(...)|RecordResult.recordOrThrow()
users = DataSet.of("test", "users")
# ANDa = await session.query(users).where("$.status == 'active' and $.age >= 18").execute()async for row in a: pass # use row.record_or_raise()a.close()
# ORb = await session.query(users).where("$.role == 'admin' or $.role == 'moderator'").execute()async for row in b: pass # ...b.close()
# NOTc = await session.query(users).where("not($.status == 'banned')").execute()async for row in c: pass # ...c.close()
# Complex combinationsd = await session.query(users).where( "($.status == 'active' or $.status == 'pending') and $.age >= 18").execute()async for row in d: pass # ...d.close()📖 API reference:
DataSet.of()|Session.query()|QueryBuilder.where()|QueryBuilder.execute()|RecordStream.close()
Membership and existence operators
| Operator | Description | Example |
|---|---|---|
in | Membership in a list | "'admin' in $.roles" |
exists() | Path/bin existence check | "$.email.exists()" |
count() | Collection cardinality | "$.tags.count() > 3" |
DataSet users = DataSet.of("test", "users");
// Membership in listRecordStream s1 = session.query(users) .where("'admin' in $.roles") .execute();s1.forEach((RecordResult result) -> { /* ... */ });
// Exists tests for the presence of the bin in the recordRecordStream s2 = session.query(users) .where("$.email.exists()") .execute();s2.forEach((RecordResult result) -> { /* ... */ });
// Collection sizeRecordStream s3 = session.query(users) .where("$.tags.count() > 3") .execute();s3.forEach((RecordResult result) -> { /* ... */ });📖 API reference:
Session.query(...)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()|RecordStream.forEach(...)|RecordResult.recordOrThrow()
users = DataSet.of("test", "users")
# Membership in lists1 = await session.query(users).where("'admin' in $.roles").execute()async for row in s1: pass # use row.record_or_raise()s1.close()
# Exists tests for the presence of the bin in the records2 = await session.query(users).where("$.email.exists()").execute()async for row in s2: pass # ...s2.close()
# Collection sizes3 = await session.query(users).where("$.tags.count() > 3").execute()async for row in s3: pass # ...s3.close()📖 API reference:
DataSet.of()|Session.query()|QueryBuilder.where()|QueryBuilder.execute()|RecordStream.close()
Select specific bins
Return only the bins you need:
DataSet users = DataSet.of("test", "users");
RecordStream stream = session.query(users) .where("$.status == 'active'") .readingOnlyBins("name", "email") // Only return name and email .execute();stream.forEach((RecordResult result) -> { Record user = result.recordOrThrow(); System.out.println(user.getString("name"));});📖 API reference:
Session.query(...)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.readingOnlyBins(...)|ChainableQueryBuilder.execute()|RecordStream.forEach(...)|RecordResult.recordOrThrow()|Record.getString(...)
users = DataSet.of("test", "users")
stream = await ( session.query(users) .where("$.status == 'active'") .bins(["name", "email"]) .execute())async for row in stream: record = row.record_or_raise() print(record.bins["name"])stream.close()📖 API reference:
DataSet.of()|Session.query()|QueryBuilder.where()|QueryBuilder.bins()|QueryBuilder.execute()|RecordResult.record_or_raise()|RecordStream.close()
Limit results
Limit the number of records returned:
DataSet users = DataSet.of("test", "users");
// Get first 10 active usersRecordStream stream = session.query(users) .where("$.status == 'active'") .limit(10) .execute();stream.forEach((RecordResult result) -> { /* ... */ });📖 API reference:
Session.query(...)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.limit(...)|ChainableQueryBuilder.execute()|RecordStream.forEach(...)|RecordResult.recordOrThrow()
users = DataSet.of("test", "users")
# Get first 10 active usersstream = await ( session.query(users) .where("$.status == 'active'") .limit(10) .execute())async for row in stream: pass # use row.record_or_raise()stream.close()📖 API reference:
DataSet.of()|Session.query()|QueryBuilder.where()|QueryBuilder.limit()|QueryBuilder.execute()|RecordStream.close()
Stream results
The record streams buffer results returned from the server efficiently when queries which return large numbers of results are executed. This is transparent to the API, but it means that large result sets are not loaded into memory in their entirety.
Record streams should be closed when finished, freeing up resources on
both the client side and server side. In Java, terminal helpers such as
getFirst, getFirstRecord, and forEach close the stream when the call
finishes. In Python, fully consuming a stream closes it automatically:
iterating to completion with async for, or calling collect() or
failures(), drains and releases the stream. first() and first_or_raise()
read only the first result, so on a multi-row stream they leave it partially
consumed and open. When you abandon a stream partway through, call
stream.close() (or wrap the work in a try / finally) to release
client and server resources promptly.
For large result sets, stream records instead of loading all into memory:
DataSet users = DataSet.of("test", "users");
// Process records one at a timeRecordStream stream = session.query(users) .where("$.status == 'active'") .execute();stream.forEach((RecordResult result) -> { if (result.isOk()) { Record user = result.recordOrThrow(); if (user != null) { System.out.println("Processing: " + user.getString("name")); } }});📖 API reference:
Session.query(...)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.execute()|RecordStream.forEach(...)|RecordResult.isOk()|RecordResult.recordOrThrow()|Record.getString(...)
users = DataSet.of("test", "users")
# Process records one at a timestream = await session.query(users).where("$.status == 'active'").execute()async for row in stream: record = row.record_or_raise() print(f"Processing: {record.bins['name']}")stream.close()
# Equivalent with try/finally — guarantees close on exception:stream = await session.query(users).where("$.status == 'active'").execute()try: async for row in stream: record = row.record_or_raise() print(f"Processing: {record.bins['name']}")finally: stream.close()📖 API reference:
DataSet.of()|Session.query()|QueryBuilder.where()|RecordResult.record_or_raise()|RecordStream.close()|QueryBuilder.execute()
Iteration patterns and key access
Accessing the record key
The Record class holds only bin data (bins, generation, expiration). The key is on the RecordResult wrapper, accessible via result.key():
stream.forEach(result -> { Key k = result.key();
// String key: String strKey = k.userKey.toString();
// Integer key (userKey is a Value; call toLong()): long intKey = k.userKey.toLong();
Record record = result.recordOrThrow(); // use record.getString("name"), record.getInt("age"), etc.});record.key and record.getKey() do not exist. Always use result.key() on the RecordResult, not on the Record itself.
Accessing bins on a Record
Record exposes typed getters (getString, getInt, getLong, getBoolean, getMap, getList) as well as the raw bins map and getValue(String):
// Typed getter (preferred):String name = record.getString("name");int age = record.getInt("age");
// Generic:Object raw = record.getValue("profile"); // same as record.bins.get("profile")Object raw2 = record.bins.get("profile"); // also fineThere is no record.get("binName") method. Use record.getValue("binName") or record.bins.get("binName") for untyped access.
record.getMap() returns AerospikeMap<?,?>, not Map<String, Object>
record.getMap("binName") returns AerospikeMap<?, ?>. Because of Java’s wildcard bounds, this cannot be directly assigned to Map<String, Object> without a cast, even though AerospikeMap does implement Map. The two simplest approaches:
// Option 1: via bins field (cleanest, no annotation needed):// record.bins is Map<String,Object>, so .get() returns ObjectMap<?, ?> profile = (Map<?, ?>) record.bins.get("profile");String level = (String) profile.get("level");
// Option 2: via getValue(), with unchecked cast to typed Map:@SuppressWarnings("unchecked")Map<String, Object> profile2 = (Map<String, Object>) record.getValue("profile");String level2 = (String) profile2.get("level");Record import disambiguation: don’t use wildcard imports
Don’t use import com.aerospike.client.sdk.*;. It causes a compile error in Java 16+ because both com.aerospike.client.sdk.Record and java.lang.Record are pulled into scope and the compiler cannot resolve the ambiguity. Always import every class explicitly:
// Import com.aerospike.client.sdk.Record explicitly to avoid java.lang.Record conflict (see shared imports above).// Do not use import com.aerospike.client.sdk.*;Mutable captures in lambdas
Java lambdas require all captured local variables to be effectively final. This applies to every mutable type: booleans, integer counters, long accumulators, and any other variable modified inside the lambda. Use the matching Atomic* wrapper:
// Additional imports for this example:import java.util.concurrent.atomic.AtomicBoolean;import java.util.concurrent.atomic.AtomicInteger;import java.util.concurrent.atomic.AtomicLong;
AtomicBoolean found = new AtomicBoolean(false);AtomicInteger chunkNumber = new AtomicInteger(1);AtomicLong recordCount = new AtomicLong(0L);
stream.forEach(result -> { if (result.isOk()) { found.set(true); chunkNumber.getAndIncrement(); recordCount.incrementAndGet(); }});
if (found.get()) { System.out.println("Record found!"); }Accessing the record key
The user key surfaces on the per-row envelope (row.key, a Key) rather than on the bin-payload Record. record.key is always None in PSDK; reach for row.key.value instead:
async for row in stream: k = row.key
# String key: str_key = k.value # returns a str if written with a string key
# Integer key (no type coercion needed — Python is dynamic): int_key = k.value # returns an int if written with an integer key
record = row.record_or_raise() # use record.bins["name"], record.bins["age"], etc.stream.close()Accessing bins on a Record
record.bins is a plain dict. Two access patterns:
# Subscript (preferred when the bin is known-present; raises KeyError otherwise):name = record.bins["name"]age = record.bins["age"]
# .get() for optional access (returns None if missing):profile = record.bins.get("profile")Bin values keep their Python types (int, str, list, dict, bytes, bool). No typed-getter methods and no AerospikeMap-style wildcard-cast concerns — a dict bin is just a dict.
Mutable captures in closures
Not applicable in Python. Regular closures capture the enclosing scope’s names, and mutation is fine:
found = Falserecord_count = 0
async for row in stream: if row.is_ok: found = True record_count += 1stream.close()
if found: print(f"Record found! ({record_count} total)")No Atomic* wrappers needed — this is the standard Python iteration idiom.
Query without filter (scan)
Query all records in a set:
DataSet users = DataSet.of("test", "users");
// Get all users (use with caution on large datasets!)RecordStream allStream = session.query(users).execute();List<Record> allUsers = new ArrayList<>();allStream.forEach(result -> { if (result.isOk()) { allUsers.add(result.recordOrThrow()); }});// forEach closes allStream.
// Better: stream one record at a time without building a full listRecordStream scan = session.query(users).execute();scan.forEach(result -> { if (result.isOk()) { Record user = result.recordOrThrow(); System.out.println(user.getString("name")); }});📖 API reference:
Session.query(...)|ChainableQueryBuilder.execute()|RecordStream.forEach(...)|RecordResult.isOk()|RecordResult.recordOrThrow()|Record.getString(...)
users = DataSet.of("test", "users")
# Get all users (use with caution on large datasets!)all_stream = await session.query(users).execute()all_users = []async for row in all_stream: if row.is_ok: all_users.append(row.record_or_raise())all_stream.close()
# Better: stream one record at a time without building a full listscan = await session.query(users).execute()async for row in scan: record = row.record_or_raise() print(record.bins["name"])scan.close()📖 API reference:
DataSet.of()|Session.query()|QueryBuilder.execute()|RecordResult.is_ok|RecordResult.record_or_raise()|RecordStream.close()
Complete example
This example is self-contained—it lists every import needed to run standalone.
import com.aerospike.client.sdk.Cluster;import com.aerospike.client.sdk.ClusterDefinition;import com.aerospike.client.sdk.DataSet;import com.aerospike.client.sdk.Record;import com.aerospike.client.sdk.RecordResult;import com.aerospike.client.sdk.RecordStream;import com.aerospike.client.sdk.Session;import com.aerospike.client.sdk.policy.Behavior;
public class QueryRecordsExample { public static void main(String[] args) { try (Cluster cluster = new ClusterDefinition("localhost", 3000).connect()) { Session session = cluster.createSession(Behavior.DEFAULT); DataSet users = DataSet.of("test", "users"); String k1 = "query-example-1"; String k2 = "query-example-2"; String k3 = "query-example-3"; String k4 = "query-example-4";
// Cleanup so the example is repeatable. session.delete(users.ids(k1, k2, k3, k4)).execute().close();
// Create sample data session.insert(users) .bins("name", "age", "status") .id(k1).values("Alice", 28, "active") .id(k2).values("Bob", 35, "active") .id(k3).values("Carol", 22, "inactive") .id(k4).values("David", 45, "active") .execute();
// Simple query System.out.println("Active users:"); RecordStream q1 = session.query(users) .where("$.status == 'active'") .execute(); q1.forEach((RecordResult result) -> { Record u = result.recordOrThrow(); System.out.println(" - " + u.getString("name")); });
// Complex query System.out.println("\nActive adults over 30:"); RecordStream q2 = session.query(users) .where("$.status == 'active' and $.age > 30") .readingOnlyBins("name", "age") .execute(); q2.forEach(result -> { if (result.isOk()) { Record u = result.recordOrThrow(); System.out.println( " - " + u.getString("name") + " (" + u.getInt("age") + ")" ); } });
// Count RecordStream q3 = session.query(users) .where("$.status == 'active'") .execute(); long activeCount = 0; while (q3.hasNext()) { RecordResult rr = q3.next(); activeCount++; } q3.close(); System.out.println("\nActive user count: " + activeCount); } }}📖 API reference:
ClusterDefinition(String,int)|ClusterDefinition.connect()|Cluster.createSession(Behavior)|Cluster.close()|DataSet.of(...)|DataSet.ids(...)|Session.delete(...)|ChainableNoBinsBuilder.execute()|RecordStream.close()|Session.insert(DataSet)|OperationObjectBuilder.bins(...)|IdValuesBuilder.id(...)|IdValuesRowBuilder.id(...)|IdValuesRowBuilder.values(...)|IdValuesRowBuilder.execute()|Session.query(...)|ChainableQueryBuilder.where(...)|ChainableQueryBuilder.readingOnlyBins(...)|ChainableQueryBuilder.execute()|RecordStream.forEach(...)|RecordResult.isOk()|RecordResult.recordOrThrow()|Record.getString(...)|Record.getInt(...)|RecordStream.hasNext()|RecordStream.next()
import asynciofrom aerospike_sdk import Behavior, ClusterDefinition, DataSet
async def main(): async with await ClusterDefinition("localhost", 3000).connect() as cluster: session = cluster.create_session(Behavior.DEFAULT) users = DataSet.of("test", "users") k1 = users.id("query-example-1") k2 = users.id("query-example-2") k3 = users.id("query-example-3") k4 = users.id("query-example-4")
# Cleanup so the example is repeatable. await session.delete(k1, k2, k3, k4).execute()
# Create sample data await ( session.batch() .insert(k1).put({"name": "Alice", "age": 28, "status": "active"}) .insert(k2).put({"name": "Bob", "age": 35, "status": "active"}) .insert(k3).put({"name": "Carol", "age": 22, "status": "inactive"}) .insert(k4).put({"name": "David", "age": 45, "status": "active"}) .execute() )
# Simple query print("Active users:") q1 = await session.query(users).where("$.status == 'active'").execute() async for row in q1: record = row.record_or_raise() print(f" - {record.bins['name']}") q1.close()
# Complex query print("\nActive adults over 30:") q2 = await ( session.query(users) .where("$.status == 'active' and $.age > 30") .bins(["name", "age"]) .execute() ) async for row in q2: r = row.record_or_raise() print(f" - {r.bins['name']} ({r.bins['age']})") q2.close()
# Count q3 = await session.query(users).where("$.status == 'active'").execute() active_count = 0 async for row in q3: row.record_or_raise() active_count += 1 q3.close() print(f"\nActive user count: {active_count}")
if __name__ == "__main__": asyncio.run(main())📖 API reference:
ClusterDefinition|ClusterDefinition.connect()|Cluster.create_session()|DataSet.of()|DataSet.id()|Behavior.DEFAULT|Session.query()|Session.batch()|QueryBuilder.where()|QueryBuilder.bins()|Session.delete()|RecordResult.record_or_raise()|BatchOperationBuilder.execute()|BatchKeyOperationBuilder.put()|QueryBuilder.execute()|WriteSegmentBuilder.execute()
AEL and secondary indexes
AEL text does not force a full scan. The planner can use available secondary indexes for selective predicates and fall back to scans when no useful index exists.
📖 Learn more: AEL Reference covers the full AEL syntax.
API reference summary
| Method | Description |
|---|---|
session.query(dataSet) | Start a query on a DataSet |
.where("expression") | Filter with AEL expression (use $.bin for bin paths) |
.readingOnlyBins(bins...) (Java) · .bins([...]) (Python) | Select specific bins to return |
.limit(n) | Limit number of results |
.execute() | Run the query and return a RecordStream |
RecordStream.forEach(...) (Java) · async for row in stream (Python) | Iterate results — both auto-consume the stream on exhaustion; wrap in try/finally with stream.close() if you may abandon iteration early |
Next steps
AEL Reference
Full AEL syntax and operators.
Batch Operations
Efficient multi-record operations.
Async Operations
Non-blocking queries for high throughput.