Migrate your application to use the Azure Cosmos DB Java SDK v4

APPLIES TO: NoSQL

Important

For more information about this SDK, please view the Azure Cosmos DB Java SDK v4 Release notes, Maven repository, Azure Cosmos DB Java SDK v4 performance tips, and Azure Cosmos DB Java SDK v4 troubleshooting guide.

Important

Because Azure Cosmos DB Java SDK v4 has up to 20% enhanced throughput, TCP-based direct mode, and support for the latest backend service features, we recommend you upgrade to v4 at the next opportunity. Continue reading below to learn more.

Update to the latest Azure Cosmos DB Java SDK to get the best of what Azure Cosmos DB has to offer - a managed non-relational database service with competitive performance, five-nines availability, one-of-a-kind resource governance, and more. This article explains how to upgrade your existing Java application that is using an older Azure Cosmos DB Java SDK to the newer Azure Cosmos DB Java SDK 4.0 for API for NoSQL. Azure Cosmos DB Java SDK v4 corresponds to the com.azure.cosmos package. You can use the instructions in this doc if you are migrating your application from any of the following Azure Cosmos DB Java SDKs:

  • Sync Java SDK 2.x.x
  • Async Java SDK 2.x.x
  • Java SDK 3.x.x

Azure Cosmos DB Java SDK’s and package mappings

The following table lists different Azure Cosmos DB Java SDKs, the package name and the release information:

Java SDK Release Date Bundled APIs Maven Jar Java package name API Reference Release Notes Retire date
Async 2.x.x June 2018 Async(RxJava) com.microsoft.azure::azure-cosmosdb com.microsoft.azure.cosmosdb.rx API Release Notes August 31, 2024
Sync 2.x.x Sept 2018 Sync com.microsoft.azure::azure-documentdb com.microsoft.azure.cosmosdb API February 29, 2024
3.x.x July 2019 Async(Reactor)/Sync com.microsoft.azure::azure-cosmos com.azure.data.cosmos API - August 31, 2024
4.0 June 2020 Async(Reactor)/Sync com.azure::azure-cosmos com.azure.cosmos API - -

SDK level implementation changes

The following are the key implementation differences between different SDKs:

RxJava is replaced with reactor in Azure Cosmos DB Java SDK versions 3.x.x and 4.0

If you are unfamiliar with asynchronous programming or Reactive Programming, see the Reactor pattern guide for an introduction to async programming and Project Reactor. This guide may be useful if you have been using Azure Cosmos DB Sync Java SDK 2.x.x or Azure Cosmos DB Java SDK 3.x.x Sync API in the past.

If you have been using Azure Cosmos DB Async Java SDK 2.x.x, and you plan on migrating to the 4.0 SDK, see the Reactor vs RxJava Guide for guidance on converting RxJava code to use Reactor.

Azure Cosmos DB Java SDK v4 has direct connectivity mode in both Async and Sync APIs

If you have been using Azure Cosmos DB Sync Java SDK 2.x.x, note that the direct connection mode based on TCP (as opposed to HTTP) is implemented in Azure Cosmos DB Java SDK 4.0 for both the Async and Sync APIs.

API level changes

The following are the API level changes in Azure Cosmos DB Java SDK 4.x.x compared to previous SDKs (Java SDK 3.x.x, Async Java SDK 2.x.x, and Sync Java SDK 2.x.x):

Azure Cosmos DB Java SDK naming conventions

  • The Azure Cosmos DB Java SDK 3.x.x and 4.0 refer the client resources as Cosmos<resourceName>. For example, CosmosClient, CosmosDatabase, CosmosContainer. Whereas in version 2.x.x, the Azure Cosmos DB Java SDKs don’t have a uniform naming scheme.

  • Azure Cosmos DB Java SDK 3.x.x and 4.0 offer both Sync and Async APIs.

    • Java SDK 4.0 : All the classes belong to the Sync API unless the class name is appended with Async after Cosmos.

    • Java SDK 3.x.x: All the classes belong to the Async API unless the class name is appended with Async after Cosmos.

    • Async Java SDK 2.x.x: The class names are similar to Sync Java SDK 2.x.x, however the name starts with Async.

