Edit

Quickstart: Vector index with Java in Azure DocumentDB

This quickstart compares vector index algorithms (DiskANN, HNSW, IVF) in Azure DocumentDB using Java to help you select the best configuration for your vector search workload. The sample uses the same hotel dataset with precalculated vectors as the other quickstarts to demonstrate performance differences across algorithms and similarity functions.

Find the sample code on GitHub.

Prerequisites

  • An Azure subscription. If you don't have an Azure subscription, create a free account.

Create a Java project

  1. Create a new directory for your project and open it in Visual Studio Code:

    mkdir select-algorithm-java
    cd select-algorithm-java
    code .
    

  1. Create a standard Maven project structure:

    mkdir -p src/main/java/com/azure/documentdb/selectalgorithm
    

  1. Create a pom.xml file in the root directory with the following content:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.azure.documentdb</groupId>
        <artifactId>select-algorithm-java</artifactId>
        <version>1.0.0</version>
        <packaging>jar</packaging>
    
        <name>DocumentDB Select Algorithm - Java</name>
        <description>Demonstrates IVF, HNSW, and DiskANN vector search indexes with Azure DocumentDB</description>
    
        <properties>
            <maven.compiler.source>21</maven.compiler.source>
            <maven.compiler.target>21</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.mongodb</groupId>
                <artifactId>mongodb-driver-sync</artifactId>
                <version>5.4.0</version>
            </dependency>
            <dependency>
                <groupId>com.azure</groupId>
                <artifactId>azure-identity</artifactId>
                <version>1.16.0</version>
            </dependency>
            <dependency>
                <groupId>com.azure</groupId>
                <artifactId>azure-ai-openai</artifactId>
                <version>1.0.0-beta.16</version>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.13.0</version>
                    <configuration>
                        <source>21</source>
                        <target>21</target>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>exec-maven-plugin</artifactId>
                    <version>3.4.1</version>
                    <configuration>
                        <mainClass>com.azure.documentdb.selectalgorithm.Main</mainClass>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
        <profiles>
            <profile>
                <id>compare</id>
                <build>
                    <plugins>
                        <plugin>
                            <groupId>org.codehaus.mojo</groupId>
                            <artifactId>exec-maven-plugin</artifactId>
                            <version>3.4.1</version>
                            <configuration>
                                <mainClass>com.azure.documentdb.selectalgorithm.CompareAll</mainClass>
                            </configuration>
                        </plugin>
                    </plugins>
                </build>
            </profile>
        </profiles>
    </project>
    

    Verify that all dependencies resolve without errors by running mvn dependency:resolve.

Create data file with vectors

  1. Create a new data directory for the hotels data file:

    mkdir data
    

  1. Download the Hotels_Vector.json data file with vectors to your data directory:

    curl -o data/Hotels_Vector.json https://raw.githubusercontent.com/Azure-Samples/documentdb-samples/refs/heads/main/ai/data/Hotels_Vector.json
    

Verify that the file exists and is valid JSON:

ls -lh data/Hotels_Vector.json

You should see Hotels_Vector.json in the data directory.

Configure environment variables

Set the required environment variables in your current shell session before you run the sample:

export DOCUMENTDB_CLUSTER_NAME=<your-cluster-name>
export AZURE_OPENAI_EMBEDDING_ENDPOINT=https://<your-resource>.openai.azure.com
export AZURE_OPENAI_EMBEDDING_MODEL=text-embedding-3-small
export AZURE_DOCUMENTDB_DATABASENAME=Hotels
export DATA_FILE_WITH_VECTORS=data/Hotels_Vector.json
export EMBEDDED_FIELD=DescriptionVector
export EMBEDDING_DIMENSIONS=1536

Replace the placeholder values with your Azure resource information:

  • DOCUMENTDB_CLUSTER_NAME: Your Azure DocumentDB cluster name
  • AZURE_OPENAI_EMBEDDING_ENDPOINT: Your Azure OpenAI resource endpoint URL

