---
title: "Secondary indexes"
description: "Learn how to create and query Aerospike secondary indexes using the Java and Python Developer SDKs."
---

# Secondary indexes

> For the complete documentation index see: [llms.txt](https://aerospike.com/docs/llms.txt)
> 
> All documentation pages available in markdown.

Secondary indexes enable efficient queries on bin values beyond the primary key.

## What is a secondary index?

By default, Aerospike retrieves records by their primary key (digest). A **secondary index** lets you query records by the value of a specific bin.

| Query type | Behavior |
| --- | --- |
| Single primary key lookup | key → record (O(1), always fast) |
| Multiple primary key lookups | keys → records (done in parallel across servers, usually fast) |
| Secondary index query | bin value → records (requires index) |
| Primary index query | (scans all records in parallel, O(N), can be slow) |

## Index types

| Type | Bin Data Type | Query Operations |
| --- | --- | --- |
| `IndexType.STRING` / `IndexTypeEnum.STRING` | String | Equality (`==`) |
| `IndexType.INTEGER` / `IndexTypeEnum.NUMERIC` | Integer | Equality and range comparisons (`<`, `<=`, `>`, `>=`) |

## When to use secondary indexes

✅ **Good use cases**:

-   Filtering by status, category, or type fields
-   Range queries on timestamps or numeric IDs
-   Geospatial queries (find nearby)

❌ **Avoid when**:

-   High cardinality (millions of unique values)
-   Zero or one result will be returned (eg alternate id for a record)
-   Frequently updated bins
-   Can use primary key lookup instead

## Creating an index

Note: Creating indexes in code in production environments is often an anti-pattern due to resource consumption. Use tools like `asadm` to manage indexes in higher environments instead.

-   [Java](#tab-panel-3056)
-   [Python](#tab-panel-3057)

```java
import com.aerospike.client.sdk.DataSet;

import com.aerospike.client.sdk.query.IndexCollectionType;

import com.aerospike.client.sdk.query.IndexType;

DataSet users = DataSet.of("test", "users");

// Create an integer index on the "age" bin

session.createIndex(users, "age_idx", "age", IndexType.INTEGER, IndexCollectionType.DEFAULT)

    .waitTillComplete();

// Create a string index on the "status" bin

session.createIndex(users, "status_idx", "status", IndexType.STRING, IndexCollectionType.DEFAULT)

    .waitTillComplete();
```

> 📖 **API reference**: [`DataSet.of(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/DataSet.html#of%28java.lang.String%2Cjava.lang.String%29) | [`Session.createIndex(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Session.html#createIndex%28com.aerospike.client.sdk.DataSet%2Cjava.lang.String%2Cjava.lang.String%2Ccom.aerospike.client.sdk.query.IndexType%2Ccom.aerospike.client.sdk.query.IndexCollectionType%2Ccom.aerospike.client.sdk.cdt.CTX...%29)

```python
from aerospike_sdk import DataSet

users = DataSet.of("test", "users")

# Create an integer index on the "age" bin

await (

    session.index(dataset=users)

    .on_bin("age")

    .named("age_idx")

    .numeric()

    .create()

)

# Create a string index on the "status" bin

await (

    session.index(dataset=users)

    .on_bin("status")

    .named("status_idx")

    .string()

    .create()

)
```

> 📖 **API reference**: [`DataSet.of()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/dataset.html#aerospike%5Fsdk.dataset.DataSet.of) | [`Session.index()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/session.html#aerospike%5Fsdk.aio.session.Session.index)

## Querying with indexes

Once an index exists, matching AEL predicates can use it:

-   [Java](#tab-panel-3058)
-   [Python](#tab-panel-3059)

```java
import com.aerospike.client.sdk.Record;

import com.aerospike.client.sdk.RecordResult;

import com.aerospike.client.sdk.RecordStream;

// This query uses the "age_idx" secondary index

RecordStream stream = session.query(users)

    .where("$.age > 21")

    .execute();

stream.forEach((RecordResult result) -> {

    if (result.isOk()) {

        Record row = result.recordOrThrow();

        if (row != null) {

            // Process row (for example, row.getString("name"))

        }

    }

});

stream.forEach(result -> {

        Record row = result.recordOrThrow();

        // Process row (for example, row.getString("name"))

        // The row will not be null (empty rows not returned by default)

});
```

> 📖 **API reference**: [`Session.query(DataSet)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Session.html#query%28com.aerospike.client.sdk.DataSet%29) | [`ChainableQueryBuilder.where(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#where%28java.lang.String%2Cjava.lang.Object...%29) | [`ChainableQueryBuilder.execute()`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#execute%28%29) | [`RecordStream.forEach(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/RecordStream.html#forEach%28java.util.function.Consumer%29) | [`RecordResult.isOk()`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/RecordResult.html#isOk%28%29) | [`RecordResult.recordOrThrow()`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/RecordResult.html#recordOrThrow%28%29) | [`Record.getString(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Record.html#getString%28java.lang.String%29)

```python
# This query uses the "age_idx" secondary index

stream = await session.query(users).where("$.age > 21").execute()

async for row in stream:

    record = row.record_or_raise()

    pass  # Process record.bins, etc.

stream.close()
```

> 📖 **API reference**: [`Session.query()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/session.html#aerospike%5Fsdk.aio.session.Session.query) | [`QueryBuilder.where()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/query.html#aerospike%5Fsdk.aio.operations.query.QueryBuilder.where) | [`RecordResult.record_or_raise()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/record-result.html#aerospike%5Fsdk.record%5Fresult.RecordResult.record%5For%5Fraise) | [`RecordStream.close()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/record-stream.html#aerospike%5Fsdk.record%5Fstream.RecordStream.close) | [`QueryBuilder.execute()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/query.html#aerospike%5Fsdk.aio.operations.query.QueryBuilder.execute)

## Complete example

-   [Java](#tab-panel-3060)
-   [Python](#tab-panel-3061)

```java
import com.aerospike.client.sdk.AerospikeException;

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;

import com.aerospike.client.sdk.query.IndexCollectionType;

import com.aerospike.client.sdk.query.IndexType;

public class SecondaryIndexExample {

    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 = "sidx-example-1";

            String k2 = "sidx-example-2";

            String k3 = "sidx-example-3";

            String ageIndex = "age_idx_demo";

            String statusIndex = "status_idx_demo";

            // Cleanup so the example is repeatable.

            session.delete(users.ids(k1, k2, k3)).execute().close();

            try {

                session.dropIndex(users, ageIndex).waitTillComplete();

            } catch (AerospikeException ignored) {

                // Index may not exist yet.

            }

            try {

                session.dropIndex(users, statusIndex).waitTillComplete();

            } catch (AerospikeException ignored) {

                // Index may not exist yet.

            }

            // Seed sample data.

            session.insert(users)

                .bins("name", "age", "status")

                .id(k1).values("Alice", 28, "sidx_active")

                .id(k2).values("Bob", 42, "sidx_active")

                .id(k3).values("Carol", 19, "sidx_inactive")

                .execute();

            // Create secondary indexes.

            session.createIndex(users, ageIndex, "age", IndexType.INTEGER, IndexCollectionType.DEFAULT)

                .waitTillComplete();

            session.createIndex(users, statusIndex, "status", IndexType.STRING, IndexCollectionType.DEFAULT)

                .waitTillComplete();

            // Query using age index.

            System.out.println("sidx_active users age >= 30:");

            RecordStream ageQuery = session.query(users)

                .where("$.status == 'sidx_active' and $.age >= 30")

                .readingOnlyBins("name", "age")

                .execute();

            ageQuery.forEach(result -> {

                Record user = result.recordOrThrow();

                System.out.println("  - " + user.getString("name") + " (" + user.getInt("age") + ")");

            });

            // Query using status index.

            System.out.println("\nsidx_active users:");

            RecordStream statusQuery = session.query(users)

                .where("$.status == 'sidx_active'")

                .readingOnlyBins("name", "status")

                .execute();

            statusQuery.forEach(result -> {

                Record user = result.recordOrThrow();

                System.out.println("  - " + user.getString("name"));

            });

        }

    }

}
```

> 📖 **API reference**: [`ClusterDefinition(String,int)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ClusterDefinition.html#%3Cinit%3E%28java.lang.String%2Cint%29) | [`ClusterDefinition.connect()`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ClusterDefinition.html#connect%28%29) | [`Cluster.createSession(Behavior)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Cluster.html#createSession%28com.aerospike.client.sdk.policy.Behavior%29) | [`Cluster.close()`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Cluster.html#close%28%29) | [`DataSet.of(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/DataSet.html#of%28java.lang.String%2Cjava.lang.String%29) | [`DataSet.ids(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/DataSet.html#ids%28java.lang.String...%29) | [`Session.insert(DataSet)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Session.html#insert%28com.aerospike.client.sdk.DataSet%29) | [`Session.delete(List)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Session.html#delete%28java.util.List%29) | [`Session.delete(Key)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Session.html#delete%28com.aerospike.client.sdk.Key%29) | [`Session.createIndex(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Session.html#createIndex%28com.aerospike.client.sdk.DataSet%2Cjava.lang.String%2Cjava.lang.String%2Ccom.aerospike.client.sdk.query.IndexType%2Ccom.aerospike.client.sdk.query.IndexCollectionType%2Ccom.aerospike.client.sdk.cdt.CTX...%29) | [`Session.dropIndex(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Session.html#dropIndex%28com.aerospike.client.sdk.DataSet%2Cjava.lang.String%29) | [`Session.query(DataSet)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Session.html#query%28com.aerospike.client.sdk.DataSet%29) | [`OperationObjectBuilder.bins(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/OperationObjectBuilder.html#bins%28java.lang.String%2Cjava.lang.String...%29) | [`IdValuesBuilder.id(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/IdValuesBuilder.html#id%28java.lang.String%29) | [`IdValuesRowBuilder.values(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/IdValuesRowBuilder.html#values%28java.lang.Object...%29) | [`ChainableQueryBuilder.where(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#where%28java.lang.String%2Cjava.lang.Object...%29) | [`ChainableQueryBuilder.readingOnlyBins(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#readingOnlyBins%28java.lang.String...%29) | [`ChainableQueryBuilder.execute()`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableQueryBuilder.html#execute%28%29) | [`ChainableNoBinsBuilder.execute()`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/ChainableNoBinsBuilder.html#execute%28%29) | [`RecordStream.forEach(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/RecordStream.html#forEach%28java.util.function.Consumer%29) | [`RecordStream.close()`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/RecordStream.html#close%28%29) | [`RecordResult.recordOrThrow()`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/RecordResult.html#recordOrThrow%28%29) | [`Record.getString(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Record.html#getString%28java.lang.String%29) | [`Record.getInt(...)`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/Record.html#getInt%28java.lang.String%29) | [`AerospikeException`](https://javadoc.io/doc/com.aerospike/aerospike-client-sdk/latest/com/aerospike/client/sdk/AerospikeException.html)

```python
import asyncio

from aerospike_sdk import Behavior, Client, DataSet

async def main():

    async with Client("localhost:3000") as client:

        session = client.create_session(Behavior.DEFAULT)

        users = DataSet.of("test", "users")

        k1 = users.id("sidx-example-1")

        k2 = users.id("sidx-example-2")

        k3 = users.id("sidx-example-3")

        age_index = "age_idx_demo"

        status_index = "status_idx_demo"

        # Cleanup so the example is repeatable.

        stream = await session.delete(key=k1).execute()

        stream.close()

        stream = await session.delete(key=k2).execute()

        stream.close()

        stream = await session.delete(key=k3).execute()

        stream.close()

        # Seed sample data.

        await session.insert(key=k1).put({"name": "Alice", "age": 28, "status": "sidx_active"}).execute()

        await session.insert(key=k2).put({"name": "Bob", "age": 42, "status": "sidx_active"}).execute()

        await session.insert(key=k3).put({"name": "Carol", "age": 19, "status": "sidx_inactive"}).execute()

        # Create secondary indexes. If an index already exists, create() may raise.

        try:

            await session.index(dataset=users).on_bin("age").named(age_index).numeric().create()

        except Exception:

            pass

        try:

            await session.index(dataset=users).on_bin("status").named(status_index).string().create()

        except Exception:

            pass

        # Query using age index.

        print("sidx_active users age >= 30:")

        stream = await (

            session.query(users)

            .where("$.status == 'sidx_active' and $.age >= 30")

            .bins(["name", "age"])

            .execute()

        )

        async for row in stream:

            record = row.record_or_raise()

            print(f"  - {record.bins.get('name')} ({record.bins.get('age')})")

        stream.close()

        # Query using status index.

        print("\nsidx_active users:")

        stream = await (

            session.query(users)

            .where("$.status == 'sidx_active'")

            .bins(["name", "status"])

            .execute()

        )

        async for row in stream:

            record = row.record_or_raise()

            print(f"  - {record.bins.get('name')}")

        stream.close()

if __name__ == "__main__":

    asyncio.run(main())
```

> 📖 **API reference**: [`Client`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/client.html#aerospike%5Fsdk.aio.client.Client) | [`Client.create_session()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/client.html#aerospike%5Fsdk.aio.client.Client.create%5Fsession) | [`DataSet.of()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/dataset.html#aerospike%5Fsdk.dataset.DataSet.of) | [`DataSet.id()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/dataset.html#aerospike%5Fsdk.dataset.DataSet.id) | [`Behavior.DEFAULT`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/behavior.html#aerospike%5Fsdk.policy.behavior.Behavior.DEFAULT) | [`Session.query()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/session.html#aerospike%5Fsdk.aio.session.Session.query) | [`Session.insert()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/session.html#aerospike%5Fsdk.aio.session.Session.insert) | [`Session.delete()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/session.html#aerospike%5Fsdk.aio.session.Session.delete) | [`Session.index()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/session.html#aerospike%5Fsdk.aio.session.Session.index) | [`QueryBuilder.where()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/query.html#aerospike%5Fsdk.aio.operations.query.QueryBuilder.where) | [`QueryBuilder.bins()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/query.html#aerospike%5Fsdk.aio.operations.query.QueryBuilder.bins) | [`WriteSegmentBuilder.put()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/write-segment.html#aerospike%5Fsdk.aio.operations.query.WriteSegmentBuilder.put) | [`RecordResult.record_or_raise()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/record-result.html#aerospike%5Fsdk.record%5Fresult.RecordResult.record%5For%5Fraise) | [`RecordStream.close()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/record-stream.html#aerospike%5Fsdk.record%5Fstream.RecordStream.close) | [`QueryBuilder.execute()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/query.html#aerospike%5Fsdk.aio.operations.query.QueryBuilder.execute) | [`WriteSegmentBuilder.execute()`](https://aerospike-python-sdk.readthedocs.io/en/latest/api/write-segment.html#aerospike%5Fsdk.aio.operations.query.WriteSegmentBuilder.execute)

## Performance considerations

-   Indexes consume memory on every node
-   Index updates add write latency
-   Queries without indexes scan entire set (slow)
-   Monitor index memory usage in production

## Next steps

-   [Query Records](https://aerospike.com/docs/develop/client/sdk/usage/query)
-   [Aerospike Expression Language (AEL)](https://aerospike.com/docs/develop/client/sdk/concepts/ael)
-   [Data Model](https://aerospike.com/docs/develop/client/sdk/concepts/data-model)