Releases: NaCode-Studios/Kdrant
Release list
v2.1.0
Tier 7, complete. Four claims that were previously compiled, argued or asserted are now things a build
proves: the REST engine runs on every target kdrant-core does and the shared contract runs from a
native binary, a scoped token is a credential the client knows about, a native image is built and made
to search, and every published POM names the platforms that module actually has.
Added
kdrant-transport-restis multiplatform (M38).kdrant-corehad compiled for eight
Kotlin/Native targets since2.0.0, and not one of them could send a request: the engine lived in
src/mainand was Ktor CIO on the JVM, so an iOS build got the models, the query DSL and the filter
builders with nothing to put them on the wire.RestQdrantTransportis incommonMainnow and the
engine is chosen per target — CIO on the JVM, unchanged; Darwin on iOS and macOS; Curl on Linux;
WinHttp on Windows. An iOS or Linux consumer depends on the samekdrant-transport-restcoordinate a
JVM one does.
Two engine choices have consequences worth knowing before a stack trace tells you: Darwin is
NSURLSession and inherits App Transport Security, so a plaintexthttp://Qdrant is refused by the
platform before Kdrant sees the request, and Curl links against the system libcurl, which a slim
container image may not have. Kotlin/JS stays out for the reason already written inkdrant-core.kdrant-testkitis multiplatform, which is what makes the above more than a compilation
exercise. The behavioural contract both engines are held to moved tocommonMainas
QdrantClientContractSuite, which knows no test framework; the JUnit and Testcontainers wrapper
stays on the JVM and still declares one test per behaviour. CI runs the same suite from alinuxX64
and amacosArm64binary against a real Qdrant.- Scoped access (M39).
KdrantConfigtakes abearerTokenbesideapiKey, mutually exclusive
with it: a Qdrant JWT narrowed to read-only, to named collections, or to a payload filter deciding
which points a caller may see at all. Both engines send it —Authorization: Bearerover REST, the
same header as gRPC metadata — because the credential belongs to the config rather than to the wire.
kdrant-testkitsigns one for tests throughQdrantJwt; minting tokens for a running system stays
Qdrant's job. KdrantException.Forbidden, a subclass ofUnauthorized, for HTTP 403 and gRPC
PERMISSION_DENIED. A read-only token refused on a write is a different fact from a missing key, and
only one of them is worth retrying. Being a subclass keeps an existing
catch (e: KdrantException.Unauthorized)catching it and keeps awhenover the sealed hierarchy
exhaustive.kdrant-otel(M41), a new module: one OpenTelemetry client span per operation, on the transport
seam, so one implementation covers both engines and a third would inherit it. Attributes follow
OpenTelemetry's database conventions rather than an invented vocabulary. No payload value, vector or
filter reaches an attribute, and a failed span carries the exception type rather than the server's
message, because Qdrant quotes the request back in its errors. It depends on the OpenTelemetry API,
never the SDK, so the exporter stays the consumer's.kdrant-migrate(M42), a new module:migrateCollection(from, to, alias)copies a collection
into one with a different vector size, verifies the result, and moves an alias so readers cross in one
step. It resumes from a cursor after an interruption rather than starting over, and the alias moves
only after the counts match and a sample of queries returns the same neighbours from both collections
above a stated recall. A failed check throwsMigrationVerificationFailedwith the numbers in it and
leaves the alias where it was: a tool that swaps because the copy finished without throwing is a tool
that will one day point production at an empty collection.decorateTransportonKdrant(...)andKdrantGrpc(...), the hookkdrant-otelneeds and the
place a caching or rate-limiting decorator of your own goes.ScrollBuilder.startAt, the id cursor a resumable job over a collection needs. It came out of M42
and is worth more than the migration.- A GraalVM native image (M40).
example-native-imageis compiled with--no-fallbackin CI and made
to answer a real search against a real Qdrant, so the README's claim is a job that fails the day a
dependency starts reflecting rather than a sentence in a table. Measured: 37 ms from process start
to first search, in a 42 MB static binary. The comparison table quotes that instead of the word
friendly.
Building it settled the claim in the second of the two ways it could go. One thing does reflect: Ktor
resolves a serializer from the response type at run time, and kotlinx-serialization answers by looking
for the compiler-generated$$serializer, which a native image cannot find unless the class is
registered.kdrant-transport-restnow ships that registration in its own jar, generated from the
classes on the classpath rather than written by hand, so a model added tomorrow is in the file the
same day and a consumer building a native image writes nothing.
Changed
- Every published module's POM description now names the platforms that module actually has, and
verifyPublishedDescriptionfails the build when one stops being true. The description Maven Central
serves forkdrant-core:2.0.0ends with "Core module for RAG and embedding search on the JVM",
which klibs.io was about to put next to badges reading iOS, macOS, Linux and Windows generated from
the same artifact's own tooling metadata. It did not go stale by accident: everything else moved at
2.0.0and the POM did not, because nothing reads it. The em dashes went with the correction. - A credential no longer requires TLS when the host is a loopback address. A key sent in the clear
across a network is a key someone else has; a request to127.0.0.1never reaches a network. This
only accepts configurations that were previously rejected, and it is what makes a local Qdrant with
an API key work without a certificate. - Three signatures changed shape, and all three need a recompile rather than a jar swap.
Kdrant(...)andKdrantGrpc(...)gaineddecorateTransport, andKdrantConfiggained
bearerToken. Every parameter is optional and every2.0.0call site compiles unchanged, but a
default parameter changes the signature Kotlin emits, so an application compiled against2.0.0
that swaps in the2.1.0jar without rebuilding will not find them. This is the case
STABILITY.md already describes for data classes,
now stated for functions and constructors too.git diff v2.0.0 v2.1.0 -- '*/api/*.api'shows the
seven removed lines, and nothing else was removed. KdrantException.Unauthorizedisopen, soForbiddencan extend it. Opening a class removes
nothing a caller could use.kdrant-transport-rest's JVM classes are published askdrant-transport-rest-jvm, the same move
kdrant-coremade at2.0.0. A Gradle build resolves the variant from the plain coordinate and
changes nothing; a Maven build namingkdrant-transport-resthas to move to the-jvmone.
Fixed
ScrollRequest.offsetwas documented as the id to start after. It is inclusive, which is what
the paging code has always relied on and what Qdrant returns asnext_page_offset.- The count of operations Qdrant serves over HTTP only was eleven in five places and is fourteen.
It was written into a KDoc once and never counted, and from there it reached the README, this
changelog, the stability policy and the migration guide.grep -o 'restOnly("[a-zA-Z]*")' | sort -u | wc -lsettles it, and the number now comes from that rather than from memory. STABILITY.mdsaid2.0.0broke nothing but the artifact layout. It broke two things: that, and
ScrollRequest/SearchRequestgaining ashardKeyparameter, which changed their generatedcopy
andcomponentN. The upgrade section names both.
Internal
- The release workflow's linked-artifacts step can no longer fail a release. On the
v2.0.0tag it
returned 404 for a digest that does have an attestation and took down a job whose jars were already
published and attested. A metadata step that can undo a successful publish is worth less than the
metadata, so it iscontinue-on-errorwith a per-artifact fallback; the run's log still says which
records were not written. - The same step's artifact list now names
kdrant-transport-rest-jvm, and gainskdrant-otel,
kdrant-migrate-jvmandkdrant-koog, which had been missing since the step shipped. - CI gained three jobs: the client contract from a
linuxX64and amacosArm64binary, and the
GraalVM native image. The two native jobs setKDRANT_QDRANT_REQUIRED, which turns the contract's
skip into a failure, because a job that was meant to run it and silently skipped would report green
for having proven nothing.
v2.0.0
Tier 5, complete, and the release the transport seam was built for. kdrant-transport-grpc is an
opt-in gRPC engine behind the same QdrantClient, and kdrant-core compiles for the JVM and eight
Kotlin/Native targets. Adding a second engine changed no line of kdrant-core.
Two things make this a major. kdrant-core's JVM classes moved to kdrant-core-jvm, because the
module is multiplatform now: a Gradle build changes only the version number, a Maven build naming
kdrant-core has to move. And ScrollRequest and SearchRequest gained a shardKey parameter, which
changed their generated constructor and copy, so code that called copy() on either against a 1.x
jar has to be recompiled. Source stays compatible. The multiplatform migration itself changed no public
API at all. See STABILITY.md.
Added
kdrant-koog(M37), a new module: a Koog document storage backed by
Kdrant, so a Koog RAG agent can keep its documents in Qdrant. It implements Koog's search-side storage
interfaces (WriteStorage,LookupStorage,SearchStorage,DeletionStorage) rather than
VectorStorageBackend, which has no search method: Koog's ownEmbeddingStorageranks by streaming
every stored document out of the backend and scoring in memory, and doing that through a vector
database would mean paying for an index and then pulling the whole collection over the network on
every query. Here Qdrant runs the search. The module depends only on Koog's stablerag-base, not on
therag-vectorbeta. Koog'snamespacebecomes a payload field and a filter, so one collection can
hold several of them.- Cluster and sharding (M32), the gap the migration guide used to name as having no Kdrant equivalent.
collectionClusterInfo(name)reads how a collection's shards are spread across peers, including the
transfers in flight;updateCollectionCluster(name, operation)moves, replicates, aborts or drops a
shard; andcreateShardKey/deleteShardKeymanage custom sharding keys. The placement calls return
once the transfer is accepted, not once it has finished, which the KDoc says rather than leaving
it to be discovered. ShardKeyandshardKeyonsearchandscroll, so a query that concerns one region or one tenant
reads that key's shards instead of all of them. A numeric key stays a number on the wire; quoting it
would make Qdrant read it as a different key.ReplicaStatedecodes an unrecognized state from a newer Qdrant toUNKNOWNrather than failing the
whole cluster-info response, the same toleranceCollectionStatusalready had.- Formula reranking and MMR (M35), scoped in M16 and not shipped with it.
formula(expression)rescores the
candidates aprefetchproduced with arithmetic over their score and payload: multiply by a
popularity field, add a bonus for points matching a condition, decay by recency or by distance. The
ExpressionAST covers Qdrant's full operator set, including the three decay curves and
geo_distance.mmr(diversity)reranks a vector query for variety instead of letting ten results
about the same thing crowd the top.
Both are validated against Qdrant's published schema by the contract tests, and the bounds Qdrant
documents (diversity in0..1, a positive decay scale, a midpoint in0..1) are checked where they
are written rather than on the round trip. - Shard-scope snapshots (M36), deferred out of M20 when snapshots first shipped:
createShardSnapshot,
listShardSnapshots,deleteShardSnapshot,recoverShardSnapshot, plus streaming
downloadShardSnapshotanduploadShardSnapshot. On a sharded collection the existing
whole-collection snapshot is every shard at once, which on a large collection is the difference
between a backup that fits in a window and one that does not. Shard ids come from
collectionClusterInfo. kdrant-transport-grpc(M31), the opt-in gRPC engine.KdrantGrpc(host)returns the same
QdrantClientthe REST factory does, over Qdrant'sCollections,Points,SnapshotsandHealth
services on port 6334. REST stays the recommended engine; reach for this one when throughput or
long-lived streaming is the bottleneck, which is the case the README used to concede to the official
client. Nothing changes for a REST user: the module is separate, and a build that does not ask for it
resolves no gRPC, no protobuf and no Netty.
The stubs are generated from Qdrant's own.protofiles, vendored verbatim at v1.18.2, rather than
taken fromio.qdrant:client. grpc-kotlin emits suspend functions andFlows, which is the shape the
transport seam already has, and generating decides the dependency set instead of inheriting a shaded
Netty jar that is most of the official client's footprint.- Both engines are held to one shared client contract (M31,
kdrant-testkit), which runs the same 30
behavioural tests against a real Qdrant over each protocol. The REST tests that came before it
asserted HTTP bodies, which a gRPC engine cannot satisfy by construction. kdrant-coreis a Kotlin Multiplatform library (M25). It builds for the JVM and for eight
Kotlin/Native targets:iosArm64,iosSimulatorArm64,iosX64,macosArm64,macosX64,
linuxArm64,linuxX64andmingwX64. Kotlin/JS is deliberately not among them: there is no JS
engine, so the target would ship models with nothing to send them over, and its test tooling is the
only npm dependency graph this repository would have. The models, DSLs, error hierarchy and client
logic were already free of the JVM, which is what the transport seam was for, so the migration moved
sources intocommonMainand changed one declaration. The engines stay JVM-only, because Ktor CIO and
grpc-java are.- A
commonTestsuite that runs on every target, covering the places a platform could actually
differ: the hand-written serializers, the uint64 point id, integer payload values above 2^53, and the
config's validation.
Changed
-
kdrant-core's artifact layout changed with the multiplatform move. Thekdrant-corecoordinate
now carries Gradle module metadata and the JVM classes live inkdrant-core-jvm. A Gradle build
resolves the right variant from the same coordinate and needs no change; a Maven build names the
artifact directly and must move tokdrant-core-jvm, which the BOM now constrains as well. The
migration changed no public API: the*.apidump is identical either side of it. -
The default dispatcher is platform-dependent, and is the one declaration the migration had to split.
It staysDispatchers.IOon the JVM. On Kotlin/Native it isDispatchers.Default, because the
coroutines library still keeps its native IO dispatcher internal. Passing your own dispatcher works
as before, everywhere. -
Releases are built on macOS. Only a macOS host can compile the Apple targets, so a Linux runner would
publish a release quietly missing its iOS and macOS klibs. -
kdrant-core's-javadoc.jarholds Dokka's HTML output rather than Javadoc HTML: the Dokka Javadoc
generator refuses a multiplatform project. Maven Central requires the jar to exist rather than to be
Javadoc, and HTML is what a Kotlin reader wants. -
Fourteen
QdrantTransportoperations have no gRPC equivalent, because the seam was shaped by
Qdrant's REST API and Qdrant serves these over HTTP only:telemetry,metrics,listIssues,
clearIssues,recoverSnapshot, the snapshot and storage-snapshot transfers, and the six
shard-scope snapshot operations. On the
gRPC engine each throws anUnsupportedOperationExceptionnaming the operation and pointing at REST,
rather than degrading quietly. A snapshot download that returns nothing is a backup that does not
exist. The REST engine is unchanged. -
Releases publish to Maven Central only. The secondary publication to GitHub Packages is gone: it
carried the same artifacts to a registry that requires authentication even for public packages, so it
was a second place to keep in sync and no second way for anyone to depend on Kdrant. Versions up to
and including1.2.0remain on GitHub Packages and are not withdrawn.
Internal
- Release notes are extracted from this file by the release workflow rather than written by hand. A
release body composed separately is a second copy of what the changelog owns, and the two eventually
disagree; derived from here it cannot. The workflow fails the release if the tag has no section. - Every published jar is recorded as a linked artifact, so the repository's Packages panel names what it
built and where it went. Metadata, not a distribution channel: nothing is hosted there. - The set of artifacts the provenance attestation covers is derived from the build instead of listed in
the workflow. A hardcoded list stops covering a module the day one is added and nothing goes red,
which is what happened when the gRPC engine arrived.
v1.2.0
Tier 6, complete. The framework adapters honour metadata filters, deployment scripts get
ensureCollection, an ordered scroll and batchUpdate, observability ships instead of being
reachable by hand, the wire format is held to Qdrant's own schema by contract tests, and switching
from the official client has a guide and measured numbers behind it.
Upgrading is a recompile, not a jar swap. apiCheck reads this release as additive, but new members
on QdrantClient and QdrantTransport break a class that implemented them against 1.1.0, and the
fields added to CollectionInfo, ScrollRequest and Record change their generated copy. Source
stays compatible; see STABILITY.md.
Added
-
Metadata-filter translation for the framework adapters (M26).
kdrant-spring-aiandkdrant-langchain4j
used to throw on any filter expression, which meant a filtered RAG application was not the drop-in swap
the modules advertised. Both now translate their framework's filter model into Kdrant's:
Filter.Expression.toKdrantFilter()for Spring AI andFilter.toKdrantFilter()for LangChain4j, wired
intosimilaritySearch,VectorStore.delete(Expression)andEmbeddingStore.search. Boolean chains
flatten into a singlemust/shouldclause, comparisons pick Qdrant's numeric or RFC 3339range
variant from the value's runtime type, and Spring AI'sIS NULLmaps tois_empty(which, unlike
Qdrant'sis_null, also covers a missing key). A value Qdrant cannot express is rejected with an
IllegalArgumentExceptionrather than dropped, so a filter never silently widens a result set. -
SearchBuilder.filter(Filter)andPrefetchBuilder.filter(Filter), plus
QdrantClient.delete(name, selector, wait)— the entry points a translator needs to pass an
already-built filter, alongside the existing DSL forms. -
ensureCollection(name) { ... }(M27): creates the collection if it is missing and otherwise checks
that the one already there has the dense vector names, sizes and distances, and the sparse vector
names, that were asked for. Returns whether it created the collection, absorbs a create that lost a
race to another process, and fails loudly on a mismatch rather than leaving an application to
discover the wrong vector size on its first upsert. Everything the server defaults (HNSW, optimizers,
quantization) is deliberately not compared. -
The enriched
getCollectionread-back that check reads:CollectionInfo.config(vectors, sparse
vectors, shard number, replication factor, on-disk payload) andCollectionInfo.payloadSchema. An
index type a future Qdrant adds is kept as its wire string rather than failing the whole response. -
An ordered
scroll(M27):scroll("docs") { orderBy("ts", Direction.DESC) }, plusorderByDatetime
for RFC 3339 keys,startFromto resume a partly consumed pass, andRecord.orderValue. Qdrant
returns no page cursor for an ordered scroll, so the client pages on the order value and drops the
points a page repeats at the boundary; each point is still emitted exactly once. A scroll that cannot
advance — more points tied on one order value than fit in a page — fails with a message saying so
instead of silently truncating. -
batchUpdate(name, wait) { ... }(M27): one request applying an ordered, mixed sequence of point,
vector and payload operations. Ordered but not transactional: a later operation sees the effect of
an earlier one, but a failure part-way through leaves the earlier operations applied. -
ScrollBuilder.filter(Filter), matching the search builders. -
kdrant-micrometer(M28), a new module:configureClient = { kdrantMetrics(registry) }times every
request askdrant.requests, tagged with the operation, HTTP method, status and outcome. The operation
tag is the route template, not the URL — collection, field and snapshot names become placeholders, so a
deployment with thousands of collections does not become thousands of time series. -
X-Request-Idcorrelation (M28):Kdrant(host, requestId = { ... })sets the header from the caller's
own trace id, so a Kdrant call can be followed into Qdrant's logs. Off by default, since sending a new
header on every request would change the bytes on the wire for everyone. -
Connection-pool settings on the REST engine factory (M28):
maxConnectionsPerRouteandkeepAliveTime
are parameters ofKdrant(...), not ofKdrantConfig, which stays transport-neutral. This is where the
pool settings declined onKdrantConfigland. -
Contract tests against Qdrant's OpenAPI schema (M29). Every request body the REST engine builds is
captured from a real client call and validated against the schema Qdrant publishes for that endpoint,
with unknown properties treated as failures. Qdrant's document is vendored under
kdrant-transport-rest/src/test/resourcesand pinned to the version the CI matrix runs against, so
refreshing it is how a wire change that would otherwise pass silently becomes a failing build. -
Kover coverage (M29), which the Kotlin 2.4 incompatibility had deferred.
./gradlew koverHtmlReport
covers the six published modules; CI runs it on JDK 17 and enforces a 75% line floor — a floor to
catch a module arriving untested, not a number to inch towards. Current line coverage is 82.8%. -
SLSA build provenance on release (M29): the release workflow assembles the jars, attests them with
actions/attest-build-provenance, and only then publishes, so the attestation covers the exact files
that reach Maven Central and GitHub Packages. -
A migration guide from
io.qdrant:client(M30), mapping the
official client operation by operation, with the differences that actually bite: the port, the
ListenableFuture-to-suspendshift, protobuf builders against the DSL, and where the official
client is still the right tool. -
A dispatchable
Benchmarksworkflow (M30) that runs the JMH harness against a chosen Qdrant image on
a clean runner and uploads the results, and the first
measured numbers from it:searchp50 1.97 ms / p99
5.40 ms,upsertp50 3.37 ms / p99 9.81 ms against Qdrantv1.18.2. Published with the conditions
they were taken under, including the ones that make them a floor rather than a capacity figure: no
network between client and server, a 1 000-point collection, and no concurrency. -
The design rationale in STABILITY.md (M30) now states what a
1.xupgrade actually
guarantees:QdrantClientandQdrantTransportare interfaces to call rather than implement, and a
field added to a public data class changes its generatedcopy, so a minor is a recompile rather
than a jar swap.
Fixed
Directionnow serializes as Qdrant's lowercaseasc/desc. It was only ever written through the
hand-rolled query serializer, which spelled it correctly, so no shipped request was affected; the enum
itself would have sentASCthe moment anything else serialized it.
Internal
- ktlint
12.1.2→14.2.0. Version 14 turns onclass-signatureandfunction-signature, which
collapse a multi-line parameter list onto one line and push the supertype onto its own; both are
disabled in.editorconfig, for the same reason the codebase pickedintellij_ideaover
ktlint_officialin the first place. Two files were rewritten before the rules were turned off. - detekt's
LongParameterList.functionThresholdraised from 8 to 12. TheKdrant(...)factory is a
settings surface likeKdrantConfig, where every parameter pastportis an independently defaulted
option, so the two now get the same allowance.
Install
dependencies {
implementation("io.github.nacode-studios:kdrant-transport-rest:1.2.0")
}Full changelog: https://github.com/NaCode-Studios/Kdrant/blob/v1.2.0/CHANGELOG.md
v1.1.0
1.1.0 is a maintenance and compatibility release on top of 1.0.0. It raises the framework
baseline of the two Spring adapter modules and moves the toolchain forward; Kdrant's own public API
is unchanged, and the *.api dumps are identical.
Changed
kdrant-spring-ainow targets Spring AI2.0(was1.0) andkdrant-spring-boot-starternow
targets Spring Boot4.1(was3.4). Kdrant's own public API is unchanged (the*.apidumps are
identical and all adapter tests pass against the new majors), but these two adapter modules now require
the newer framework generation (Spring Framework 7 / Jakarta EE 11 for the starter). Applications still
on Spring AI 1.x or Spring Boot 3.x should pin those modules to1.0.0until they upgrade.kdrant-core
andkdrant-transport-restare unaffected.kdrant-langchain4jnow builds against LangChain4j1.18.0(was1.0.0) — a backwards-compatible
minor upgrade.
Internal
- Toolchain & tooling: Kotlin
2.4.10, Gradle9.6.1, kotest6.2.2, plus assorted minor/patch dependency
bumps; CI actions run on Node 24. The Kotlin modules now compile withallWarningsAsErrors, so any
compiler deprecation fails the build — keeping the code warning-clean across future dependency upgrades.
Install
dependencies {
implementation("io.github.nacode-studios:kdrant-transport-rest:1.1.0")
}Full changelog: https://github.com/NaCode-Studios/Kdrant/blob/v1.1.0/CHANGELOG.md
v1.0.0
Kdrant's 1.0: the REST client is feature-complete and its public API is now stable under Semantic
Versioning — see STABILITY.md. On top of 0.2.0 (M10–M18), this release adds M19–M24
(aliases, snapshots, service/analytics endpoints, granular transport & observability, a no-boxing hot
path, quality/CI hardening, the Spring Boot / Spring AI / LangChain4j integrations, and the catching
helper).
Added
- Aliases (M19):
updateAliases { createAlias(collection, alias); deleteAlias(alias); renameAlias(from, to) },
applied by the server as one atomic batch — the primitive behind zero-downtime reindexing (build a new
collection, then swap the alias in a single step). PluslistAliases()andlistCollectionAliases(name). - Service & health endpoints (M19):
healthz()/readyz()/livez()(Kubernetes-style probes that return
aBooleanand never throw on a not-ready status),listCollections(),telemetry()andlistIssues()
(raw JSON, since the shape is server-version-specific),clearIssues(), andmetrics()(Prometheus
text-exposition format). - Analytics (M19):
facet(name, key, limit, exact) { filter }— distinct payload-value counts (a histogram
over a key) — and the distance-matrix endpointssearchMatrixPairs(name) { sample; limit; using; filter }
andsearchMatrixOffsets(...)(explicit edge-list and sparse-coordinate forms) for clustering/visualization. - Snapshots & backup/restore (M20):
createSnapshot/listSnapshots/deleteSnapshot/
recoverSnapshot(location, priority, checksum)for a collection, pluscreateStorageSnapshot/
listStorageSnapshots/deleteStorageSnapshotfor the whole storage. Binary transfer is streamed, so a
multi-GB backup is never buffered in memory:downloadSnapshot(...)/downloadStorageSnapshot(...)return
a coldFlow<ByteArray>, anduploadSnapshot(name, data: Flow<ByteArray>, ...)streams a snapshot file back
as a multipart upload.SnapshotPriority(NO_SYNC/SNAPSHOT/REPLICA) sets the source of truth when
recovering into a replicated collection. Note: unlike the mutationwaitflags, snapshotwaitdefaults to
true, matching the Qdrant server default. - Granular transport & observability (M21): a
configureClientescape hatch on theKdrant(...)factory
(anHttpClientConfig<*>hook to install your own plugins — metrics, tracing — tune the CIO engine, or
override any default);connectTimeout/socketTimeouton the client config; and optional
request/response logging vialogLevel = LogLevel.…, which always redacts theapi-keyheader so the
key never reaches the logs. - Streaming ingest (M21):
upsert(name, points: Flow<PointStruct>)andupsert(name, points: Sequence<PointStruct>)
— ingest a large or unbounded source without materializing it all in memory; the engine chunks it by the
configured batch size (sequential, not atomic across chunks, like the DSLupsert). - Ergonomics (M24):
catching { … }— a coroutine-saferunCatchingthat returnsResult<T>but re-throws
CancellationExceptioninstead of trapping it. The exception-based API stays the primary style. - No-boxing hot path (M21): the DSL
vector(f1, f2, …)/vector(*floatArray)(upsert) andquery(f1, f2, …)
(search) now keep the values in aFloatArrayand serialize it directly, avoiding a boxedFloatper element
(VectorData.DenseArray/QueryInterface.VectorArray). Upsert batching is byte-aware: a batch is bounded by
both the point count and a serialized-size cap (maxUpsertBytes, default ~30 MiB), so Qdrant's ~32 MiB REST
limit is respected even for high-dimensional vectors.
Install
dependencies {
implementation("io.github.nacode-studios:kdrant-transport-rest:1.0.0")
}Full changelog: https://github.com/NaCode-Studios/Kdrant/blob/v1.0.0/CHANGELOG.md
v0.2.0
0.2.0 covers Tier 1 "robustness, DX and reach" through Tier 3 "data and collections": the
modern /points/query search engine, payload and vector management, and the correctness work that
had to come first. It opens with a data-loss fix, which is why that section is first.
Fixed
- delete-by-filter data loss: a delete whose filter clauses were all empty (e.g.
delete(c) { must { } }, ormust { if (cond) … }wherecondis false at runtime) is no longer
sent as a match-all filter that would delete every point in the collection. Empty clause blocks now
normalize away, and delete-by-filter rejects an all-empty filter before issuing any request. collectionExistsnow returnsfalseon a404instead of throwing, matching its documented contract.KdrantException.CollectionNotFoundnow carries the server's error message when the server provides one.
Security
- The client rejects a configuration that sets an
apiKeywithoutuseTls, so an API key is never
sent over plaintext HTTP.
Added
- Collection config tuning:
updateCollection { optimizers = …; hnsw = …; quantization = … }(PATCH),
andoptimizers/quantization(QuantizationConfig.Scalar/.Binary) oncreateCollection. - Payload field indexes (
createPayloadIndex(field, PayloadSchemaType.KEYWORD)/deletePayloadIndex), so
filtering on a field scales instead of doing a full scan; and payload mutationssetPayload/
overwritePayload/deletePayload/clearPayloadover a points-or-filter selector. - Vector mutations:
updateVectors(write new vectors to existing points, keeping payload) and
deleteVectors(remove named vectors from the selected points). - Advanced retrieval queries on
search:recommend { positive(...); negative(...); strategy = ... },
discover { target(...); context(...) }, andcontext { pair(...) }. Examples (VectorInput) accept a
dense/sparse vector or a point id. - Batch and grouped search:
searchBatch { search { } … }(several searches in one round-trip, hits per
search) andsearchGroups(groupBy = …) { }returningList<PointGroup>. - Sparse & multi-vectors:
VectorData.Sparse/MultiDense,sparseVector(name) { modifier = Modifier.IDF }
and per-vectormultivectorincreateCollection, andquerySparse(...)/queryMulti(...)— enabling
true dense+sparse hybrid search combined with M14 fusion. Response decoding now degrades an unknown vector
shape toVectorData.Rawinstead of failing the whole response. - Modern
/points/querysearch: a polymorphicquery(nearest by vector or by point id,orderBy,
sample), nestableprefetch { }sub-requests, and hybrid-search fusion (rrf(k, weights)/dbsf()),
pluslookupFromfor cross-collection id lookups. The previousquery(vector)call is unchanged. - Typed payload access:
kdrantJson(public defaultJson),ScoredPoint.payloadAs<T>()/
Record.payloadAs<T>(), andQdrantClient.searchAs<T>(): List<Hit<T>>to decode hit payloads
straight into your own types. - Collection conveniences:
getCollectionOrNull, race-tolerantcreateCollectionIfNotExists(...): Boolean,
and acreateCollection(name, size, distance = COSINE)shorthand. PayloadBuilderindex-assignment sugar:payload["key"] = value(operator set, acceptsnull).- Automatic retries with exponential backoff + jitter for transient failures (HTTP 429/502/503/504 and
transient I/O errors), honoring the server'sRetry-Afterheader. Tunable viamaxRetries,
retryBaseDelay, andretryMaxDelayon the client config (maxRetries = 0disables retries). - Finer error taxonomy:
KdrantException.RateLimited(429, carryingRetry-After),ServiceUnavailable
(503),ServerError(other 5xx), andAlreadyExists(409). - Client-side validation of collection parameters: vector
size,shardNumber, andreplicationFactor
must be positive, with error messages that echo the received value. kdrant-bom— a Bill of Materials module to keepkdrant-coreandkdrant-transport-reston one
aligned version.
Changed
- Server errors (HTTP 5xx other than 503) now surface as
KdrantException.ServerErrorinstead of
Transport, which is now reserved for connection-level I/O failures. HTTP408maps toTimeout
and409toAlreadyExists. local.propertiesis no longer tracked in version control.
Install
dependencies {
implementation("io.github.nacode-studios:kdrant-transport-rest:0.2.0")
}Full changelog: https://github.com/NaCode-Studios/Kdrant/blob/v0.2.0/CHANGELOG.md
v0.1.0
The first release of Kdrant: an idiomatic, coroutine-first Kotlin client for Qdrant, over a REST
transport that sits behind a protocol-independent seam.
Added
- Coroutine-first
QdrantClientwith a pluggableQdrantTransportseam and a default REST/Ktor
engine. - Collection operations:
createCollection(DSL) anddeleteCollection. upsertDSL supporting dense and named vectors, heterogeneous payloads, and automatic batching
under the REST request-size limit.search(nearest-vector query over the unified query API) with a DSL for the query vector,
filter, limit, payload projection, and search params.scrollexposed as a coldFlow<Record>that transparently follows the pagination cursor.deleteby point ids or by filter.- Collection introspection:
collectionExistsandgetCollection(status and point counts). count(optionally filtered) andretrievepoints by id.- Complete filter DSL:
must/should/mustNot/minShouldwith every Qdrant condition type
(match/any/except/text, numeric and datetime ranges,values_count, geo box/radius/polygon,
is_empty/is_null,has_id,has_vector, per-elementnested, and recursive sub-filters). - Typed error hierarchy
KdrantException.
Install
dependencies {
implementation("io.github.nacode-studios:kdrant-transport-rest:0.1.0")
}Full changelog: https://github.com/NaCode-Studios/Kdrant/blob/v0.1.0/CHANGELOG.md