This sample uses passwordless authentication with DefaultAzureCredential, which requires your identity to have proper RBAC roles assigned. For more information on authentication options, see Authenticate Java apps to Azure services by using the Azure SDK for Java.

Create code files

When you're done, the project structure should look like this:

select-algorithm-java/
├── data/
│   └── Hotels_Vector.json
├── src/main/java/com/azure/documentdb/selectalgorithm/
│   ├── CompareAll.java
│   ├── Main.java
│   └── Utils.java
└── pom.xml

Create the algorithm comparison code

Create the following source files to implement the vector search comparison.

Create utility functions

Create src/main/java/com/azure/documentdb/selectalgorithm/Utils.java and paste the following code:

package com.azure.documentdb.selectalgorithm;

import com.azure.ai.openai.OpenAIClient;
import com.azure.ai.openai.OpenAIClientBuilder;
import com.azure.ai.openai.models.EmbeddingItem;
import com.azure.ai.openai.models.EmbeddingsOptions;
import com.azure.core.credential.AccessToken;
import com.azure.identity.DefaultAzureCredential;
import com.azure.identity.DefaultAzureCredentialBuilder;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoCredential;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.InsertManyOptions;
import org.bson.Document;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

public class Utils {

    public static String getEnv(String key, String defaultValue) {
        String value = System.getenv(key);
        return (value != null && !value.isBlank()) ? value : defaultValue;
    }

    public static String getEnv(String key) {
        return getEnv(key, null);
    }

    public static MongoClient getMongoClient() {
        String clusterName = getEnv("DOCUMENTDB_CLUSTER_NAME");
        if (clusterName == null) {
            throw new IllegalStateException("DOCUMENTDB_CLUSTER_NAME environment variable is required");
        }

        String clusterHost = String.format("%s.global.mongocluster.cosmos.azure.com", clusterName);

        // Use custom OIDC callback with DefaultAzureCredential
        // This chains through CLI, managed identity, etc.
        DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
        String tokenResource = "https://ossrdbms-aad.database.windows.net/.default";

        MongoCredential mongoCredential = MongoCredential.createOidcCredential(null)
                .withMechanismProperty("OIDC_CALLBACK", (MongoCredential.OidcCallback) context -> {
                    AccessToken token = credential.getToken(
                            new com.azure.core.credential.TokenRequestContext()
                                    .addScopes(tokenResource)).block();
                    return new MongoCredential.OidcCallbackResult(token.getToken());
                });

        MongoClientSettings settings = MongoClientSettings.builder()
                .applyToClusterSettings(builder -> builder.srvHost(clusterHost))
                .applyToSslSettings(builder -> builder.enabled(true))
                .credential(mongoCredential)
                .retryWrites(false)
                .build();

        return MongoClients.create(settings);
    }

    public static OpenAIClient getOpenAIClient() {
        String endpoint = getEnv("AZURE_OPENAI_EMBEDDING_ENDPOINT");
        if (endpoint == null) {
            throw new IllegalStateException("AZURE_OPENAI_EMBEDDING_ENDPOINT environment variable is required");
        }

        DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();

        return new OpenAIClientBuilder()
                .endpoint(endpoint)
                .credential(credential)
                .buildClient();
    }

    public static List<Document> readJsonFile(String path) {
        try {
            String content = Files.readString(Path.of(path));
            // Parse JSON array of documents
            @SuppressWarnings("unchecked")
            List<Document> docs = Document.parse("{\"data\":" + content + "}").getList("data", Document.class);
            return docs;
        } catch (IOException e) {
            throw new RuntimeException("Failed to read data file: " + path, e);
        }
    }

