← Code Recipes
Embeddings & Search

Qdrant Free code examples in C#, Python, Java and JavaScript

Open-source vector database in Rust; runs as one container.

C# Python Java JavaScript

WHAT THIS PAGE COVERS

Hybrid search — dense vectors and keywords scored together.

Model text-embedding-3-small
Package qdrant-client
Checked

Model ids change. If a sample returns a 404 on the model, that is the first thing to check against the official docs.

Install

The .NET client speaks gRPC and is first-party. QdrantClient is thread-safe and meant to be held as a singleton.

dotnet add package Qdrant.Client

docker run -p 6333:6333 -p 6334:6334 qdrant/qdrant

export QDRANT_API_KEY="your-key"     # Cloud only
Create a collection

PORT 6334, NOT 6333. 6333 is the REST port; the .NET client wants gRPC and pointing it at the wrong one fails with a transport error that never mentions ports.

using Qdrant.Client;
using Qdrant.Client.Grpc;

// 6334 is gRPC. 6333 is REST and will not work here.
var client = new QdrantClient("localhost", 6334);

await client.CreateCollectionAsync(
    collectionName: "documents",
    // 1536 = text-embedding-3-small. Fixed for the life of the
    // collection.
    vectorsConfig: new VectorParams { Size = 1536, Distance = Distance.Cosine });
Upsert points

Payload values are Qdrant's own Value type, and the implicit conversions cover strings and numbers — so a page number stays a number and stays filterable as one.

var operation = await client.UpsertAsync(
    collectionName: "documents",
    points: new List<PointStruct>
    {
        new()
        {
            Id = 1,
            Vectors = denseVector,          // float[] of 1536
            Payload =
            {
                ["source"] = "handbook.pdf",
                ["page"] = 4,
                ["text"] = "Error code E-4471 means the pump stalled.",
            },
        },
    });

Console.WriteLine(operation.Status);
Querying

QueryAsync is the current call; SearchAsync is the older one and still works. The filter runs INSIDE the search rather than over its output, so narrowing does not empty the result page.

var results = await client.QueryAsync(
    collectionName: "documents",
    query: queryVector,
    limit: 5);

foreach (var point in results)
{
    Console.WriteLine($"{point.Score:F4}  {point.Id}");
}

// NAMED AND SPARSE VECTORS — the hybrid search the Python and
// JavaScript samples show — have a different builder surface in
// .NET that is not covered in the quickstart. Check the reference
// before reaching for them here rather than adapting the Python
// shape by eye.
Install

Qdrant runs as one container with no dependencies. Local needs no key at all; Qdrant Cloud reads QDRANT_API_KEY.

pip install qdrant-client

docker run -p 6333:6333 -p 6334:6334 qdrant/qdrant

# Cloud only — a local instance needs no credential.
export QDRANT_API_KEY="your-key"
Create a collection

Named vectors are the setup that makes hybrid possible later. Declaring `dense` and `sparse` now costs nothing; retrofitting them means recreating the collection.

import os
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, SparseVectorParams

client = QdrantClient(
    url="http://localhost:6333",
    api_key=os.environ.get("QDRANT_API_KEY"),   # None locally, fine
)

client.create_collection(
    collection_name="documents",
    vectors_config={
        # 1536 = text-embedding-3-small. This width is fixed for good.
        "dense": VectorParams(size=1536, distance=Distance.COSINE),
    },
    sparse_vectors_config={
        "sparse": SparseVectorParams(),
    },
)
Upsert points

A point is an id, its vectors and a payload. The payload is ordinary JSON and is what you filter on — there is no separate metadata schema to declare.

from qdrant_client.models import PointStruct, SparseVector

client.upsert(
    collection_name="documents",
    points=[
        PointStruct(
            id=1,
            vector={
                "dense": dense_vector,                     # 1536 floats
                "sparse": SparseVector(
                    indices=[42, 1337, 9001],              # token ids
                    values=[0.8, 0.5, 0.3],                # their weights
                ),
            },
            payload={
                "source": "handbook.pdf",
                "page": 4,
                "text": "Error code E-4471 means the pump stalled.",
            },
        ),
    ],
)
Hybrid search, fused

