Skip to content

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;

Basic 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(...)

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

OperatorDescriptionExample
==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 comparisons
RecordStream s1 = session.query(users).where("$.age >= 18").execute();
s1.forEach((RecordResult result) -> { /* use result.recordOrThrow() */ });
// String equality
RecordStream s2 = session.query(users).where("$.role == 'admin'").execute();
s2.forEach((RecordResult result) -> { /* ... */ });
// Not equal
RecordStream s3 = session.query(users).where("$.deleted != true").execute();
s3.forEach((RecordResult result) -> { /* ... */ });

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

Logical operators

Combine conditions with and, or, and not:

DataSet users = DataSet.of("test", "users");
// AND
RecordStream a = session.query(users)
.where("$.status == 'active' and $.age >= 18")
.execute();
a.forEach((RecordResult result) -> { /* ... */ });
// OR
RecordStream b = session.query(users)
.where("$.role == 'admin' or $.role == 'moderator'")
.execute();
b.forEach((RecordResult result) -> { /* ... */ });
// NOT
RecordStream c = session.query(users)
.where("not($.status == 'banned')")
.execute();
c.forEach((RecordResult result) -> { /* ... */ });
// Complex combinations
RecordStream 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()

Membership and existence operators

OperatorDescriptionExample
inMembership 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 list
RecordStream s1 = session.query(users)
.where("'admin' in $.roles")
.execute();
s1.forEach((RecordResult result) -> { /* ... */ });
// Exists tests for the presence of the bin in the record
RecordStream s2 = session.query(users)
.where("$.email.exists()")
.execute();
s2.forEach((RecordResult result) -> { /* ... */ });
// Collection size
RecordStream 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()

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(...)

Limit results

Limit the number of records returned:

DataSet users = DataSet.of("test", "users");
// Get first 10 active users
RecordStream 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()

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 time
RecordStream 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(...)

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 fine

There 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 Object
Map<?, ?> 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!"); }

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 list
RecordStream 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(...)

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()

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

MethodDescription
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

Feedback

Was this page helpful?

What type of feedback are you giving?

What would you like us to know?

+Capture screenshot

Can we reach out to you?