    public static void insertData(MongoCollection<Document> collection, List<Document> data, int batchSize) {
        System.out.printf("  Inserting %d documents in batches of %d...%n", data.size(), batchSize);
        InsertManyOptions options = new InsertManyOptions().ordered(false);

        for (int i = 0; i < data.size(); i += batchSize) {
            List<Document> batch = data.subList(i, Math.min(i + batchSize, data.size()));
            // Remove _id to avoid duplicate key errors on re-run
            List<Document> cleaned = new ArrayList<>();
            for (Document doc : batch) {
                Document copy = new Document(doc);
                copy.remove("_id");
                cleaned.add(copy);
            }
            try {
                collection.insertMany(cleaned, options);
            } catch (Exception e) {
                // Ignore duplicate key errors on re-insert
                if (!e.getMessage().contains("duplicate key")) {
                    throw e;
                }
            }
            System.out.printf("  Inserted batch %d-%d%n", i + 1, Math.min(i + batchSize, data.size()));
        }
        System.out.println("  Data insertion complete.");
    }

    public static void dropVectorIndexes(MongoCollection<Document> collection, String vectorField) {
        try {
            for (Document idx : collection.listIndexes()) {
                String name = idx.getString("name");
                if (name != null && name.contains(vectorField) && !name.equals("_id_")) {
                    System.out.printf("  Dropping existing index: %s%n", name);
                    collection.dropIndex(name);
                }
            }
        } catch (Exception e) {
            // Ignore errors when indexes don't exist
            System.out.println("  No existing vector indexes to drop.");
        }
    }

    public static List<Float> getEmbedding(OpenAIClient client, String text, String model) {
        EmbeddingsOptions options = new EmbeddingsOptions(List.of(text));
        List<EmbeddingItem> embeddings = client.getEmbeddings(model, options).getData();
        if (embeddings.isEmpty()) {
            throw new RuntimeException("No embedding returned for query text");
        }
        return embeddings.get(0).getEmbedding();
    }

    public static List<Document> performVectorSearch(
            MongoCollection<Document> collection,
            OpenAIClient aiClient,
            String query,
            String vectorField,
            String model,
            int topK) {

        System.out.printf("  Generating embedding for query: \"%s\"%n", query);
        List<Float> queryVector = getEmbedding(aiClient, query, model);
        System.out.printf("  Embedding generated (%d dimensions)%n", queryVector.size());

        // Convert List<Float> to List<Double> for BSON
        List<Double> vectorAsDoubles = queryVector.stream()
                .map(Float::doubleValue)
                .toList();

        Document searchStage = new Document("$search", new Document("cosmosSearch", new Document()
                .append("vector", vectorAsDoubles)
                .append("path", vectorField)
                .append("k", topK)));

        Document projectStage = new Document("$project", new Document()
                .append("_id", 0)
                .append("HotelName", 1)
                .append("Description", 1)
                .append("score", new Document("$meta", "searchScore")));

        List<Document> pipeline = List.of(searchStage, projectStage);
        List<Document> results = new ArrayList<>();
        collection.aggregate(pipeline).forEach(results::add);

        return results;
    }

    public static void printResults(List<Document> results) {
        System.out.println("\n  === Search Results ===");
        for (int i = 0; i < results.size(); i++) {
            Document doc = results.get(i);
            System.out.printf("  %d. %s (score: %.4f)%n",
                    i + 1,
                    doc.getString("HotelName"),
                    doc.getDouble("score"));
            System.out.printf("     %s%n", doc.getString("Description"));
        }
        System.out.println();
    }
}

This utility class provides:

  • Environment variable management: Reads configuration from environment variables with System.getenv().
  • Passwordless authentication: Uses DefaultAzureCredential for both MongoDB and Azure OpenAI.
  • MongoDB client creation: Configures OIDC authentication for DocumentDB.
  • Azure OpenAI client creation: Sets up the OpenAI client for embedding generation.
  • Data loading: Reads hotel data from JSON file.
  • Embedding generation: Creates vector embeddings for text queries.
  • Index configuration: Generates algorithm-specific vector index options.
  • Search configuration: Generates algorithm-specific search parameters.
  • Results formatting: Prints comparison table of algorithm performance.

