If you build on the JVM and want to use Qdrant, the official client is io.qdrant:client — and it’s built for Java. Every call returns a ListenableFuture, requests are assembled with protobuf builders, and it drags a gRPC/Netty stack onto your classpath. From Kotlin, that means fighting the language:
// official Java client, from Kotlin
val future: ListenableFuture<UpdateResult> = client.upsertAsync("articles", points)
val result = future.get() // block, or bolt on a future→coroutine bridge yourself
Enter fullscreen mode Exit fullscreen mode
You reach for coroutines, you get futures. You want a DSL, you get protobuf builders.
Meet Kdrant
Kdrant is the client you’d actually want to write Kotlin against:
-
Coroutine-first — every operation is a
suspendfunction, with cooperative cancellation and timeouts - Type-safe DSLs for collections, points, payloads, and filters
-
Small footprint — a pure-Kotlin REST engine on Ktor +
kotlinx-serialization; no gRPC, Netty, or protobuf -
Typed errors — a sealed
KdrantExceptionyou can handle exhaustively
It’s stable (1.1.0, SemVer) and published to Maven Central under io.github.nacode-studios.
Quick start
Requires JDK 17+. One dependency:
dependencies {
implementation("io.github.nacode-studios:kdrant-transport-rest:1.1.0")
}
Enter fullscreen mode Exit fullscreen mode
Spin up Qdrant locally:
docker run -p 6333:6333 qdrant/qdrant
Enter fullscreen mode Exit fullscreen mode
Connect, create a collection, upsert, search:
val qdrant = Kdrant(host = "localhost", port = 6333) {
apiKey = System.getenv("QDRANT_API_KEY") // omit for a local, unauthenticated node
requestTimeout = 5.seconds
}
qdrant.use { client ->
client.createCollection("articles") {
vector { size = 1_536; distance = Distance.COSINE }
}
client.upsert("articles", wait = true) {
point(id = 1) {
vector(embedding) // your List<Float> from any embedding model
payload("title" to "Introduction", "lang" to "en", "year" to 2026)
}
}
val hits = client.search("articles") {
query(queryVector)
limit = 5
filter { must { "lang" eq "en" } }
}
}
Enter fullscreen mode Exit fullscreen mode
Kdrant stores and searches vectors you already have — it does not generate embeddings.
A filter DSL that reads like Kotlin
Qdrant’s full filtering model, expressed declaratively:
val query = filter {
must {
"lang" eq "en"
"year" gte 2024
"price" between 10.0..99.0
}
should {
matchAny("tag", "featured", "promo")
geoRadius("location", GeoPoint(lon = 13.40, lat = 52.52), radius = 5_000.0)
}
mustNot { "archived" eq true }
}
Enter fullscreen mode Exit fullscreen mode
Decode results straight into your types
@Serializable data class Article(val title: String, val lang: String)
val articles: List<Hit<Article>> = qdrant.searchAs<Article>("articles") {
query(queryVector); limit = 5
}
val first: Article? = articles.firstOrNull()?.payload
Enter fullscreen mode Exit fullscreen mode
Hybrid search (dense + sparse)
The modern /points/query engine is fully supported. Fuse several prefetch sources with Reciprocal Rank Fusion for true dense + keyword hybrid search:
val hits = qdrant.search("articles") {
prefetch { query(denseVector); using = "text"; limit = 50 }
prefetch { querySparse(indices, values); using = "keywords"; limit = 50 }
rrf() // or dbsf()
limit = 10
}
Enter fullscreen mode Exit fullscreen mode
recommend / discover / context, grouped and batch search, multi-vectors, and a Flow-based scroll are all there too.
Building RAG? It plugs in.
Kdrant ships first-class integrations so you don’t wire it by hand:
-
Spring Boot starter — an auto-configured
QdrantClientbean -
Spring AI — implements
VectorStore -
LangChain4j — implements
EmbeddingStore
There’s a runnable example-rag service (ingest → embed → store → retrieve) with a docker-compose for Qdrant, so you can see it end to end.
The honest tradeoff
For raw throughput and streaming, gRPC/HTTP2 still wins — reach for the official client when that is your bottleneck. Kdrant trades that for idiomatic Kotlin and a much smaller footprint:
Kdrant Officialio.qdrant:client
Wire protocol
REST over Ktor CIO
gRPC (HTTP/2)
Heavy deps
none — pure Kotlin
shaded Netty, protobuf, gRPC, Guava
Added footprint
~3–5 MB
~15–20 MB
API style
suspend + Flow, DSL
ListenableFuture, protobuf builders
GraalVM native
friendly
needs gRPC/Netty/protobuf config
For typical RAG and embedding-search workloads, that’s a trade I’ll take.
Try it
- ⭐ Repo: https://github.com/NaCode-Studios/Kdrant
- 📦 Maven Central:
io.github.nacode-studios:kdrant-transport-rest:1.1.0 - 📖 API docs: https://nacode-studios.github.io/Kdrant/
- 🪪 Apache-2.0
Feedback is very welcome — especially on API ergonomics. If there’s something you’d want from a Kotlin-native Qdrant client, open an issue and let’s talk.
답글 남기기