Qdrant Free code examples in C#, Python, Java and JavaScript
Open-source vector database in Rust; runs as one container.
WHAT THIS PAGE COVERS
Hybrid search — dense vectors and keywords scored together.
text-embedding-3-small
qdrant-client
Model ids change. If a sample returns a 404 on the model, that is the first thing to check against the official docs.
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
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 });
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);
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.
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"
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(),
},
)
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.",
},
),
],
)
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"])
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,
)
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
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());
}
}
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();
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()));
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
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: {},
},
});
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.",
},
},
],
});
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);
}
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.
https://qdrant.tech/documentation/