Skip to content

Map indexes and querying

For the complete documentation index see: llms.txt

All documentation pages available in markdown.

Overview

This page describes how to create a secondary index on bins where the data type is a map. You can index either map key or map value.

Indexing on Map elements

  • Similar to basic indexing, the indexable map data types are numeric, string, and GeoJSON.
  • You can index a Map at any depth, up to the 64-level CDT nesting limit (Database 8.2.0 and later). Prior to Database 6.1.0, Map indexing was only on the top-level element, not nested elements.
  • When creating index, specify explicitly that map bins should be indexed, and what data type to index on.
  • When querying, specify that query should be applied on a CDT data type.
  • Similar to basic querying, equality, range for integer and string data types, points-within-region, and region-containing-points for GeoJSON data type are supported.

Examples

The following examples use asadm. For index management options, see Secondary index queries.

Create an index with source type mapkeys and a String data type:

Terminal window
Admin+> manage sindex create string foo_mapkey_idx in mapkeys ns test set demo bin foo

Create an index with source type mapvalues and an integer data type:

Terminal window
Admin+> manage sindex create integer foo_mapval_idx in mapvalues ns test set demo bin foo

Use show sindex to confirm the indexes were created successfully. Current versions of asadm display integer in the Bin Type column; older versions display numeric.

Terminal window
Admin+> show sindex
~~~~~~~~~~~~~~~~~~~Secondary Indexes (2026-04-08 16:01:35 UTC)~~~~~~~~~~~~~~~~~~~
Index Name|Namespace| Set|Bin| Bin| Index|State
| | | | Type| Type|
foo_mapkey_idx|test |demo|foo|string |mapkeys |RW
foo_mapval_idx|test |demo|foo|integer|mapvalues|RW
Number of rows: 2

Elements of the indexed map are type checked, so a record whose foo bin contains { a:1, b:"2", c:3, d:[4], e:5, 666:"zzz" } results in the following indexing:

Index OnKey TypeIndex TypeEligible Secondary Index Key
foostringMAPKEYSa, b, c, d, e
foointegerMAPVALUES1, 3, 5

Map index queries

This example inserts records with a foo map bin containing mixed key-value types, then queries by map key and map value using the indexes created above.

Insert data

import com.aerospike.client.sdk.Cluster;
import com.aerospike.client.sdk.ClusterDefinition;
import com.aerospike.client.sdk.DataSet;
import com.aerospike.client.sdk.Session;
import com.aerospike.client.sdk.policy.Behavior;
import java.util.Map;
Cluster cluster = new ClusterDefinition("127.0.0.1", 3000).connect();
Session session = cluster.createSession(Behavior.DEFAULT);
DataSet demo = DataSet.of("test", "demo");
session.upsert(demo.id("u1"))
.bin("username").setTo("Bob Roberts")
.bin("foo").setTo(Map.of("a", 1, "b", "2", "c", 3))
.execute();
session.upsert(demo.id("u2"))
.bin("username").setTo("rocketbob")
.bin("foo").setTo(Map.of("c", 3, "e", 5))
.execute();
session.upsert(demo.id("u3"))
.bin("username").setTo("samunwise")
.bin("foo").setTo(Map.of("x", Map.of("z", 26), "y", "yyy"))
.execute();

Query by map value (integer equality)

Query foo_mapval_idx for records where the foo map contains a value equal to 1. Only records with a matching integer value are returned. b: "2" is a string, not an integer, so it does not match.

import com.aerospike.client.sdk.RecordStream;
try (RecordStream rs = session.query(demo).where("$.foo.{=1}.exists()").execute()) {
rs.forEachRemaining(r -> System.out.println(r.recordOrThrow().getString("username")));
}
// Output: Bob Roberts

Query by map value (integer equality, multiple matches)

Query for records where the foo map contains a value equal to 3. Both u1 (c: 3) and u2 (c: 3) match.

try (RecordStream rs = session.query(demo).where("$.foo.{=3}.exists()").execute()) {
rs.forEachRemaining(r -> System.out.println(r.recordOrThrow().getString("username")));
}
// Output, in no guaranteed order:
// Bob Roberts
// rocketbob

Query by map value (integer range)

Query foo_mapval_idx for records where the foo map contains any integer value in the range [1, 5]. Every matching record is returned at least once. A record whose map holds more than one value in the range can be returned more than once, so your application must handle deduplication (see Known limitations).

// AEL value ranges exclude the upper bound, so 1:6 covers 1 through 5.
try (RecordStream rs = session.query(demo).where("$.foo.{=1:6}.exists()").execute()) {
rs.forEachRemaining(r -> System.out.println(r.recordOrThrow().getString("username")));
}
// Output, in no guaranteed order:
// Bob Roberts
// rocketbob

Query by map key (string equality)

Query foo_mapkey_idx for records where the foo map contains the key "y".

try (RecordStream rs = session.query(demo).where("$.foo.{@'y'}.exists()").execute()) {
rs.forEachRemaining(r -> System.out.println(r.recordOrThrow().getString("username")));
}
// Output: samunwise

Query by map key (multiple matches)

Query for records where the foo map contains the key "c". Both u1 and u2 have a "c" key.

try (RecordStream rs = session.query(demo).where("$.foo.{@'c'}.exists()").execute()) {
rs.forEachRemaining(r -> System.out.println(r.recordOrThrow().getString("username")));
}
// Output, in no guaranteed order:
// Bob Roberts
// rocketbob

Query by map key (no match)

Query for a key that does not exist in any record’s map. "z" appears only as a key of the nested "x" map, and a map-keys index without a context holds only top-level keys.

try (RecordStream rs = session.query(demo).where("$.foo.{@'z'}.exists()").execute()) {
rs.forEachRemaining(r -> System.out.println(r.recordOrThrow().getString("username")));
}
// Output: (none)

Known limitations

  • When using range queries on maps, records can be returned multiple times if the map contains multiple values that fall within the range.