Hierarchical API structure

Azure Cosmos DB Java SDK 4.0 and 3.x.x introduce a hierarchical API structure that organizes the clients, databases, and containers in a nested fashion as shown in the following 4.0 SDK code snippet:

CosmosContainer container = client.getDatabase("MyDatabaseName").getContainer("MyContainerName");

In version 2.x.x of the Azure Cosmos DB Java SDK, all operations on resources and documents are performed through the client instance.

Representing documents

In Azure Cosmos DB Java SDK 4.0, custom POJO's and JsonNodes are the two options to read and write the documents from Azure Cosmos DB.

In the Azure Cosmos DB Java SDK 3.x.x, the CosmosItemProperties object is exposed by the public API and served as a document representation. This class is no longer exposed publicly in version 4.0.

Imports

  • The Azure Cosmos DB Java SDK 4.0 packages begin with com.azure.cosmos

  • Azure Cosmos DB Java SDK 3.x.x packages begin with com.azure.data.cosmos

  • Azure Cosmos DB Java SDK 2.x.x Sync API packages begin with com.microsoft.azure.documentdb

  • Azure Cosmos DB Java SDK 4.0 places several classes in a nested package com.azure.cosmos.models. Some of these packages include:

    • CosmosContainerResponse
    • CosmosDatabaseResponse
    • CosmosItemResponse
    • The Async API analogs for all of the above packages
    • CosmosContainerProperties
    • FeedOptions
    • PartitionKey
    • IndexingPolicy
    • IndexingMode ...etc.

Accessors

Azure Cosmos DB Java SDK 4.0 exposes get and set methods to access the instance members. For example, the CosmosContainer instance has container.getId() and container.setId() methods.

This is different from Azure Cosmos DB Java SDK 3.x.x which exposes a fluent interface. For example, a CosmosSyncContainer instance has container.id() which is overloaded to get or set the id value.

Managing Dependency Conflicts

Upgrading from Azure Cosmos DB Java SDK V2 to V4 can introduce dependency conflicts due to changes in the libraries used by the SDK. Resolving these conflicts requires careful management of the dependencies.

  1. Understand the New Dependencies: The Azure Cosmos DB V4 SDK has its own set of dependencies that might be different from those in prior versions. Make sure you are aware of these dependencies:

    • azure-cosmos
    • reactor-core
    • reactor-netty
    • netty-handler
    • guava
    • slf4j-api
    • jackson-databind
    • jackson-annotations
    • jackson-core
    • commons-lang3
    • commons-collections4
    • azure-core
    • azure-core-http-netty
  2. Remove Conflicting Dependencies: Start by removing the dependencies related to prior versions of the SDK from your pom.xml file. These include azure-cosmosdb and any transitive dependencies that the old SDK might have had.

  3. Add V4 SDK Dependencies: Add the V4 SDK and its dependencies to your pom.xml. Here's an example:

    <dependency>
        <groupId>com.azure</groupId>
        <artifactId>azure-cosmos</artifactId>
        <version>4.x.x</version> <!-- Use the latest version available -->
    </dependency>
    
  4. Check for Dependency Conflicts: Use the Maven dependency:tree command to generate a dependency tree and identify any conflicts. Run:

    mvn dependency:tree
    

    Look for any conflicting versions of dependencies. These conflicts often occur with libraries like reactor-core, netty-handler, guava, and jackson.

  5. Use Dependency Management: If you encounter version conflicts, you might need to override problematic versions using the <dependencyManagement> section in your pom.xml. Here’s an example to enforce a specific version of reactor-core:

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>io.projectreactor</groupId>
                <artifactId>reactor-core</artifactId>
                <version>3.x.x</version> <!-- Use a compatible version -->
            </dependency>
            <!-- Repeat for any other conflicting dependencies -->
        </dependencies>
    </dependencyManagement>
    
  6. Exclude Transitive Dependencies: Sometimes, you may need to exclude transitive dependencies brought in by other dependencies. For instance, if another library brings in an older version of a dependency that conflicts, you can exclude it like this:

    <dependency>
        <groupId>some.group</groupId>
        <artifactId>some-artifact</artifactId>
        <version>x.x.x</version>
        <exclusions>
            <exclusion>
                <groupId>conflicting.group</groupId>
                <artifactId>conflicting-artifact</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    
  7. Rebuild and Test: After making these changes, rebuild your project and thoroughly test it to ensure that the new dependencies work correctly and that no runtime conflicts occur.

