

# Amazon DocumentDB Java programming guide
<a name="docdb-java-pg"></a>

This comprehensive guide provides a detailed walk through for working with Amazon DocumentDB using MongoDB’s Java drivers, covering essential aspects of database operations and management.

**Topics**
+ [

## Introduction
](#java-pg-intro)
+ [

## Prerequisites
](#java-pg-prereqs)
+ [Data models](#java-pg-data-models)
+ [Connecting with a Java driver](java-pg-connect-mongo-driver.md)
+ [CRUD operations with Java](java-crud-operations.md)
+ [Index management with Java](index-management-java.md)
+ [Event-driven programming](event-driven-programming.md)

## Introduction
<a name="java-pg-intro"></a>

The guide begins with connectivity, explaining how to establish secure connections to DocumentDB clusters using the MongoDB Java driver. It details the connection string components, SSL/TLS implementation, and various connection options including IAM authentication and connection pooling, along with robust error handling strategies.

In the CRUD (create, read, update, delete) operations section, the guide thoroughly covers document manipulation, demonstrating how to create, read, update, and delete documents using both single and bulk operations. It explains the usage of filters, queries, and various operation options, while emphasizing best practices for error handling and implementing retry logic for improved reliability. The guide also extensively covers index management, detailing the creation and maintenance of different index types including single-field, compound, sparse, and text indexes. It explains how to optimize query performance through proper index selection and usage of the `explain()` function to analyze query execution plans.

The final section focuses on event-driven programming using Amazon DocumentDB's change streams, demonstrating how to implement real-time data change monitoring in Java applications. It covers the implementation of change stream cursors, handling of resume tokens for continuous operation, and time-based operations for historical data processing. Throughout the guide, practical code examples and best practices are provided, making it an invaluable resource for you when building robust Java applications with Amazon DocumentDB.

## Prerequisites
<a name="java-pg-prereqs"></a>

Before you begin, make sure you have the following:
+ An Amazon account with a configured DocumentDB cluster. See this [getting started blog post](https://aws.amazon.com/blogs/database/part-1-getting-started-with-amazon-documentdb-using-amazon-ec2/) for DocumentDB cluster setup.
+ Java Development Kit (JDK) installed (we will be using [Amazon Corretto 21](https://docs.amazonaws.cn/corretto/latest/corretto-21-ug/downloads-list.html) for this guide).
+ Maven for dependency management.

## Data models for this guide
<a name="java-pg-data-models"></a>

All the example code in this guide assumes a connection to a “ProgGuideData” test database that has a “Restaurants” collection. All the sample codes in this guide work on a restaurant listing system and below is an example of what a document in this system looks like:

```
{
    "_id": "ab6ad8f119b5bca3efa2c7ae",
    "restaurantId": "REST-CRT9BL",
    "name": "Thai Curry Palace",
    "description": "Amazing Restaurant, must visit",
    "cuisine": "Thai",
    "address": {
        "street": "914 Park Street",
        "city": "Bryan",
        "state": "AL",
        "zipCode": "96865",
        "location": {
            "type": "Point",
            "coordinates": [-25.4619, 8.389]
        }
    },
    "contact": {
        "phone": "(669) 915-9056 x6657"
    },
    "rating": {
        "average": 3.4,
        "totalReviews": 275
    },
    "priceRange": "$",
    "menu": [{
        "category": "Appetizers",
        "items": [{
            "name": "Buffalo Chicken Wings",
            "price": 13.42
        }]
    }],
    "features": [
        "Private Dining"
    ],
    "isActive": false“ michelin”: {“
        star”: 3,
        “ranking_years”: 4
    }
}
```

All code samples showing CRUD, index management, and event-driven programming assumes that you have a [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoClient.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoClient.html) object `dbClient`, a [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoDatabase.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoDatabase.html) object `connectionDB`, and a [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCollection.html#find()](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCollection.html#find()) object `collection`.

**Note**  
All code examples in this guide have been tested with MongoDB Java driver version 5.3.0.

# Connecting to Amazon DocumentDB with a MongoDB Java driver
<a name="java-pg-connect-mongo-driver"></a>

This section provides a step-by-step guide for connecting to Amazon DocumentDB using Java drivers. This will get you started with integrating DocumentDB into your Java applications.

**Topics**
+ [

## Step 1: Set up your project
](#step1-set-up)
+ [

## Step 2: Create the connection string
](#step2-create-connection-string)
+ [

## Step 3: Write the connection code
](#step3-write-connect-code)
+ [

## Step 4: Handle connection exceptions
](#step4-handle-connect-exceptions)
+ [

## Step 5: Running the code
](#step5-running-code)
+ [

## Connection best practices
](#java-connect-best-practices)

## Step 1: Set up your project
<a name="step1-set-up"></a>

1. Using Maven, create java project:

   ```
   mvn archetype:generate -DgroupId=com.docdb.guide -DartifactId=my-docdb-project -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
   ```

1. Add the MongoDB Java driver as a dependency for the project in your ‘pom.xml’ file:

   ```
   <dependency>
       <groupId>org.mongodb</groupId>
       <artifactId>mongodb-driver-sync</artifactId> 
       <version>5.3.0</version> 
   </dependency>
   ```

## Step 2: Create the connection string
<a name="step2-create-connection-string"></a>

Amazon DocumentDB connection string is essential for establishing a connection between your application and your DocumentDB cluster. This string encapsulates crucial information such as the cluster endpoint, port, authentication details, and various connection options. To build a DocumentDB connection string, you typically start with the basic format:

```
"mongodb://username:password@cluster-endpoint:port/?[connection options]"
```

You'll need to replace "username" and "password" with your actual credentials. You can find your cluster's endpoint and port number in the Amazon Web Services Management Console as well as through the Amazon CLI. See [Finding a cluster's endpoints](db-cluster-endpoints-find.md) to find the cluster endpoint for your cluster. The default port for DocumentDB is 27017.

**Connection string examples**
+ Making a connection to DocumentDB using encryption in transit and making sure read requests go to read replicas and write to primary:

  ```
  "mongodb://username:password@cluster-endpoint:27017/?tls=true& 
     tlsCAFile=global-bundle.pem& 
     readPreference=secondaryPreferred&
     retryWrites=false"
  ```
+ Making a connection to DocumentDB using IAM authentication:

  ```
  "mongodb://cluster-endpoint:27017/?tls=true& 
     tlsCAFile=global-bundle.pem& 
     readPreference=secondaryPreferred&
     retryWrites=false&
     authSource=%24external&
     authMechanism=MONGODB-AWS"
  ```

The different options available for the connection string are as follows:
+ [TLS certificate](#connection-string-tls)
+ [Reading from read replicas](#connection-string-read-rep)
+ [Write concern and journaling](#connection-string-write-journal)
+ [RetryWrites](#connection-string-retry-writes)
+ [IAM authentication](#connection-string-iam-auth)
+ [Connection pool](#connection-string-pool)
+ [Connection timeout parameters](#connection-string-timeout)

### TLS certificate
<a name="connection-string-tls"></a>

**`tls=true|false`** — This option enables or disables Transport Layer Security (TLS). By default, encryption in transit is enabled on Amazon DocumentDB cluster and therefore, unless TLS is disabled at the cluster level, the value for this option should be `true`.

When using TLS, the code needs to provide an SSL certificate when creating connection to a DocumentDB cluster. Download the certificate that is required to make the secure connection to the cluster: [https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem](https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem). There are two ways to use the `global-bundle.pem` file.
+ **Option 1** — Extract all the certificates from the `global-bundle.pem` file and use Java’s keytool to store them in a `.jks` file that can be later used in the code. Refer to the Java tab in [Connecting with TLS enabled](connect_programmatically.md#connect_programmatically-tls_enabled) for the script that shows how to do this.
+ **Option 2** — Dynamically add the `global-bundle.pem` file in the code, build an in-memory keystore and use `SSLContext` to provide the certificate as part of making the connection.

### Reading from read replicas
<a name="connection-string-read-rep"></a>

**`replicaSet=rs0&readPreference=secondaryPreferred`** — Specifying these two options routes all the read requests to the read replicas, and write requests to the primary instance. Using `replicaSet=rs0` in the connection string enables the MongoDB driver to maintain an automatically updated view of the cluster topology, allowing applications to maintain visibility of current node configurations as instances are added or removed. Not providing these options or specifying `readPreference=primary` sends all the reads and writes to primary instance. For more options for `readPreference`, see [Read preference options](how-it-works.md#durability-consistency-isolation).

### Write concern and journaling
<a name="connection-string-write-journal"></a>

Write concern determines the level of acknowledgment requested from the database for write operations. MongoDB drivers provide an option to tune write concern and journal files. Amazon DocumentDB doesn’t expect you to set write concern and journal, and ignores the values sent for `w` and `j` (`writeConcern` and `journal`). DocumentDB always writes data with `writeConcern`: `majority` and `journal`: `true` so the writes are durably recorded on a majority of nodes before sending an acknowledgement to the client.

### RetryWrites
<a name="connection-string-retry-writes"></a>

**`retryWrites=false`** — DocumentDB does not support retryable writes and therefore this attribute should always be set to `false`.

### IAM authentication
<a name="connection-string-iam-auth"></a>

**`authSource=%24external` and `authMechanism=MONGODB-AWS`** — These two parameters are used to authenticate using Amazon Identity and Access Management. IAM authentication is currently only available in instance-based cluster version 5.0. For more information, see [Authentication using IAM identity](iam-identity-auth.md).

### Connection pool
<a name="connection-string-pool"></a>

These options are available for connection pooling:
+ **`maxPoolSize`** — Sets the maximum number of connections that can be created in the pool. When all connections are in use and a new request comes in, it waits for a connection to become available. The default for MongoDB Java drivers is 100.
+ **`minPoolSize`** — Indicates the minimum number of connections that should be maintained in the pool at all times. The default for MongoDB Java drivers is 0.
+ **`maxIdleTimeMS`** — Determines how long a connection can remain idle in the pool before being closed and removed. The default for MongoDB Java drivers is 100 milliseconds.
+ **`waitQueueTimeoutMS`** — Configures how long a thread should wait for a connection to become available when the pool is at its maximum size. If a connection doesn't become available within this time, an exception is thrown. The default value for MongoDB Java drivers is 120,000 milliseconds (2 minutes).

### Connection timeout parameters
<a name="connection-string-timeout"></a>

Timeout is a mechanism to limit the amount of time an operation or connection attempt can take before it is considered failed. The following timeout parameters are available for preventing indefinite waits and managing resource allocation:
+ **`connectTimeoutMS`** — Configures how long the driver will wait to establish a connection to the cluster. The default is 10,000 milliseconds (10 seconds).
+ **`socketTimeoutMS`** — Specifies how long the driver will wait for a response from the server for a non-write operation. The default is 0, (no timeout or infinite).
+ **`serverSelectionTimeoutMS`** — Species how long the driver will wait to find an available server in the cluster. The default value for this setting is 30 seconds and is sufficient for a new primary instance to be elected during failover.

## Step 3: Write the connection code
<a name="step3-write-connect-code"></a>

The following code example shows how to make a TLS connection to Amazon DocumentDB:
+ It creates Java’s [https://docs.oracle.com/javase/8/docs/api/java/security/KeyStore.html](https://docs.oracle.com/javase/8/docs/api/java/security/KeyStore.html) and [`SSLContext`>](https://docs.oracle.com/javase/8/docs/api/javax/net/ssl/SSLContext.html) objects.
+ It also creates the [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/MongoClientSettings.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/MongoClientSettings.html) object by passing it to the [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/ConnectionString.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/ConnectionString.html) object. To make TLS connection, you must use the `MongoClientSettings` object to bind the `connectionstring` and `sslcontext`.
+ Using [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoClients.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoClients.html) gets a [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoClient.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoClient.html) object.

```
public static MongoClient makeDbConnection(String dbName, String DbUserName, String DbPassword,
    String DbClusterEndPoint, String keyStorePass) throws Exception {
    MongoClient connectedClient;
    String connectionOptions = "?replicaSet=rs0&readPreference=secondaryPreferred&retryWrites=false";
    String connectionUrl = "mongodb://" + DbUserName + ":" + DbPassword + "@" + DbClusterEndPoint + ":27017/" +
        dbName + connectionOptions;

    try {
        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        try (FileInputStream fis = new FileInputStream("src/main/resources/certs/truststore.jks")) {
            trustStore.load(fis, keyStorePass.toCharArray());
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            tmf.init(trustStore);

            SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
            sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());
            ConnectionString connectionString = new ConnectionString(connectionUrl);
            MongoClientSettings settings = MongoClientSettings.builder()
                .applyConnectionString(connectionString)
                .applyToSslSettings(builder - > {
                    builder.enabled(true);
                    builder.context(sslContext);
                })
                .build();
            connectedClient = MongoClients.create(settings);
        }
        return connectedClient;
    } catch (MongoException e5) {
        throw new RuntimeException(e5);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}
```

## Step 4: Handle connection exceptions
<a name="step4-handle-connect-exceptions"></a>

When working with DocumentDB in Java applications, handling connection exceptions is crucial for maintaining robust and reliable database operations. Properly managing, these exceptions not only help in diagnosing issues quickly, but also ensure that your application can gracefully handle temporary network disruptions or server unavailability, leading to improved stability and user experience. Some of the critical exceptions related to establishing connection include:
+ **`MongoException`** — A general exception and could be issued in various scenarios not covered by more specific exceptions. Make sure this exception is handled after all the other specific exceptions as this is a general catch all MongoDB exception.
+ **`MongoTimeoutException`** — Issued when an operation times out. For example, querying a nonexistent cluster endpoint.
+ **`MongoSocketException`** — Issued for network-related issues. For example, sudden network disconnection during an operation. 
+ **`MongoSecurityException`** — Issued when authentication fails. For example, connecting with incorrect credentials. 
+ **`MongoConfigurationException`** — Issued when there is an error in the client configuration. For example, using an invalid connection string.

## Step 5: Running the code
<a name="step5-running-code"></a>

The following code sample creates an Amazon DocumentDB connection and prints all the databases:

```
public static void TestConnection() {
    try (MongoClient mongoClient = makeDbConnection(DATABASE_NAME, DB_USER_NAME, DB_PASSWORD, DB_CLUSTER_ENDPOINT, KEYSTORE_PASSWORD)) {
        List < String > databases = mongoClient.listDatabaseNames().into(new ArrayList < > ());
        System.out.println("Databases: " + databases);
    } catch (MongoException e) {
        System.err.println("MongoDB error: " + e.getMessage());
        throw new RuntimeException(e);
    }
}
```

## Connection best practices
<a name="java-connect-best-practices"></a>

The following are best practices to consider when connecting to Amazon DocumentDB with a MongoDB Java driver:
+ Always close your [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoClient.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoClient.html) when you no longer need the client to release resources.
+ Handle exceptions appropriately and implement proper error logging.
+ Use environment variables or Amazon Secrets Manager to store sensitive information like usernames and passwords.

# Performing CRUD operations in Amazon DocumentDB with Java
<a name="java-crud-operations"></a>

This section discusses performing CRUD (create, read, update, delete) operation in Amazon DocumentDB using MongoDB Java drivers.

**Topics**
+ [

## Creating and inserting documents in a DocumentDB collection
](#creating-inserting)
+ [

## Reading and retrieving data from a DocumentDB collection
](#reading-retrieving)
+ [

## Updating existing documents in a DocumentDB collection
](#updating-documents)
+ [

## Removing documents from a DocumentDB collection
](#deleting-documents)
+ [

## Error handling with retry logic
](#error-handling)

## Creating and inserting documents in a DocumentDB collection
<a name="creating-inserting"></a>

Inserting documents into Amazon DocumentDB allows you to add new data to your collections. There are several ways to perform insertions, depending on your needs and the volume of data you're working with. The most basic method for inserting an individual document to the collection is `insertOne()`. For inserting multiple documents at once, you can use the `insertMany()` method, which allows you to add an array of documents in a single operation. Another method for inserting many documents in a DocumentDB collection is `bulkWrite()`. In this guide, we discuss all of these methods for creating documents in a DocumentDB collection.

**`insertOne()`**

Let's begin by examining how to insert an individual document into an Amazon DocumentDBB collection. Inserting a single document is accomplished by using the `insertOne()` method. This method takes a [BsonDocument](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/bson/org/bson/BsonDocument.html) for insertion and returns an [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/result/InsertOneResult.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/result/InsertOneResult.html) object that can be used to get the object id of the new inserted document. The example code below shows inserting one restaurant document into the collection:

```
Document article = new Document()
    .append("restaurantId", "REST-21G145")
    .append("name", "Future-proofed Intelligent Bronze Hat")
    .append("cuisine", "International")
    .append("rating", new Document()
        .append("average", 1.8)
        .append("totalReviews", 267))
    .append("features", Arrays.asList("Outdoor Seating", "Live Music"));

try {
    InsertOneResult result = collection.insertOne(article);
    System.out.println("Inserted document with the following id: " + result.getInsertedId());
} catch (MongoWriteException e) {
    // Handle duplicate key or other write errors
    System.err.println("Failed to insert document: " + e.getMessage());
    throw e;
} catch (MongoException e) {
    // Handle other MongoDB errors
    System.err.println("MongoDB error: " + e.getMessage());
    throw e;
}
```

When using `insertOne()`, make sure to include appropriate error handling. For instance, in the above code, “`restaurantId`” has a unique index and therefore running this code again will raise the following `MongoWriteException`:

```
Failed to insert document: Write operation error on server docdbCluster.docdb.amazonaws.com:27017. 
Write error: WriteError{code=11000, message='E11000 duplicate key error collection: Restaurants index: restaurantId_1', details={}}.
```

**insertMany()**

The primary methods used for inserting many document into a collection are insertMany() and `bulkWrite()`. 

The `insertMany()` method is the simplest way to insert multiple documents in a single operation. It accepts a list of documents and inserts them into the collection. This method is ideal when you're inserting a batch of new documents that are independent of each other and don't require any special processing or mixed operations. The following code shows reading JSON documents from a file and inserting them into the collection. The `insertMany()` function returns an [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/result/InsertManyResult.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/result/InsertManyResult.html)`InsertManyResult` object that can be used to get the IDs of all the inserted documents.

```
// Read JSON file content
String content = new String(Files.readAllBytes(Paths.get(jsonFileName)));
JSONArray jsonArray = new JSONArray(content);

// Convert JSON articles to Documents
List < Document > restaurants = new ArrayList < > ();
for (int i = 0; i < jsonArray.length(); i++) {
    JSONObject jsonObject = jsonArray.getJSONObject(i);
    Document doc = Document.parse(jsonObject.toString());
    restaurants.add(doc);
}
//insert documents in collection
InsertManyResult result = collection.insertMany(restaurants);

System.out.println("Count of inserted documents: " + result.getInsertedIds().size());
```

**[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/bulk/package-summary.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/bulk/package-summary.html)**

The `bulkWrite()` method allows to perform multiple write operations (insert, update, delete) in a single batch. You can use `bulkWrite()` when you need to perform different types of operations in a single batch, such as inserting some documents while updating others. `bulkWrite()` support two types of batch write, ordered and unordered:
+ *Ordered operations * — (default) Amazon DocumentDB processes the write operations sequentially, and stops at the first error it encounters. This is useful when the order of operations matters, such as when later operations depend on earlier ones. However, ordered operations are generally slower then unordered operations. With ordered operations, you must address the case where the batch stops at the first error, potentially leaving some operations unprocessed.
+ *Unordered operations * — Allows Amazon DocumentDB to process inserts as a single execution in the database. If an error occurs with one document, the operation continues with the remaining documents. This is particularly useful when you're inserting large amounts of data and can tolerate some failures, such as during data migration or bulk imports where some documents might fail due to duplicate keys. With unordered operations, you must address partial success scenarios where some operations succeed while others fail.

When working with the `bulkWrite()` method, there are some essential classes that are required. First, the [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/WriteModel.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/WriteModel.html) class serves as the base class for all write operations and with specific implementations like [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/InsertOneModel.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/InsertOneModel.html), [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/UpdateOneModel.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/UpdateOneModel.html), [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/UpdateManyModel.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/UpdateManyModel.html), [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/DeleteOneModel.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/DeleteOneModel.html), and [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/DeleteManyModel.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/DeleteManyModel.html) handling different types of operations.

The [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/BulkWriteOptions.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/BulkWriteOptions.html) class is necessary for configuring the behavior of bulk operations, such as setting ordered/unordered execution or bypassing document validation. The [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/bulk/BulkWriteResult.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/bulk/BulkWriteResult.html) class provides detailed information about the execution results, including counts of inserted, updated, and deleted documents.

For error handling, the [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/MongoBulkWriteException.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/MongoBulkWriteException.html) class is crucial as it contains information about any failures during the bulk operation, while the [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/bulk/BulkWriteError.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/bulk/BulkWriteError.html) class provides specific details about individual operation failures. The following code shows an example of inserting a list of documents, as well as updating and deleting a single document, all within the execution of single `bulkWrite()` method call. The code also shows how to work with [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/BulkWriteOptions.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/BulkWriteOptions.html) and [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/bulk/BulkWriteResult.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/bulk/BulkWriteResult.html), as well as proper error handling of the `bulkWrite()` operation. 

```
List < WriteModel < Document >> bulkOperations = new ArrayList < > ();
// get list of 10 documents representing 10 restaurants
List < Document > restaurantsToInsert = getSampleData();

for (Document doc: restaurantsToInsert) {
    bulkOperations.add(new InsertOneModel < > (doc));
}
// Update operation
bulkOperations.add(new UpdateOneModel < > (
    new Document("restaurantId", "REST-Y2E9H5"),
    new Document("", new Document("stats.likes", 20))
    .append("", new Document("rating.average", 4.5))));
// Delete operation
bulkOperations.add(new DeleteOneModel < > (new Document("restaurantId", "REST-D2L431")));

// Perform bulkWrite operation
try {
    BulkWriteOptions options = new BulkWriteOptions()
        .ordered(false); // Allow unordered inserts

    BulkWriteResult result = collection.bulkWrite(bulkOperations, options);

    System.out.println("Inserted: " + result.getInsertedCount());
    System.out.println("Updated: " + result.getModifiedCount());
    System.out.println("Deleted: " + result.getDeletedCount());
} catch (MongoBulkWriteException e) {
    System.err.println("Bulk write error occurred: " + e.getMessage());
    // Log individual write errors
    for (BulkWriteError error: e.getWriteErrors()) {
        System.err.printf("Error at index %d: %s (Code: %d)%n", error.getIndex(), error.getMessage(),
            error.getCode());

        // Log the problematic document
        Document errorDoc = new Document(error.getDetails());
        if (errorDoc != null) {
            System.err.println("Problematic document: " + errorDoc);
        }
    }
} catch (Exception e) {
    System.err.println("Error during bulkWrite: " + e.getMessage());
}
```

**Retryable writes**

Unlike MongoDB, Amazon DocumentDB doesn't support retryable writes. As a result, you must implement custom retry logic in their applications, particularly for handling network issues or temporary service unavailability. A well-implemented retry strategy, typically, involves increasing the delay between retry attempts and limiting the total number of retries. See [Error handling with retry logic](#error-handling) below for a code sample of building retry logic with error handling.

## Reading and retrieving data from a DocumentDB collection
<a name="reading-retrieving"></a>

Querying documents in Amazon DocumentDB revolves around several key components that allow you to precisely retrieve and manipulate data. The [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCollection.html#find()](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCollection.html#find()) method is the fundamental querying APIs in MongoDB Java drivers. It allows for complex data retrieval with numerous options for filtering, sorting, and projecting results. Besides the `find()` method, [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/Filters.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/Filters.html) and [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/FindIterable.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/FindIterable.html) are two other fundamental components that provide the building blocks for query operations in MongoDB Java drivers.

The `Filters` class is a utility class in the MongoDB Java driver that provides a fluent API for constructing query filters. This class offers static factory methods that create instances of `Bson` objects representing various query conditions. The most commonly used methods include `eq()` for equality comparisons, `gt()`, `lt()`, `gte()`, and `lte()` for numeric comparisons, `and()` and `or()` for combining multiple conditions, `in()` and `nin()` for array membership tests, and `regex()` for pattern matching. The class is designed to be type-safe and provides better compile-time checking compared to raw document-based queries, making it the preferred approach for constructing DocumentDB queries in Java applications. Error handling is robust, with clear exceptions thrown for invalid filter constructions.

`FindIterable` is a specialized interface designed to handle the result of the `find()` method. It provides a rich set of methods for refining and controlling query execution, offering a fluent API for method chaining. The interface includes essential query modification methods such as `limit()` for restricting the number of returned documents, `skip()` for pagination, `sort()` for ordering results, `projection()` for selecting specific fields, and `hint()` for index selection. The batch, skip, and limit operations in `FindIterable` are essential pagination and data management tools that help control how documents are retrieved and processed from the database.

Batching (`batchSize`) controls how many documents DocumentDB returns to the client in a single network round trip. When you set a batch size, DocumentDB doesn't return all matching documents at once but instead returns them in groups of the specified batch size. 

Skip allows you to offset the starting point of your results, essentially telling DocumentDB to skip over a specified number of documents before beginning to return matches. For example, `skip(20)` will bypass the first 20 matching documents. This is commonly used in pagination scenarios where you want to retrieve subsequent pages of results. 

Limit restricts the total number of documents that can be returned from a query. When you specify `limit(n)`, DocumentDB will stop returning documents after it has returned 'n' documents, even if there are more matches in the database. 

`FindIterable` supports both iterator and cursor patterns when retrieving documents from Amazon DocumentDB. The benefit of using `FindIterable` as iterator is that it allows lazy loading of documents and only fetches documents when requested by the application. Another benefit of using iterator is that you are not responsible for maintaining the connection to the cluster and thus no explicit closing of the connection is required. 

`FindIterable` also provide support for [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCursor.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCursor.html) which allows cursor patterns to be used when working with Amazon DocumentDB queries. `MongoCursor` is a MongoDB Java driver-specific implementation that provides control over database operations and resource management. It implements the `AutoCloseable` interface, allowing for explicit resource management through try-with-resources blocks, which is crucial for properly closing database connections and freeing server resources. By default, the cursor times out in 10 minutes and DocumentDB does not give you the option of changing this time out behavior. When working with batched data, make sure to retrieve the next batch of data before the cursor times out. One key consideration when using `MongoCursor` is that it requires explicit closing to prevent resource leaks.

In this section, several examples are presented for `find()`, `Filters` and `FindIterable`.

The following code example shows how to use `find()` to retrieve a single document using its “restaurantId” field:

```
Document filter = new Document("restaurantId", "REST-21G145");
Document result = collection.find(filter).first();
```

Even though using `Filters` allows for better compile time error checking, the java driver also allows you to specify a `Bson` filter directly in the `find()` method. The following example code passes `Bson` document to `find()`:

```
result = collection.find(new Document("$and", Arrays.asList(
    new Document("rating.totalReviews", new Document("$gt", 1000)),
    new Document("priceRange", "$$"))))
```

The next example code shows several examples of using the `Filters` class with `find()`:

```
FindIterable < Document > results;

// Exact match
results = collection.find(Filters.eq("name", "Thai Curry Palace"));

// Not equal
results = collection.find(Filters.ne("cuisine", "Thai"));

// find an element in an array
results = collection.find(Filters.in("features", Arrays.asList("Private Dining")));

// Greater than
results = collection.find(Filters.gt("rating.average", 3.5));

// Between (inclusive)
results = collection.find(Filters.and(
    Filters.gte("rating.totalReviews", 100),
    Filters.lte("rating.totalReviews", 200)));
// AND
results = collection.find(Filters.and(
    Filters.eq("cuisine", "Thai"),
    Filters.gt("rating.average", 4.5)));

// OR
results = collection.find(Filters.or(
    Filters.eq("cuisine", "Thai"),
    Filters.eq("cuisine", "American")));


// All document where the Field exists
results = collection.find(Filters.exists("michelin"));

// Regex
results = collection.find(Filters.regex("name", Pattern.compile("Curry", Pattern.CASE_INSENSITIVE)));

// Find all document where the array contain the list of value regardless of its order
results = collection.find(Filters.all("features", Arrays.asList("Private Dining", "Parking")));

// Array size
results = collection.find(Filters.size("features", 4));
```

The following example shows how to chain the operations of `sort()`, `skip()`, `limit()`, and `batchSize()` on a `FindIterable` object. The order of how these operations is provided will influence the performance of your query. As a best practice, the order of these operations should be `sort()`, `projection()`, `skip()`, `limit()` and `batchSize()`.

```
FindIterable < Document > results = collection.find(Filters.gt("rating.totalReviews", 1000))
    // Sorting
    .sort(Sorts.orderBy(
        Sorts.descending("address.city"),
        Sorts.ascending("cuisine")))
    // Field selection
    .projection(Projections.fields(
        Projections.include("name", "cuisine", "priceRange"),
        Projections.excludeId()))

    // Pagination
    .skip(20)
    .limit(10)
    .batchSize(2);
```

The following example code shows creating an iterator on `FindIterable`. It uses Java’s `forEach` construct to traverse through the result set.

```
collection.find(Filters.eq("cuisine", "American")).forEach(doc -> System.out.println(doc.toJson()));
```

In the last `find()` code example, it shows how to use `cursor()` for document retrieval. It creates the cursor in the try block which ensures that the cursor will get closed when the code exits the try block.

```
try (MongoCursor < Document > cursor = collection.find(Filters.eq("cuisine", "American"))
    .batchSize(25)
    .cursor()) {
    while (cursor.hasNext()) {
        Document doc = cursor.next();
        System.out.println(doc.toJson());
    }
} // Cursor automatically closed
```

## Updating existing documents in a DocumentDB collection
<a name="updating-documents"></a>

Amazon DocumentDB provides flexible and powerful mechanisms for modifying existing documents and inserting new ones when they don't exist. The MongoDB Java driver offers several methods for updates: `updateOne()` for single document updates, `updateMany()` for multiple document updates, and `replaceOne()` for complete document replacement. Beside these three methods, [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/Updates.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/Updates.html), [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/UpdateOptions.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/UpdateOptions.html), and [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/result/UpdateResult.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/result/UpdateResult.html) are other fundamental components that provide the building blocks for update operations in MongoDB Java drivers. 

The `Updates` class in the MongoDB Java driver is a utility class that provides static factory methods for creating update operators. It serves as the primary builder for constructing update operations in a type-safe and readable manner. Basic methods like `set()`, `unset()`, and `inc()` allows direct modification of the documents. The power of this class becomes evident when combining multiple operations using the `Updates.combine()` method which allows multiple update operations to be executed atomically, ensuring data consistency.

`UpdateOptions` is a powerful configuration class in MongoDB's Java driver that provides essential customization capabilities for document update operations. Two important aspects of this class is providing upsert and array filter support for update operations. The upsert feature, enabled through `upsert(true)`, allows for the creation of new documents when no matching documents are found during an update operation. Through `arrayFilters()`, the update operation can precisely update array elements that meet specific criteria.

`UpdateResult` in MongoDB's Java driver provides the feedback mechanism detailing the outcome of an update operation. This class encapsulates three key metrics: the number of documents matched by the update criteria (`matchedCount`), the number of documents actually modified (`modifiedCount`), and information about any upserted documents (`upsertedId`). Understanding these metrics is essential for proper error handling, verification of update operations, and maintaining data consistency in applications.

### Update and replace a single document
<a name="update-single-doc"></a>

In DocumentDB, updating a single document can be accomplished using the updateOne() method. This method take a filter parameter, usually provided by the `Filters` class, to identify the document to be updated, an `Updat`e parameter that determines which field(s) to be updated, and an optional `UpdateOptions` parameter to set different options for the update. Using the `updateOne()` method will only update the first document that matches the selection criteria. The following example code updates a single field of one document:

```
collection.updateOne(Filters.eq("restaurantId", "REST-Y2E9H5"),
    Updates.set("name", "Amazing Japanese sushi"));
```

To update multiple fields in one document, use `updateOne()` with `Update.combine()` as shown in the following example. This example also shows how to add an item to an array in the document.

```
List<Bson> updates = new ArrayList<>();
// Basic field updates
updates.add(Updates.set("name", "Shanghai Best"));
// Array operations
updates.add(Updates.addEachToSet("features", Arrays.asList("Live Music")));
// Counter updates
updates.add(Updates.inc("rating.totalReviews", 10));
// Combine all updates
Bson combinedUpdates = Updates.combine(updates);
// Execute automic update with one call
collection.updateOne(Filters.eq("restaurantId","REST-1J83NH"), combinedUpdates);
```

The following code example demonstrates how to update a document in the database. If the specified document doesn't exist, the operation will automatically insert it as a new document instead. This code also shows how to use the metrics available via the `UpdateResult` object.

```
Bson filter = Filters.eq("restaurantId", "REST-0Y9GL0");
Bson update = Updates.set("cuisine", "Indian");
// Upsert operation
UpdateOptions options = new UpdateOptions().upsert(true);
UpdateResult result = collection.updateOne(filter, update, options);

if (result.getUpsertedId() != null) {
   	System.out.println("Inserted document with _id: " + result.getUpsertedId());
} else {
    	System.out.println("Updated " + result.getModifiedCount() + " document(s)");
}
```

The following code example demonstrates how to completely replace an existing document with a new document using the `replaceOne()` method, rather than updating individual fields. The `replaceOne()` method overwrites the entire document, preserving only the `_id` field of the original. If multiple documents match the filter criteria, only the first encountered document is replaced.

```
Document newDocument = new Document()
                .append("restaurantId", "REST-0Y9GL0")
                .append("name", "Bhiryani Adda")
                .append("cuisine", "Indian")
                .append("rating", new Document()
                        .append("average", 4.8)
                        .append("totalReviews", 267))
                .append("features", Arrays.asList("Outdoor Seating", "Live Music"));

UpdateResult result = collection.replaceOne(
                    Filters.eq("restaurantId", "REST-0Y9GL0"),
                    newDocument);
System.out.printf("Modified %d document%n", result.getModifiedCount());
```

### Update multiple documents
<a name="update-multiple-docs"></a>

There are two ways to update multiple document in a collection simultaneously. You can use the `updateMany()` method or use the [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/UpdateManyModel.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/UpdateManyModel.html) with the `bulkWrite()` method. The `updateMany()` method uses a filter parameter to select documents for update, the `Update` parameter to identify the fields to be updated, and an optional `UpdateOptions` parameter to specify update options.

The following example code demonstrates the usage of the `updateMany()` method:

```
Bson filter = Filters.and(
    Filters.in("features", Arrays.asList("Private Dining")),
    Filters.eq("cuisine", "Thai"));
UpdateResult result1 = collection.updateMany(filter, Updates.set("priceRange", "$$$"));
```

The following example code demonstrates the `bulkWrite()` method using the same update:

```
BulkWriteOptions options = new BulkWriteOptions().ordered(false);
List < WriteModel < Document >> updates = new ArrayList < > ();
Bson filter = Filters.and(
    Filters.in("features", Arrays.asList("Private Dining")),
    Filters.eq("cuisine", "Indian"));
Bson updateField = Updates.set("priceRange", "$$$");
updates.add(new UpdateManyModel < > (filter, updateField));
BulkWriteResult result = collection.bulkWrite(updates, options);
System.out.printf("Modified %d document%n", result.getModifiedCount());
```

## Removing documents from a DocumentDB collection
<a name="deleting-documents"></a>

The MongoDB Java driver offers `deleteOne()` for removing a single document and `deleteMany()` for removing multiple documents that match specific criteria. Just like update, delete operation can also be used with the `bulkWrite()` method. Both `deleteOne()` and `deleteMany()` return a [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/result/DeleteResult.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/result/DeleteResult.html) object that provides information about the operation's outcome, including the count of documents deleted. The following is an example of using `deleteMany()` to remove multiple documents:

```
Bson filter = Filters.and(
    Filters.eq("cuisine", "Thai"),
    Filters.lt("rating.totalReviews", 50));
DeleteResult result = collection.deleteMany(filter);
System.out.printf("Deleted %d document%n", result.getDeletedCount());
```

## Error handling with retry logic
<a name="error-handling"></a>

A robust error handling strategy for Amazon DocumentDB should implement categorization of errors into retryable (like network timeouts, connection issues) and non-retryable (like authentication failures, invalid queries). For operation failures due of errors that should be retried, it should implement a time delay between each retry as well as max retry attempts. The CRUD operations should be in a try-catch block that catches [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/MongoException.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/MongoException.html) and its subclasses. Additionally, it should include monitoring and logging of errors for operational visibility. The following is sample code that shows how to implement retry error handling:

```
int MAX_RETRIES = 3;
int INITIAL_DELAY_MS = 1000;
int retryCount = 0;

while (true) {
    try {
        crud_operation(); //perform crud that will throw MongoException or one of its subclass
        break;
    } catch (MongoException e) {
        if (retryCount < MAX_RETRIES) {
            retryCount++;
            long delayMs = INITIAL_DELAY_MS * (long) Math.pow(2, retryCount - 1);
            try {
                TimeUnit.MILLISECONDS.sleep(delayMs);
            } catch (InterruptedException t) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("Retry interrupted", t);
            }
            continue;
        } else
            throw new RuntimeException("Crud operation failed", e);
    }
}
```

# Index management in Amazon DocumentDB with Java
<a name="index-management-java"></a>

Indexes allow efficient retrieval of data from an Amazon DocumentDB collection. Without indexes, DocumentDB must scan every document in the collection to return results that satisfy a given query. This topic provides information on how to create, drop, and list indexes using the MongoDB Java drivers. It also discusses how to determine if a particular index is being used in the query and how to give hints to Amazon DocumentDB to use a specific index.

**Topics**
+ [Creating indexes](#creating-indexes)
+ [Dropping indexes](#dropping-indes)
+ [Determining index selection and providing index hint](#w2aac45b9b7c17c13)

Amazon DocumentDB support many types of indexes. For a comprehensive overview of all the supported indexes refer to this [blog post](https://aws.amazon.com/blogs/database/how-to-index-on-amazon-documentdb-with-mongodb-compatibility/). 

## Creating indexes with Java
<a name="creating-indexes"></a>

There are two mechanisms for creating indexes in Amazon DocumentDB using MongoDB Java drivers: through `runCommand()`, and by either the `createIndex()` method for a single index or the `createIndexes()` method for multiple indexes. One reason for using the `createIndex()` and `createIndexes()` methods is that you can build better error handling by catching specific errors related to index creation. Another reason for using these method over `runCommand()` is that the MongDB Java driver provides a rich set of supporting classes for index creation and manipulation. Note that these supporting classes can only be used when you are using the `createIndex()` or `createIndexes()` methods. There are three supporting classes:
+ **[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/Indexes.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/Indexes.html)** — This class serves as a utility class offering static factory methods for creating various types of indexes. It simplifies the process of creating complex index definitions and is commonly used in conjunction with other index-related classes.
+ **[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/IndexModel.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/IndexModel.html)** — This is a fundamental class that encapsulates both the index keys definition and its options. It represents a complete index specification, combining what to index (the keys) with how to index (the options). This class is particularly useful when creating multiple indexes simultaneously, as it allows you to define a collection of index specifications that can be passed to the `createIndexes()` method.
+ **[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/IndexOptions.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/IndexOptions.html)** — This is a comprehensive configuration class that provides a rich set of methods for customizing index behavior. It includes settings for unique indexes, sparse indexes, expiration time (TTL), and partial filter expressions. Through method chaining, you can configure multiple options like background index building and unique constraints.

**Create single index**

This example shows how to create a single index using the `createIndex(`) method in the background. To understand background and foreground index creation, please refer [Index build types](managing-indexes.md#index-build-types). The following code example uses [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/IndexOptions.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/IndexOptions.html) to create a unique index with a name of “unique\$1restaurantId\$1idx” in the background. This `IndexOptions` object is then passed to the `createIndex()` method.

```
collection.createIndex(
    Indexes.ascending("restaurantId"),
    new IndexOptions()
        .unique(true)
        .name("unique_restaurantId_idx")
        .background(true));
```

**Create multiple indexes**

This example creates multiple indexes using the `createIndexes()` method. It first builds the option for each index by using the [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/IndexModel.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/IndexModel.html) object and then passes a list of `IndexModel` objects to the `createIndexes()` method. The the following code example shows how to create a composite index by using the [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/Indexes.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/Indexes.html) utility class. This class is also used to specify if you want to create an index using ascending or descending sort order. After creating multiple indexes, it verifies index creation by calling the `listIndexes()` method.

```
// Single Field Index on cuisine
IndexModel singleIndex = new IndexModel(
    Indexes.ascending("cuisine"),
    new IndexOptions().name("cuisine_idx"));

// Compound Index
IndexModel compoundIndex = new IndexModel(
    Indexes.compoundIndex(
        Indexes.ascending("address.state"),
        Indexes.ascending("priceRange")),
    new IndexOptions().name("location_price_idx"));

// Build a list of IndexModel for the indexes
List < IndexModel > indexes = Arrays.asList(
    singleIndex,
    compoundIndex
);

collection.createIndexes(indexes);

// Verify created indexes
collection.listIndexes().forEach(index - > System.out.println("Created index: " + index.toJson()));
```

**Create sparse and partial indexes**

This example shows creates a sparse and a partial index by creating an [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/IndexModel.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/IndexModel.html) for each type of index.

```
// Sparse Index Model, this will identify only those documents that have a
// michelin star rating
IndexModel sparseIndex = new IndexModel(
    Indexes.ascending("michelin.star"),
    new IndexOptions()
    .name("michelin_sparse_idx")
    .sparse(true));

// Partial Index Model where the restaurant is active and has a rating of 4 and above
IndexModel partialIndex = new IndexModel(
    Indexes.ascending("rating.average"),
    new IndexOptions()
    .name("high_rated_active_idx")
    .partialFilterExpression(
        Filters.and(
            Filters.eq("isActive", true),
            Filters.gte("rating.average", 4.0))));
```

**Create a text index**

This example shows how to create a text index. Only one text index is allowed on a collection but that one text index can be a compound index covering multiple fields. When using multiple fields in the text index, you can also assign weights to each of the fields in the index. Text indexes on array fields are not supported by Amazon DocumentDB and even though you can use up to 30 fields in the compound text index, only three fields can be assigned a weight.

```
IndexModel textIndex = new IndexModel(
    new Document()
    .append("name", "text")
    .append("description", "text")
    .append("cuisine", "text"),
    new IndexOptions()
    .name("restaurant_text_idx")
    .weights(new Document()
        .append("name", 10) // Restaurant name gets highest weight
        .append("description", 5) // Description get medium weight
        .append("cuisine", 2) // Cuisine type gets low weight
    ));

collection.createIndex(textIndex.getKeys(), textIndex.getOptions());
```

**Create an index using `runCommand()`**

Amazon DocumentDB supports parallel index creation to decrease the time it takes to create indexes. Parallel indexing uses multiple concurrent workers. The default workers used for index creation is two. This [blog post](https://aws.amazon.com/blogs/database/unlock-the-power-of-parallel-indexing-in-amazon-documentdb/) provides an in-depth discussion on parallel indexing. Currently, MongDB Java drivers do not support specifying the worker option when you are using `createIndex()` or `createIndexes()` and therefore the only way to specify workers is through the `runCommand`. The following code example demonstrates how to use `runCommand` to create an index that increase the worker to four:

```
Document command = new Document("createIndexes", "Restaurants")
    .append("indexes", Arrays.asList(
        new Document("key", new Document("name", 1))
        .append("name", "restaurant_name_idx")
        .append("workers", 4) // Specify number of workers
    ));

Document commendResult = connectedDB.runCommand(command);
```

## Dropping indexes
<a name="dropping-indes"></a>

The MongoDB Java driver provides multiple methods to drop indexes, catering to different scenarios and your preferences. You can drop indexes by name, by key specification, or drop all indexes at once. The methods `dropIndex()` and `dropIndexes()` can be invoked on a collection object to drop an index. When dropping an index by name, you should ensure they use the correct index name which may not always be intuitive, especially for compound or auto-generated indexes. Attempting to drop a non-existent index will result in a [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/MongoCommandException.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/MongoCommandException.html). The `default _id` index cannot be dropped as it ensures document uniqueness within the collection.

The following code example shows demonstrates how to drop an index by providing field name where the index is created or by deleting all the indexes: 

```
String indexName = "unique_restaurantId_idx";
Document keys = new Document("cuisine", 1);
// Drop index by name
collection.dropIndex(indexName);
            
// Drop index by keys
collection.dropIndex(keys);
            
// Drop all indexes
collection.dropIndexes();
```

When dropping indexes using multiple keys, make sure there is a compound index containing all the keys specified and the order of the keys is correct. The above index creation example code shows a compound key on "cuisine" and features. If you try to drop that compound key but the order is not what was used for creation, then a MongoCommnadException error occurs as follows:

```
Document keys = new Document("features", 1)
    .append("cuisine", 1);
try {
    // Drop index by keys
    collection.dropIndex(keys);
    System.out.println("Successfully dropped index with keys: " + keys.toJson());

} catch (MongoCommandException commErr) {
    System.out.println("Error dropping index: " + commErr.getErrorMessage());
    throw new RuntimeException("MongoCommandException was thrown while dropping index", commErr);
}
```

The following error is displayed:

```
Error dropping index: Cannot drop index: index not found.
Tests run: 3, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.819 sec <<< FAILURE!
com.amazon.docdb.guide.DocDBGuideTest.testindexGuide()  Time elapsed: 0.817 sec  <<< FAILURE!
org.opentest4j.AssertionFailedError: Unexpected exception thrown: java.lang.RuntimeException: MongoCommandException was thrown while dropping index
```

## Determining index selection and providing index hint
<a name="w2aac45b9b7c17c13"></a>

Working with the explain functionality in Amazon DocumentDB is crucial for you to understand query performance and index usage. When executing a query, you can append the `explain()` method to obtain detailed information about the query plan, including which indexes, if any, are being used. The `explain()` output provides insights into the query execution stages, the number of documents examined, and the time taken for each stage. This information is invaluable for identifying whether a particular index is being used effectively or if the query could benefit from a different index structure.

The `explain()` method can be chained with the `find()` method. The `explain()` method can take an optional [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/ExplainVerbosity.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/ExplainVerbosity.html) enum that determines the level of verbosity that is returned by `explain()`. At this time, only the `EXECUTION_STATS` and `QUERY_PLANNER` enumerators are supported by DocumentDB. The following code example shows how to get query planner for a specific query:

```
// Query we want to analyze
Document query = new Document()
    .append("cuisine", "Thai")
    .append("rating.average", new Document("$gte", 4.0));


Document allPlansExplain = collection.find(query).explain(ExplainVerbosity.QUERY_PLANNER);
System.out.println("All Plans Explain:\n" + allPlansExplain.toJson());
```

The following JSON document is returned for the query planner verbosity level:

```
{
  "queryPlanner": {
    "plannerVersion": 1,
    "namespace": "ProgGuideData.Restaurants",
    "winningPlan": {
      "stage": "IXSCAN",
      "indexName": "cuisine_idx",
      "direction": "forward"
    }
  },
  "serverInfo": {
    "host": "guidecluster3",
    "port": 27017,
    "version": "5.0.0"
  },
  "ok": 1,
  "operationTime": {
    "$timestamp": {
      "t": 1739221668,
      "i": 1
    }
  }
}
```

You have several options to influence or force Amazon DocumentDB to use a specific index. The `hint()` and `hintString()` methods allow you to override the query optimizer's default index selection behavior by explicitly specifying which index should be used for a query. While DocumentDB's query optimizer generally makes good choices for index selection, there are scenarios where forcing a specific index through `hint()` or `hintString()` can be beneficial, such as when dealing with skewed data, or testing index performance.

The following code example forces the use of compound index “cuisine\$1features\$1idx” for the same query that was run in the above code:

```
// Query we want to analyze
Document query = new Document()
    .append("cuisine", "Thai")
    .append("rating.average", new Document("$gte", 4.0));

List < Document > queryDocs = new ArrayList < > ();
collection.find(query).hintString("cuisine_features_idx").forEach(doc - > queryDocs.add(doc));
```

# Event-driven programming with Amazon DocumentDB and Java
<a name="event-driven-programming"></a>

Event-driven programming in the context of Amazon DocumentDB represents a powerful architectural pattern where database changes serve as the primary event generators that trigger subsequent business logic and processes. When records are inserted, updated, or deleted in a DocumentDB collection, these changes act as events that automatically initiate various downstream processes, notifications, or data synchronization tasks. This pattern is particularly valuable in modern distributed systems where multiple applications or services need to react to data changes in real-time. The primary mechanism of implementing event-driven programming in DocumentDB is by change streams.

**Note**  
This guide assumes you have enabled change streams on a collection that you are working with. See [Using change streams with Amazon DocumentDB](change_streams.md) to learn how to enable change streams on the collection. 

**Working with change streams from the Java application**

The `watch()` method in MongoDB’s Java driver is the primary mechanism for monitoring real-time data changes in Amazon DocumentDB. The `watch()` method can be called on by [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoClient.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoClient.html), [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoDatabase.html#watch(com.mongodb.client.ClientSession,java.lang.Class)](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoDatabase.html#watch(com.mongodb.client.ClientSession,java.lang.Class)), and [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCollection.html#watch()](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCollection.html#watch()) objects.

The `watch()` method returns an instance of [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/ChangeStreamIterable.html#startAtOperationTime(org.bson.BsonTimestamp)](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/ChangeStreamIterable.html#startAtOperationTime(org.bson.BsonTimestamp)) that supports various configuration options, including full document lookup for updates, providing resume tokens and timestamp for reliability, and pipeline aggregation stages for filtering changes. 

[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/ChangeStreamIterable.html#startAtOperationTime(org.bson.BsonTimestamp)](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/ChangeStreamIterable.html#startAtOperationTime(org.bson.BsonTimestamp)) implements the core Java interface `Iterable` and can be used with `forEach()`. To capture events using `forEach()`, pass in a callback function to `forEach()` that processes the changed event. The following code snippet shows how to open a change streams on a collection to start change event monitoring:

```
ChangeStreamIterable < Document > iterator = collection.watch();
iterator.forEach(event - > {
    System.out.println("Received a change: " + event);
});
```

Another way of traversing through all the change events is by opening a cursor that maintains a connection to the cluster and continuously receives new change events as they occur. To obtain a change streams cursor, use the `cursor()` method of [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/ChangeStreamIterable.html#startAtOperationTime(org.bson.BsonTimestamp)](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/ChangeStreamIterable.html#startAtOperationTime(org.bson.BsonTimestamp)) object. The following code example shows how to monitor change events using cursor:

```
try (MongoChangeStreamCursor < ChangeStreamDocument < Document >> cursor = collection.watch().cursor()) {
    System.out.println(cursor.tryNext());
}
```

As a best practice, either create the [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoChangeStreamCursor.html#getResumeToken()](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoChangeStreamCursor.html#getResumeToken()) in a try-with-resource statement or manually close the cursor. Calling the `cursor()` method on [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/ChangeStreamIterable.html#startAtOperationTime(org.bson.BsonTimestamp)](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/ChangeStreamIterable.html#startAtOperationTime(org.bson.BsonTimestamp)) returns a `MongoChangeStreamCursor` that is created over a [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/changestream/ChangeStreamDocument.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/changestream/ChangeStreamDocument.html) object. 

The [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/changestream/ChangeStreamDocument.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/changestream/ChangeStreamDocument.html) class is a crucial component that represents individual change events in the stream. It contains detailed information about each modification, including the operation type (insert, update, delete, replace), the document key, namespace information, and the full document content when available. The class provides methods to access various aspects of the change event, such as `getOperationType()` to determine the type of change, `getFullDocument()` to access the complete document state, and `getDocumentKey()` to identify the modified document.

The [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/changestream/ChangeStreamDocument.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/changestream/ChangeStreamDocument.html) object provides two important pieces of information, a resume token and time of the change event.

Resume tokens and time-based operations in DocumentDB change streams provide crucial mechanisms for maintaining continuity and managing historical data access. A resume token is a unique identifier generated for each change event, serving as a bookmark that allows applications to restart change stream processing from a specific point after disconnections or failures. When a change stream cursor is created, it can use a previously stored resume token through the `resumeAfter()` option, enabling the stream to continue from where it left off rather than starting from the beginning or losing events.

Time-based operations in change streams offer different approaches to manage the starting point of change event monitoring. The `startAtOperationTime()` option allows you to begin watching changes that occurred at or after a specific timestamp. These time-based features are particularly valuable in scenarios requiring historical data processing, point-in-time recovery, or synchronization between systems.

The following code example retrieves the event associated with the insert document, captures it’s resume token, and then provides that token to start monitoring for events after the insert event. The event is associated with the update event, then gets the cluster time when the update happened and uses that timestamp as a starting point for further processing.

```
BsonDocument resumeToken;
BsonTimestamp resumeTime;

try (MongoChangeStreamCursor < ChangeStreamDocument < Document >> cursor = collection.watch().cursor()) {
    System.out.println("****************** Insert Document *******************");
    ChangeStreamDocument < Document > insertChange = cursor.tryNext();
    resumeToken = insertChange.getResumeToken();
    printJson(cursor.tryNext());
}
try (MongoChangeStreamCursor < ChangeStreamDocument < Document >> cursor = collection.watch()
    .resumeAfter(resumeToken)
    .cursor()) {
    System.out.println("****************** Update Document *******************");
    ChangeStreamDocument < Document > insertChange = cursor.tryNext();
    resumeTime = insertChange.getClusterTime();
    printJson(cursor.tryNext());
}
try (MongoChangeStreamCursor < ChangeStreamDocument < Document >> cursor = collection.watch()
    .startAtOperationTime(resumeTime)
    .cursor()) {
    System.out.println("****************** Delete Document *******************");
    printJson(cursor.tryNext());
  }
```

By default, the update change event does not include the full document and it only include the changes that were made. If you need to access the complete document that was updated, you can call the `fullDocument()` method on the [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/ChangeStreamIterable.html#startAtOperationTime(org.bson.BsonTimestamp)](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/ChangeStreamIterable.html#startAtOperationTime(org.bson.BsonTimestamp)) object. Keep in mind that when you ask for a full document to be returned for an update event, it returns the document that exists at the time the call to change streams is made.

This method takes a [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/changestream/FullDocument.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/changestream/FullDocument.html) enum as a parameter. Currently, Amazon DocumentDB only support DEFAULT and `UPDATE_LOOKUP` values. The following code snippet shows how to ask for full document for update events when starting to watch for changes:

```
try (MongoChangeStreamCursor < ChangeStreamDocument < Document >> cursor = collection.watch().fullDocument(FullDocument.UPDATE_LOOKUP).cursor())
```