Note

The Java sample configures the DocumentDB connection with retryWrites=false, which is required for DocumentDB vector search operations.

Create main comparison logic

Create the following source files in src/main/java/com/azure/documentdb/selectalgorithm/:

CompareAll.java

package com.azure.documentdb.selectalgorithm;

import com.azure.ai.openai.OpenAIClient;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;

import java.util.ArrayList;
import java.util.List;

/**
 * Unified comparison runner that executes all 9 combinations
 * (3 algorithms x 3 similarity metrics) and prints a formatted table.
 */
public class CompareAll {

    private static final String COLLECTION_NAME = "hotels";
    private static final String[] ALGORITHMS = {"ivf", "hnsw", "diskann"};
    private static final String[] METRICS = {"COS", "L2", "IP"};

    public static void main(String[] args) {
        run();
    }

    public static void run() {
        String queryText = Utils.getEnv("QUERY_TEXT", "luxury hotel near the beach");
        int topK = Integer.parseInt(Utils.getEnv("TOP_K", "5"));

        String databaseName = Utils.getEnv("AZURE_DOCUMENTDB_DATABASENAME", "Hotels");
        String dataFile = Utils.getEnv("DATA_FILE_WITH_VECTORS", "data/Hotels_Vector.json");
        String vectorField = Utils.getEnv("EMBEDDED_FIELD", "DescriptionVector");
        int dimensions = Integer.parseInt(Utils.getEnv("EMBEDDING_DIMENSIONS", "1536"));
        String model = Utils.getEnv("AZURE_OPENAI_EMBEDDING_MODEL", "text-embedding-3-small");

        System.out.println("==============================================");
        System.out.println("  Azure DocumentDB - Compare All Algorithms");
        System.out.println("==============================================");
        System.out.printf("  Query:   \"%s\"%n", queryText);
        System.out.printf("  Top K:   %d%n", topK);
        System.out.printf("  Metrics: COS, L2, IP%n");
        System.out.printf("  Algos:   IVF, HNSW, DiskANN%n");
        System.out.println();

        List<SearchResult> results = new ArrayList<>();

        try (MongoClient mongoClient = Utils.getMongoClient()) {
            MongoDatabase database = mongoClient.getDatabase(databaseName);
            MongoCollection<Document> collection = database.getCollection(COLLECTION_NAME);

            // Load data ONCE into the single collection
            System.out.println("  Loading data from: " + dataFile);
            List<Document> data = Utils.readJsonFile(dataFile);
            System.out.printf("  Loaded %d documents%n", data.size());

            collection.drop();
            System.out.println("  Collection reset.");
            int batchSize = Integer.parseInt(System.getenv().getOrDefault("LOAD_SIZE_BATCH", "100"));
            Utils.insertData(collection, data, batchSize);

            // Generate ONE embedding for the query (reused for all 9 searches)
            OpenAIClient aiClient = Utils.getOpenAIClient();
            System.out.printf("%n  Generating embedding for: \"%s\"%n", queryText);
            List<Float> queryVector = Utils.getEmbedding(aiClient, queryText, model);
            System.out.printf("  Embedding generated (%d dimensions)%n%n", queryVector.size());

            // Convert to doubles for BSON
            List<Double> vectorAsDoubles = queryVector.stream()
                    .map(Float::doubleValue)
                    .toList();

            // Run 9 algorithm × metric combinations sequentially (create→search→drop)
            // DocumentDB does not allow multiple vector indexes of the same kind
            // on the same field path simultaneously.
            System.out.println("  Running 9 algorithm × metric combinations...\n");
            for (String algo : ALGORITHMS) {
                for (String metric : METRICS) {
                    String indexName = String.format("vector_%s_%s", algo, metric.toLowerCase());

                    // 1. Drop all existing vector indexes
                    dropVectorIndexes(collection, vectorField);

                    // 2. Create this specific index
                    createIndex(database, collection, vectorField, dimensions, algo, metric);
                    System.out.printf("  ✓ %s created%n", indexName);

                    // 3. Search with retries while the index becomes available
                    List<Document> searchResults = performSearchWithRetry(
                            collection, vectorAsDoubles, vectorField, topK, indexName);
                    if (searchResults.isEmpty()) {
                        results.add(new SearchResult(algo.toUpperCase(), metric, "(failed)", 0.0, "(failed)", 0.0));
                        continue;
                    }

                    // 4. Extract top 2 results
                    String top1Name = "-"; double top1Score = 0.0;
                    String top2Name = "-"; double top2Score = 0.0;
                    if (!searchResults.isEmpty()) {
                        Document top1 = searchResults.get(0);
                        top1Name = top1.getString("HotelName") != null ? top1.getString("HotelName") : "-";
                        top1Score = top1.getDouble("score") != null ? top1.getDouble("score") : 0.0;
                    }
                    if (searchResults.size() > 1) {
                        Document top2 = searchResults.get(1);
                        top2Name = top2.getString("HotelName") != null ? top2.getString("HotelName") : "-";
                        top2Score = top2.getDouble("score") != null ? top2.getDouble("score") : 0.0;
                    }
                    results.add(new SearchResult(algo.toUpperCase(), metric, top1Name, top1Score, top2Name, top2Score));
                }
            }

            // Print comparison table
            printComparisonTable(results);

            int successCount = (int) results.stream().filter(r -> !r.top1Name().equals("(failed)")).count();
            if (successCount == 0) {
                throw new IllegalStateException("All 9 comparisons failed — no algorithm returned results.");
            }

            System.out.printf("%nSummary: %d succeeded, %d failed%n", successCount, 9 - successCount);

            // Cleanup: drop the comparison collection
            System.out.println("\n  Cleanup: dropping comparison collection...");
            collection.drop();
            System.out.println("  Cleanup: dropped collection 'hotels'");
        }
    }