Code snippet comparisons

Create resources

The following code snippet shows the differences in how resources are created between the 4.0, 3.x.x Async, 2.x.x Sync, and 2.x.x Async APIs:


        // Create Async client.
        // Building an async client is still a sync operation.
        CosmosAsyncClient client = new CosmosClientBuilder()
                .endpoint("your.hostname")
                .key("yourmasterkey")
                .consistencyLevel(ConsistencyLevel.EVENTUAL)
                .buildAsyncClient();

        // Create database with specified name
        client.createDatabaseIfNotExists("YourDatabaseName")
                .flatMap(databaseResponse -> {
                    testDatabaseAsync = client.getDatabase("YourDatabaseName");
                    // Container properties - name and partition key
                    CosmosContainerProperties containerProperties =
                            new CosmosContainerProperties("YourContainerName", "/id");

                    // Provision manual throughput
                    ThroughputProperties throughputProperties = ThroughputProperties.createManualThroughput(400);

                    // Create container
                    return database.createContainerIfNotExists(containerProperties, throughputProperties);
                }).flatMap(containerResponse -> {
            testContainerAsync = database.getContainer("YourContainerName");
            return Mono.empty();
        }).subscribe();

Item operations

The following code snippet shows the differences in how item operations are performed between the 4.0, 3.x.x Async, 2.x.x Sync, and 2.x.x Async APIs:


// Container is created. Generate many docs to insert.
int number_of_docs = 50000;
ArrayList<JsonNode> docs = generateManyDocs(number_of_docs);

// Insert many docs into container...
Flux.fromIterable(docs)
        .flatMap(doc -> testContainerAsync.createItem(doc))
        .subscribe(); // ...Subscribing triggers stream execution.

Indexing

The following code snippet shows the differences in how indexing is created between the 4.0, 3.x.x Async, 2.x.x Sync, and 2.x.x Async APIs:


CosmosContainerProperties containerProperties = new CosmosContainerProperties(containerName, "/lastName");

// Custom indexing policy
IndexingPolicy indexingPolicy = new IndexingPolicy();
indexingPolicy.setIndexingMode(IndexingMode.CONSISTENT);

// Included paths
List<IncludedPath> includedPaths = new ArrayList<>();
includedPaths.add(new IncludedPath("/*"));
indexingPolicy.setIncludedPaths(includedPaths);

// Excluded paths
List<ExcludedPath> excludedPaths = new ArrayList<>();
excludedPaths.add(new ExcludedPath("/name/*"));
indexingPolicy.setExcludedPaths(excludedPaths);

containerProperties.setIndexingPolicy(indexingPolicy);

ThroughputProperties throughputProperties = ThroughputProperties.createManualThroughput(400);

database.createContainerIfNotExists(containerProperties, throughputProperties);
CosmosAsyncContainer containerIfNotExists = database.getContainer(containerName);

Stored procedures

The following code snippet shows the differences in how stored procedures are created between the 4.0, 3.x.x Async, 2.x.x Sync, and 2.x.x Async APIs:


logger.info("Creating stored procedure...\n");

String sprocId = "createMyDocument";

String sprocBody = "function createMyDocument() {\n" +
        "var documentToCreate = {\"id\":\"test_doc\"}\n" +
        "var context = getContext();\n" +
        "var collection = context.getCollection();\n" +
        "var accepted = collection.createDocument(collection.getSelfLink(), documentToCreate,\n" +
        "    function (err, documentCreated) {\n" +
        "if (err) throw new Error('Error' + err.message);\n" +
        "context.getResponse().setBody(documentCreated.id)\n" +
        "});\n" +
        "if (!accepted) return;\n" +
        "}";

CosmosStoredProcedureProperties storedProcedureDef = new CosmosStoredProcedureProperties(sprocId, sprocBody);
container.getScripts()
        .createStoredProcedure(storedProcedureDef,
                new CosmosStoredProcedureRequestOptions()).block();

