Querying Items with Azure Cosmos DB SDK for Java

This tutorial demonstrates how to query items from your Azure Cosmos DB container using the Azure Cosmos DB SDK for Java. We'll cover various querying techniques, including point reads, complex queries with SQL, and leveraging change feed.

Prerequisite: Ensure you have a working Azure Cosmos DB account and a container with some data. If you haven't set this up, please refer to the Create an Azure Cosmos DB account and Quick start guide.

1. Setting up the SDK

First, make sure you have the Azure Cosmos DB Java SDK added to your project's dependencies. If you're using Maven, add the following to your pom.xml:

<dependency>
    <groupId>com.azure</groupId>
    <artifactId>azure-cosmos</artifactId>
    <version>4.40.0</version><!-- Check for the latest version -->
</dependency>

2. Establishing a Connection

Before querying, you need to establish a connection to your Cosmos DB endpoint. This typically involves creating a CosmosClient instance.

import com.azure.cosmos.CosmosClient;
import com.azure.cosmos.CosmosClientBuilder;
import com.azure.cosmos.CosmosDatabase;
import com.azure.cosmos.CosmosContainer;

// Replace with your actual endpoint and key
String endpoint = "YOUR_COSMOS_DB_ENDPOINT";
String key = "YOUR_COSMOS_DB_PRIMARY_KEY";
String databaseId = "YOUR_DATABASE_ID";
String containerId = "YOUR_CONTAINER_ID";

CosmosClient client = new CosmosClientBuilder()
    .endpoint(endpoint)
    .key(key)
    .buildClient();

CosmosDatabase database = client.getDatabase(databaseId);
CosmosContainer container = database.getContainer(containerId);

System.out.println("Connected to Cosmos DB.");

3. Querying with SQL

The most common way to query data in Azure Cosmos DB is by using SQL queries. The SDK provides methods to execute these queries against your container.

3.1 Basic SQL Query

To retrieve all items that match a specific condition:

import com.azure.cosmos.models.CosmosQueryRequestOptions;
import com.azure.cosmos.models.SqlParameter;
import com.azure.cosmos.models.SqlParametersList;
import com.azure.cosmos.util.CosmosPagedIterable;
import com.fasterxml.jackson.databind.JsonNode;

String query = "SELECT * FROM c WHERE c.category = @category";
CosmosQueryRequestOptions options = new CosmosQueryRequestOptions();
options.setPartitionKey(new PartitionKey("exampleCategory")); // Optional: Specify partition key for efficiency

SqlParametersList parameters = SqlParametersList.of(new SqlParameter("@category", "Electronics"));
options.setQueryMetricsEnabled(true);

CosmosPagedIterable<JsonNode> results = container.queryItems(query, parameters, JsonNode.class, options);

System.out.println("Query results:");
results.forEach(item -> {
    System.out.println(" - " + item.toString());
});

// Access query metrics if enabled
results.iterable().forEach(page -> {
    System.out.println("Request charge for page: " + page.getCosmosDiagnostics().getRequestCharge());
});

3.2 Querying with Parameters

Using parameters is highly recommended to prevent SQL injection and improve query performance.

String queryWithParams = "SELECT VALUE r.name FROM r WHERE r.status = @status AND r.priority >= @priority";
CosmosQueryRequestOptions optionsWithParams = new CosmosQueryRequestOptions();
SqlParametersList paramsList = SqlParametersList.of(
    new SqlParameter("@status", "Active"),
    new SqlParameter("@priority", 5)
);

CosmosPagedIterable<String> names = container.queryItems(queryWithParams, paramsList, String.class, optionsWithParams);

System.out.println("\nItem names with Active status and priority >= 5:");
names.forEach(name -> System.out.println(" - " + name));

3.3 Limiting Results and Pagination

You can limit the number of results returned and implement pagination to handle large datasets.

String limitQuery = "SELECT TOP 10 * FROM c";
CosmosQueryRequestOptions limitOptions = new CosmosQueryRequestOptions();
limitOptions.setLimit(10); // Not strictly needed with TOP 10 in SQL, but good practice for some scenarios

CosmosPagedIterable<JsonNode> topItems = container.queryItems(limitQuery, JsonNode.class, limitOptions);

System.out.println("\nTop 10 items:");
topItems.forEach(item -> System.out.println(" - " + item.get("id").asText()));

// Pagination example (using continuation tokens)
CosmosQueryRequestOptions paginationOptions = new CosmosQueryRequestOptions();
paginationOptions.setPageSizeHint(100); // Suggest a page size

CosmosPagedIterable<JsonNode> firstPage = container.queryItems("SELECT * FROM c", JsonNode.class, paginationOptions);

System.out.println("\nFirst page of items:");
firstPage.forEach(item -> System.out.println(" - " + item.get("id").asText()));

if (firstPage.hasMoreResults()) {
    paginationOptions.setContinuationToken(firstPage.getContinuationToken());
    CosmosPagedIterable<JsonNode> secondPage = container.queryItems("SELECT * FROM c", JsonNode.class, paginationOptions);
    System.out.println("Second page of items:");
    secondPage.forEach(item -> System.out.println(" - " + item.get("id").asText()));
}

4. Point Reads

For retrieving a single item by its ID and partition key, a point read is much more efficient than a query.

import com.azure.cosmos.models.PartitionKey;
import com.azure.cosmos.models.CosmosItemResponse;

String itemId = "your-item-id"; // The ID of the item to retrieve
String partitionKey = "your-partition-key"; // The partition key of the item

try {
    CosmosItemResponse response = container.readItem(itemId, new PartitionKey(partitionKey), JsonNode.class);
    JsonNode item = response.getItem();
    System.out.println("\nRetrieved item by ID: " + item.toString());
    System.out.println("Item ETag: " + response.getETag());
} catch (Exception e) {
    System.err.println("Error reading item: " + e.getMessage());
}

5. Change Feed Queries

The change feed allows you to track changes to items in your container. You can query for changes since a specific point in time or a continuation token.

import com.azure.cosmos.models.ChangeFeedPolicy;
import com.azure.cosmos.models.ChangeFeedOptions;

// Assuming you have established connection as before...

ChangeFeedOptions options = new ChangeFeedOptions();
options.setPageSizeHint(100); // Optional: hint for page size

// To get changes since the beginning (first time) or a specific point
// options.setStartTime(java.time.OffsetDateTime.now().minusHours(1)); // Example: last hour

CosmosPagedIterable<JsonNode> changes = container.queryChangeFeed(JsonNode.class, options);

System.out.println("\nChange feed items:");
changes.forEach(change -> {
    System.out.println(" - Changed item ID: " + change.get("id").asText());
    // You can inspect change.get("_ts") for timestamp if needed
});

// To continue reading from where you left off, use the continuation token:
// String continuationToken = changes.getContinuationToken();
// if (continuationToken != null) {
//     options.setContinuationToken(continuationToken);
//     CosmosPagedIterable<JsonNode> moreChanges = container.queryChangeFeed(JsonNode.class, options);
//     // Process moreChanges...
// }

6. Best Practices

Tip: The Azure Cosmos DB emulator is a great tool for local development and testing your queries without incurring cloud costs.

This tutorial covered the essential aspects of querying items in Azure Cosmos DB using the Java SDK. Explore the SDK documentation for more advanced querying capabilities and options.

Azure Cosmos DB, Java SDK, Query Items, SQL Query, Point Read, Change Feed, Azure Documentation, Tutorial, Database