    private static void dropVectorIndexes(MongoCollection<Document> collection, String vectorField) {
        for (Document idx : collection.listIndexes()) {
            String name = idx.getString("name");
            Document key = idx.get("key", Document.class);
            if (key != null && "cosmosSearch".equals(key.getString(vectorField))) {
                try {
                    collection.dropIndex(name);
                } catch (Exception e) {
                    // Ignore if index doesn't exist
                }
            }
        }
    }

    private static void createIndex(MongoDatabase database, MongoCollection<Document> collection,
                                    String vectorField, int dimensions,
                                    String algo, String metric) {
        String indexName = String.format("vector_%s_%s", algo, metric.toLowerCase());

        Document cosmosSearchOptions = new Document()
                .append("dimensions", dimensions)
                .append("similarity", metric);

        switch (algo) {
            case "ivf" -> cosmosSearchOptions
                    .append("kind", "vector-ivf")
                    .append("numLists", 1);
            case "hnsw" -> cosmosSearchOptions
                    .append("kind", "vector-hnsw")
                    .append("m", 16)
                    .append("efConstruction", 64);
            case "diskann" -> cosmosSearchOptions
                    .append("kind", "vector-diskann")
                    .append("maxDegree", 32)
                    .append("lBuild", 50);
        }

        Document indexDefinition = new Document()
                .append("name", indexName)
                .append("key", new Document(vectorField, "cosmosSearch"))
                .append("cosmosSearchOptions", cosmosSearchOptions);

        Document command = new Document("createIndexes", collection.getNamespace().getCollectionName())
                .append("indexes", List.of(indexDefinition));

        try {
            database.runCommand(command);
        } catch (Exception e) {
            // Idempotent: ignore if index already exists
            if (!e.getMessage().contains("already exists")) {
                throw e;
            }
        }
    }