// ...

logger.info(String.format("Executing stored procedure %s...\n\n", sprocId));

CosmosStoredProcedureRequestOptions options = new CosmosStoredProcedureRequestOptions();
options.setPartitionKey(new PartitionKey("test_doc"));

container.getScripts()
        .getStoredProcedure(sprocId)
        .execute(null, options)
        .flatMap(executeResponse -> {
            logger.info(String.format("Stored procedure %s returned %s (HTTP %d), at cost %.3f RU.\n",
                    sprocId,
                    executeResponse.getResponseAsString(),
                    executeResponse.getStatusCode(),
                    executeResponse.getRequestCharge()));
            return Mono.empty();
        }).block();

Change feed

The following code snippet shows the differences in how change feed operations are executed between the 4.0 and 3.x.x Async APIs:


ChangeFeedProcessor changeFeedProcessorInstance =
        new ChangeFeedProcessorBuilder()
                .hostName(hostName)
                .feedContainer(feedContainer)
                .leaseContainer(leaseContainer)
                .handleChanges((List<JsonNode> docs) -> {
                    logger.info("--->setHandleChanges() START");

                    for (JsonNode document : docs) {
                        try {
                            //Change Feed hands the document to you in the form of a JsonNode
                            //As a developer you have two options for handling the JsonNode document provided to you by Change Feed
                            //One option is to operate on the document in the form of a JsonNode, as shown below. This is great
                            //especially if you do not have a single uniform data model for all documents.
                            logger.info("---->DOCUMENT RECEIVED: " + OBJECT_MAPPER.writerWithDefaultPrettyPrinter()
                                    .writeValueAsString(document));

                            //You can also transform the JsonNode to a POJO having the same structure as the JsonNode,
                            //as shown below. Then you can operate on the POJO.
                            CustomPOJO pojo_doc = OBJECT_MAPPER.treeToValue(document, CustomPOJO.class);
                            logger.info("----=>id: " + pojo_doc.getId());

                        } catch (JsonProcessingException e) {
                            e.printStackTrace();
                        }
                    }
                    logger.info("--->handleChanges() END");

                })
                .buildChangeFeedProcessor();

// ...

changeFeedProcessorInstance.start()
        .subscribeOn(Schedulers.elastic())
        .subscribe();

Container level Time-To-Live(TTL)

The following code snippet shows the differences in how to create time to live for data in the container between the 4.0, 3.x.x Async, 2.x.x Sync, and 2.x.x Async APIs:


CosmosAsyncContainer container;

// Create a new container with TTL enabled with default expiration value
CosmosContainerProperties containerProperties = new CosmosContainerProperties("myContainer", "/myPartitionKey");
containerProperties.setDefaultTimeToLiveInSeconds(90 * 60 * 60 * 24);
ThroughputProperties throughputProperties = ThroughputProperties.createManualThroughput(400);
database.createContainerIfNotExists(containerProperties, throughputProperties).block();
container = database.getContainer("myContainer");

Item level Time-To-Live(TTL)

The following code snippet shows the differences in how to create time to live for an item between the 4.0, 3.x.x Async, 2.x.x Sync, and 2.x.x Async APIs:


// Include a property that serializes to "ttl" in JSON
class SalesOrder
{
    private String id;
    private String customerId;
    private Integer ttl;

    public SalesOrder(String id, String customerId, Integer ttl) {
        this.id = id;
        this.customerId = customerId;
        this.ttl = ttl;
    }

    public String getId() {return this.id;}
    public void setId(String new_id) {this.id = new_id;}
    public String getCustomerId() {return this.customerId;}
    public void setCustomerId(String new_cid) {this.customerId = new_cid;}
    public Integer getTtl() {return this.ttl;}
    public void setTtl(Integer new_ttl) {this.ttl = new_ttl;}

    //...
}


// Set the value to the expiration in seconds
SalesOrder salesOrder = new SalesOrder(
        "SO05",
        "CO18009186470",
        60 * 60 * 24 * 30  // Expire sales orders in 30 days
);

Next steps