TWO PREFETCHES, ONE FUSION. Dense finds passages that mean the same thing; sparse finds the ones containing E-4471. Reciprocal Rank Fusion merges the rankings without needing the two scores to be on the same scale — which they are not.

from qdrant_client.models import Prefetch, FusionQuery, Fusion

results = client.query_points(
    collection_name="documents",
    prefetch=[
        Prefetch(query=dense_vector, using="dense", limit=20),
        Prefetch(
            query=SparseVector(indices=[42, 1337], values=[0.9, 0.4]),
            using="sparse",
            limit=20,
        ),
    ],
    # RRF needs no score normalisation, which is why it is the default
    # choice — cosine similarity and BM25 are not comparable numbers.
    query=FusionQuery(fusion=Fusion.RRF),
    limit=5,
    with_payload=True,
)

for point in results.points:
    print(round(point.score, 4), point.payload["text"])
Filtering, and why it is not a post-filter

Qdrant applies the filter DURING the search, not after it. A post-filter over top-5 often returns nothing; a real filter searches the matching subset and still returns five.

from qdrant_client.models import Filter, FieldCondition, MatchValue, Range

# Index the field first, or filtering falls back to a full scan.
client.create_payload_index(
    collection_name="documents",
    field_name="source",
    field_schema="keyword",
)

results = client.query_points(
    collection_name="documents",
    query=dense_vector,
    using="dense",
    query_filter=Filter(
        must=[
            FieldCondition(key="source", match=MatchValue(value="handbook.pdf")),
            FieldCondition(key="page", range=Range(gte=1, lte=50)),
        ],
    ),
    limit=5,
    with_payload=True,
)
Install

The Java client is gRPC-based and every call returns a ListenableFuture — hence .get() throughout. Port 6334.

# Gradle
implementation("io.qdrant:client:1.15.0")

docker run -p 6333:6333 -p 6334:6334 qdrant/qdrant

export QDRANT_API_KEY="your-key"     # Cloud only
Create a collection

QdrantGrpcClient wraps the channel; QdrantClient wraps that. Build both once — the channel is the expensive part.

import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.Distance;
import io.qdrant.client.grpc.Collections.VectorParams;

public class Setup {
    public static void main(String[] args) throws Exception {
        QdrantClient client = new QdrantClient(
            QdrantGrpcClient.newBuilder("localhost", 6334, false).build());

        client.createCollectionAsync(
            "documents",
            VectorParams.newBuilder()
                .setDistance(Distance.Cosine)
                .setSize(1536)        // text-embedding-3-small
                .build()).get();

        System.out.println(client.listCollectionsAsync().get());
    }
}
Upsert points

The static helpers — id(), vectors(), value() — exist because building the protobuf by hand is unreadable. Import them statically and the call fits on a screen.

import static io.qdrant.client.PointIdFactory.id;
import static io.qdrant.client.ValueFactory.value;
import static io.qdrant.client.VectorsFactory.vectors;

import io.qdrant.client.grpc.Points.PointStruct;
import java.util.List;
import java.util.Map;

PointStruct point = PointStruct.newBuilder()
    .setId(id(1))
    .setVectors(vectors(denseVector))          // float[] of 1536
    .putAllPayload(Map.of(
        "source", value("handbook.pdf"),
        "page", value(4),
        "text", value("Error code E-4471 means the pump stalled.")))
    .build();

client.upsertAsync("documents", List.of(point)).get();
Querying with a filter

queryAsync is the current call. The filter is applied inside the search rather than over its output, so a narrow filter still fills the result page.

import static io.qdrant.client.ConditionFactory.matchKeyword;
import static io.qdrant.client.QueryFactory.nearest;

import io.qdrant.client.grpc.Points.Filter;
import io.qdrant.client.grpc.Points.QueryPoints;
import io.qdrant.client.grpc.Points.ScoredPoint;
import io.qdrant.client.grpc.Points.WithPayloadSelector;

List<ScoredPoint> results = client.queryAsync(
    QueryPoints.newBuilder()
        .setCollectionName("documents")
        .setQuery(nearest(denseVector))
        .setFilter(Filter.newBuilder()
            .addMust(matchKeyword("source", "handbook.pdf"))
            .build())
        .setLimit(5)
        .setWithPayload(WithPayloadSelector.newBuilder().setEnable(true).build())
        .build()).get();