    private static List<Document> performSearch(MongoCollection<Document> collection,
                                                List<Double> vectorAsDoubles,
                                                String vectorField, int topK) {
        Document searchStage = new Document("$search", new Document("cosmosSearch", new Document()
                .append("vector", vectorAsDoubles)
                .append("path", vectorField)
                .append("k", topK)));

        Document projectStage = new Document("$project", new Document()
                .append("_id", 0)
                .append("HotelName", 1)
                .append("Description", 1)
                .append("score", new Document("$meta", "searchScore")));

        List<Document> pipeline = List.of(searchStage, projectStage);
        List<Document> results = new ArrayList<>();
        collection.aggregate(pipeline).forEach(results::add);
        return results;
    }

    private static List<Document> performSearchWithRetry(MongoCollection<Document> collection,
                                                         List<Double> vectorAsDoubles,
                                                         String vectorField,
                                                         int topK,
                                                         String indexName) {
        int maxRetries = 5;
        int retryDelayMs = 2000;

        for (int attempt = 0; attempt <= maxRetries; attempt++) {
            List<Document> results = performSearch(collection, vectorAsDoubles, vectorField, topK);
            if (!results.isEmpty()) {
                return results;
            }

            if (attempt < maxRetries) {
                System.out.printf("  No results for %s yet. Retrying in 2 seconds (%d/%d)...%n",
                        indexName, attempt + 1, maxRetries);
                try {
                    Thread.sleep(retryDelayMs);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }

        System.out.printf("  Search for %s did not return results after %d retries. Recording as failed.%n",
                indexName, maxRetries);
        return List.of();
    }

    private static void printComparisonTable(List<SearchResult> results) {
        System.out.println("┌──────────┬────────┬────────────────────────────┬────────┬────────────────────────────┬────────┬───────┐");
        System.out.printf("│ %-9s│ %-7s│ %-27s│ %-7s│ %-27s│ %-7s│ %-6s│%n",
                "Algorithm", "Metric", "Top 1 Result", "Score", "Top 2 Result", "Score", "Diff");
        System.out.println("├──────────┼────────┼────────────────────────────┼────────┼────────────────────────────┼────────┼───────┤");

        for (int i = 0; i < results.size(); i++) {
            SearchResult r = results.get(i);
            double diff = Math.abs(r.top1Score() - r.top2Score());
            String top1Display = r.top1Name().length() > 27 ? r.top1Name().substring(0, 24) + "..." : r.top1Name();
            String top2Display = r.top2Name().length() > 27 ? r.top2Name().substring(0, 24) + "..." : r.top2Name();
            System.out.printf("│ %-9s│ %-7s│ %-27s│ %-7.4f│ %-27s│ %-7.4f│ %-6.4f│%n",
                    r.algorithm(), r.metric(), top1Display, r.top1Score(), top2Display, r.top2Score(), diff);
            if (i < results.size() - 1) {
                System.out.println("├──────────┼────────┼────────────────────────────┼────────┼────────────────────────────┼────────┼───────┤");
            }
        }
        System.out.println("└──────────┴────────┴────────────────────────────┴────────┴────────────────────────────┴────────┴───────┘");
    }

    private record SearchResult(
            String algorithm,
            String metric,
            String top1Name,
            double top1Score,
            String top2Name,
            double top2Score) {
    }
}

Main.java

package com.azure.documentdb.selectalgorithm;

public class Main {

    public static void main(String[] args) {
        System.out.println("==============================================");
        System.out.println("  Azure DocumentDB - Compare All Algorithms");
        System.out.println("==============================================");
        System.out.println();

        CompareAll.run();

        System.out.println("==============================================");
        System.out.println("  Comparison complete.");
        System.out.println("==============================================");
    }
}

This main comparison logic provides:

  • Algorithm comparison logic: Tests all combinations of algorithms and similarity functions.
  • Collection management: Creates separate collections for each configuration.
  • Data loading: Inserts hotel data in batches.
  • Index creation: Creates vector indexes for each algorithm and metric combination.
  • Performance measurement: Measures average query latency.
  • Results display: Outputs comparison table.

Run the code

  1. Sign in with Azure CLI for passwordless authentication:

    az login
    
  2. Compile the project:

    mvn clean compile
    

    Verify that the build output ends with BUILD SUCCESS.

  3. Run the comparison entry point. Main.java calls CompareAll.run() and always executes all 9 combinations (3 algorithms × 3 metrics):

    mvn exec:java -Dexec.mainClass="com.azure.documentdb.selectalgorithm.Main"
    

The program prints output similar to the following:

==============================================
  Azure DocumentDB - Compare All Algorithms
==============================================
  Query:   "luxury hotel near the beach"
  Top K:   5
  Metrics: COS, L2, IP
  Algos:   IVF, HNSW, DiskANN

  Loading data from: data/Hotels_Vector.json
  Loaded 50 documents
  Collection reset.

  Generating embedding for: "luxury hotel near the beach"
  Embedding generated (1536 dimensions)

  Running 9 algorithm × metric combinations...
  ✓ vector_ivf_cos created
  ✓ vector_ivf_l2 created
  ✓ vector_ivf_ip created
  ✓ vector_hnsw_cos created
  ✓ vector_hnsw_l2 created
  ✓ vector_hnsw_ip created
  ✓ vector_diskann_cos created
  ✓ vector_diskann_l2 created
  ✓ vector_diskann_ip created
┌──────────┬────────┬────────────────────────────┬────────┬────────────────────────────┬────────┬───────┐
│ Algorithm│ Metric │ Top 1 Result               │ Score  │ Top 2 Result               │ Score  │ Diff  │
├──────────┼────────┼────────────────────────────┼────────┼────────────────────────────┼────────┼───────┤
│ IVF      │ COS    │ Ocean Water Resort & Spa   │ 0.6184 │ Windy Ocean Motel          │ 0.5056 │ 0.1128│
├──────────┼────────┼────────────────────────────┼────────┼────────────────────────────┼────────┼───────┤
│ IVF      │ L2     │ Ocean Water Resort & Spa   │ 0.8736 │ Windy Ocean Motel          │ 0.9943 │ 0.1208│
├──────────┼────────┼────────────────────────────┼────────┼────────────────────────────┼────────┼───────┤
│ IVF      │ IP     │ Ocean Water Resort & Spa   │ 0.6184 │ Windy Ocean Motel          │ 0.5056 │ 0.1128│
├──────────┼────────┼────────────────────────────┼────────┼────────────────────────────┼────────┼───────┤
│ HNSW     │ COS    │ Ocean Water Resort & Spa   │ 0.6184 │ Windy Ocean Motel          │ 0.5056 │ 0.1128│
├──────────┼────────┼────────────────────────────┼────────┼────────────────────────────┼────────┼───────┤
│ HNSW     │ L2     │ Ocean Water Resort & Spa   │ 0.8736 │ Windy Ocean Motel          │ 0.9943 │ 0.1208│
├──────────┼────────┼────────────────────────────┼────────┼────────────────────────────┼────────┼───────┤
│ HNSW     │ IP     │ Ocean Water Resort & Spa   │ 0.6184 │ Windy Ocean Motel          │ 0.5056 │ 0.1128│
├──────────┼────────┼────────────────────────────┼────────┼────────────────────────────┼────────┼───────┤
│ DISKANN  │ COS    │ Ocean Water Resort & Spa   │ 0.6184 │ Windy Ocean Motel          │ 0.5056 │ 0.1128│
├──────────┼────────┼────────────────────────────┼────────┼────────────────────────────┼────────┼───────┤
│ DISKANN  │ L2     │ Ocean Water Resort & Spa   │ 0.8736 │ Windy Ocean Motel          │ 0.9943 │ 0.1208│
├──────────┼────────┼────────────────────────────┼────────┼────────────────────────────┼────────┼───────┤
│ DISKANN  │ IP     │ Ocean Water Resort & Spa   │ 0.6184 │ Windy Ocean Motel          │ 0.5056 │ 0.1128│
└──────────┴────────┴────────────────────────────┴────────┴────────────────────────────┴────────┴───────┘

Summary: 9 succeeded, 0 failed

  Cleanup: dropping comparison collection...
  Cleanup: dropped collection 'hotels'
==============================================
  Comparison complete.
==============================================

The Diff column shows the score gap between the top-1 and top-2 results. A smaller diff indicates the algorithm found results with more similar relevance scores.

Choosing the right algorithm

Important

For production workloads, start with DiskANN on an M30+ cluster. DiskANN supports higher embedding dimensions, uses less cluster memory, and is less likely to require an index redesign as your models evolve.

Use this quick-reference table to select the right algorithm for your workload:

Scenario Algorithm Cluster tier Max dimensions
Dev/test, demos, small datasets IVF M10+ 2,000
Production (default) DiskANN M30+ 16,000
Production (max recall priority) HNSW M30+ 8,000

IVF (inverted file index):

  • Best for: Test environments, demos, and small clusters
  • Pros: Fast to build, low resource requirements
  • Cons: Lower recall compared to graph-based algorithms at scale
  • Tune: Increase numLists for larger datasets, increase nProbes for better recall

DiskANN (disk-based approximate nearest neighbor) — recommended for production:

  • Best for: Production workloads on M30+ clusters
  • Pros: Supports embeddings up to 16,000 dimensions, keeps most index data on disk freeing cluster memory for reads and writes, lighter index updates, easier backups, faster recovery
  • Cons: Requires M30+ cluster tier
  • Tune: Increase maxDegree and lBuild for better accuracy, increase lSearch for better recall
  • Why default: As embedding models evolve (some already exceed 8,000 dimensions), DiskANN avoids costly index redesigns. Its disk-based architecture also means your cluster memory stays available for operational workloads rather than index storage.

HNSW (hierarchical navigable small world):

  • Best for: Production workloads on M30+ clusters where maximum recall is the top priority
  • Pros: Excellent recall, fast queries
  • Cons: Requires M30+ cluster tier, supports embeddings up to 8,000 dimensions (vs 16,000 for DiskANN), higher memory usage since the full graph lives in RAM
  • Tune: Increase m and efConstruction for better index quality, increase efSearch for better recall

Choosing the right similarity function

Function Score meaning Best for
COS (Cosine) Higher = more similar (0–1) Text embeddings (normalized vectors)
L2 (Euclidean) Lower = more similar (distance) When magnitude matters
IP (Inner Product) Higher = more similar Equivalent to COS for normalized vectors

For the text-embedding-3-small model used in this quickstart, COS (cosine similarity) is recommended because OpenAI embeddings are normalized and optimized for cosine similarity.

Troubleshooting

Issue Solution
MongoTimeoutException Verify the DOCUMENTDB_CLUSTER_NAME environment variable, and ensure your IP is in the DocumentDB firewall rules.
MongoSecurityException Verify your Microsoft Entra token is valid. Run az login to refresh your credentials.
Maven build failures Run mvn dependency:resolve to check for missing dependencies. Ensure Java 21 or later is installed.
No plugin found for prefix 'exec' Add exec-maven-plugin to your pom.xml as shown in this article.
Empty search results Index may not be ready. The sample retries up to 5 times with 2-second intervals after index creation. If results are still empty, increase the wait time or verify index status with the DocumentDB for VS Code extension.

Clean up resources

Remove the database using the DocumentDB for VS Code extension:

  1. Install the DocumentDB for VS Code extension.
  2. Connect to your Azure DocumentDB cluster.
  3. Expand the cluster, right-click the Hotels database, and select Drop Database.