results.forEach(point -> System.out.printf("%.4f  %s%n",
    point.getScore(), point.getPayloadMap().get("text").getStringValue()));
Install

The REST client is the one to use from Node. Local Qdrant needs no credential; Cloud reads QDRANT_API_KEY.

npm install @qdrant/js-client-rest

docker run -p 6333:6333 qdrant/qdrant

export QDRANT_API_KEY="your-key"     # Cloud only
Create a collection

Declare both vector kinds up front. Adding a sparse vector to an existing collection is not a migration — it is a rebuild.

import { QdrantClient } from "@qdrant/js-client-rest";

const client = new QdrantClient({
  url: "http://localhost:6333",
  apiKey: process.env.QDRANT_API_KEY,   // undefined locally, fine
});

await client.createCollection("documents", {
  vectors: {
    // 1536 = text-embedding-3-small. Fixed for the life of the collection.
    dense: { size: 1536, distance: "Cosine" },
  },
  sparse_vectors: {
    sparse: {},
  },
});
Upsert points

The payload is free-form JSON and is what filters read. There is no schema to declare — only an index to add if you intend to filter on a field often.

await client.upsert("documents", {
  wait: true,          // block until it is searchable, not just accepted
  points: [
    {
      id: 1,
      vector: {
        dense: denseVector,                        // 1536 floats
        sparse: {
          indices: [42, 1337, 9001],               // token ids
          values: [0.8, 0.5, 0.3],                 // their weights
        },
      },
      payload: {
        source: "handbook.pdf",
        page: 4,
        text: "Error code E-4471 means the pump stalled.",
      },
    },
  ],
});
Hybrid search, fused

Dense retrieval loses exact tokens — a part number, an error code, a surname. Sparse finds those and nothing else. RRF fuses the two rankings without pretending their scores are comparable.

const results = await client.query("documents", {
  prefetch: [
    { query: denseVector, using: "dense", limit: 20 },
    {
      query: { indices: [42, 1337], values: [0.9, 0.4] },
      using: "sparse",
      limit: 20,
    },
  ],
  // Reciprocal Rank Fusion: merges by position, not by score, so
  // cosine similarity and BM25 never have to be normalised.
  query: { fusion: "rrf" },
  limit: 5,
  with_payload: true,
});

for (const point of results.points) {
  console.log(point.score.toFixed(4), point.payload.text);
}
Filtering during the search

Qdrant filters inside the search rather than after it, so a narrow filter still returns a full page of results. Index the field first or every filtered query is a scan.

await client.createPayloadIndex("documents", {
  field_name: "source",
  field_schema: "keyword",
});

const results = await client.query("documents", {
  query: denseVector,
  using: "dense",
  filter: {
    must: [
      { key: "source", match: { value: "handbook.pdf" } },
      { key: "page", range: { gte: 1, lte: 50 } },
    ],
  },
  limit: 5,
  with_payload: true,
});

QUESTIONS

Which languages are the Qdrant examples in?

C#, Python, Java and JavaScript. Every sample runs as pasted and reads its credentials from an environment variable — no key is ever written into the code.

Which package do I install for Qdrant?

The samples use qdrant-client. The install step on each language tab gives the exact command.

Which Qdrant model do these examples use?

text-embedding-3-small, checked against the official documentation on 26 September 2026. Model ids change often; if a call returns a model-not-found error, check the current list in the official docs.

Where is the official Qdrant documentation?

https://qdrant.tech/documentation/ — it is linked at the bottom of this page, and it is the authority whenever it disagrees with a sample here.

ALSO IN EMBEDDINGS & SEARCH

The authority

Official Qdrant documentation

Model names, pricing and rate limits change faster than any page here can. When this page and the official docs disagree, the official docs are right.

Read the docs ↗

https://qdrant.tech/documentation/

New drops, every week

Free tools & Claude templates, landing regularly.

We keep shipping new free tools, code recipes and Claude Code templates. Follow @freeyantra on Instagram or join the newsletter so you never miss one.

Follow @freeyantra

or get them by email

Double opt-in. Unsubscribe any time. Privacy