Skip to content
zvec-aiPublic

zvec-java

English | 简体中文

Maven Central CI Java 8+ License

zvec-java provides industrial-grade Java bindings for the Zvec vector database C API, built on JavaCPP for JNI binding generation. JavaCPP auto-generates the JNI glue from zvec/c_api.h, bundles the per-platform native libraries into the JAR, and extracts and loads them at runtime — no hand-written JNI code, and no manual library-path configuration required.

Features

  • JavaCPP + JNI: JavaCPP parses c_api.h to auto-generate the low-level binding class ZvecNative and the JNI glue, balancing performance and maintainability.
  • Cross-platform, zero-config: native libraries (libzvec_c_api + libjniZvecNative) are packed into the JAR under platform-arch directories and loaded automatically at runtime with no configuration.
  • High-level wrappers: type-safe, resource-safe Java objects layered on top of the generated ZvecNative.
  • AutoCloseable resource management: every object holding native resources implements AutoCloseable for use with try-with-resources.
  • Rich index support: HNSW, IVF, Flat, Invert (inverted), Vamana, DiskANN and IVF-RaBitQ (zvec ≥ v0.7.0), plus quantized variants (FP16/INT8/INT4/RaBitQ). Platform availability follows zvec itself: DiskANN requires Linux x86_64/ARM64, macOS ARM64 or Windows x86_64, and IVF-RaBitQ requires Linux x86_64 or Windows x86_64; on the remaining platforms — notably macOS Intel and Windows ARM64 — the native layer reports NotSupported.
  • Document iteration: snapshot iterators over collections with output-field selection (Collection.createIterator, zvec ≥ v0.7.0).
  • Jieba FTS out of the box: the default cppjieba dictionary and HMM model are compiled into the native library (zvec ≥ v0.8.0), so the jieba full-text tokenizer needs no setup — nothing to download, extract or point at.
  • Many data types: 30+ field types, including sparse/dense vectors of various dimensions.
  • Java 8+: compatible with Java 8 and above.

Installation

Released artifacts are published to Maven Central as org.zvec:zvec-java. Adding the dependency is the entire setup: the JAR already carries the native libraries, so there is no separate native install step and no library-path configuration.

Maven

<dependency>
    <groupId>org.zvec</groupId>
    <artifactId>zvec-java</artifactId>
    <version>0.8.0</version>
</dependency>

Gradle

implementation 'org.zvec:zvec-java:0.8.0'

org.bytedeco:javacpp comes in transitively, so you do not need to declare it. Java 8 or newer is required.

The public API lives in the org.zvec.binding package: Zvec, Collection, Doc, Schema, IndexParams and VectorQuery are all there rather than under org.zvec.

Picking an artifact

The version tracks the bundled zvec native version: 0.8.0 ships zvec v0.8.0-beta2, 0.7.0 ships zvec v0.7.0.

Artifact Contents Pick it when
(no classifier) classes + natives for all supported platforms You want one dependency that runs anywhere. Simplest choice, largest download.
macosx-arm64 classes + macOS ARM64 natives You deploy to a single known platform and want a smaller artifact.
macosx-x86_64 classes + macOS Intel natives Same, for Intel Macs.
linux-x86_64 classes + Linux x86_64 natives Same, for Linux x86_64.
linux-arm64 classes + Linux ARM64 natives Same, for Linux ARM64.
windows-x86_64 classes + Windows x86_64 natives Same, for Windows x86_64.
windows-arm64 classes + Windows ARM64 natives Same, for a native ARM64 JVM on Windows.
nolib classes, no natives You build or ship zvec_c_api yourself and point the loader at it (see How are native libraries loaded?).

Single-platform classifiers:

<dependency>
    <groupId>org.zvec</groupId>
    <artifactId>zvec-java</artifactId>
    <version>0.8.0</version>
    <classifier>linux-x86_64</classifier>
</dependency>
implementation 'org.zvec:zvec-java:0.8.0:linux-x86_64'

Supported platforms: macOS ARM64 and macOS Intel (x86_64), Linux x86_64, Linux ARM64, Windows x86_64 and Windows ARM64. The Linux native libraries are built in the manylinux_2_28 images and need glibc 2.27 or newer, so Ubuntu 18.04+, Debian 10+, RHEL/CentOS 8+ and Fedora 28+ are all covered. Both macOS libraries carry an 11.0 deployment target as of 0.8.0, so macOS 11 (Big Sur) and later are covered; the published 0.7.0 macOS libraries were built without one and therefore load only on macOS 15 or newer.

Two platforms ship a reduced index set, because zvec compiles DiskAnn and IVF-RaBitQ only for the architectures listed under Features: on macOS Intel and Windows ARM64 both index types report NotSupported. Everything else, including the jieba full-text tokenizer, is available on all six.

On Windows on ARM, an x86_64 JVM running under emulation reports windows-x86_64 to JavaCPP and therefore picks up that classifier, which also works; the windows-arm64 classifier is for a native ARM64 JVM.

From here, jump to Code Examples — Zvec.initialize(null) is the only setup call you need.

Quick Start

Everything below builds the binding from source. That is what you want for development, or when you need a native library for a platform/architecture the released artifacts do not cover. If you only want to use zvec-java, the Maven Central dependency from Installation is enough and you can skip straight to Code Examples.

Prerequisites

Tool Minimum version Purpose
JDK 8 Compile & run
Maven 3.6 Build
C++ compiler (clang / gcc / MSVC) C++17 support JavaCPP compiles the JNI glue
CMake + Ninja 3.30 / 1.11 Build the Zvec C library

Get the source (with submodules)

The Zvec core is included as a git submodule at ./zvec:

Version requirement: the submodule is pinned to zvec v0.8.0-beta2 (8397071). Relative to v0.7.0 the C API drops the four jieba dictionary-directory entry points — the default dictionary and HMM model are now compiled into libzvec_c_api — and adds ZVEC_DATA_TYPE_VECTOR_UINT8 plus the Windows overlapped-I/O backend; the bindings follow both. Everything v0.7.0 added (DiskANN / IVF-RaBitQ index and query parameters, the collection document iterator, I/O backend introspection) is still exposed.

git clone https://github.com/zvec-ai/zvec-java.git
cd zvec-java
git submodule update --init --recursive

Build the Zvec C library

cd zvec
mkdir -p build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_C_BINDINGS=ON -G Ninja
cmake --build . --target zvec_c_api -j
# Artifacts are placed under zvec/build/lib/
cd ../..

CMake 4.x: add -DCMAKE_POLICY_VERSION_MINIMUM=3.5 to the cmake line. Some of zvec's third-party subprojects still declare a cmake_minimum_required below 3.5, which CMake 4 refuses to configure; zvec's own iOS workflow passes the same flag.

Build the Java binding

# By default, headers and libraries are read from the ./zvec submodule (zvec.home=${project.basedir}/zvec)
mvn package

# To reuse an existing Zvec checkout (e.g. a sibling directory), override with -Dzvec.home:
mvn package -Dzvec.home=/path/to/zvec

Key build-time properties:

Property Default Description
zvec.home ${project.basedir}/zvec Zvec core root directory (submodule)
zvec.include.path ${zvec.home}/src/include Header directory (JavaCPP parse)
zvec.lib.path ${zvec.home}/build/lib Link library directory (JavaCPP link)

Run tests

mvn test
# Or specify the Zvec location
mvn test -Dzvec.home=/path/to/zvec

Run the example

The packaged fat JAR already embeds the native library for the current platform, so no library-path configuration is needed at runtime:

mvn package -DskipTests
java -jar target/zvec-java-0.8.1-SNAPSHOT-with-dependencies.jar

0.8.1-SNAPSHOT is the version main builds under between releases. It names the JAR this checkout produces — it is not on Maven Central and cannot be resolved as a dependency. For that, use the published version in the Installation snippets, which never carries -SNAPSHOT.

Smoke-test a built artifact

scripts/smoke-test.sh loads an already-built JAR the way a consumer does and exercises it end to end: JavaCPP unpacks this platform's natives out of the JAR, then a real collection is created, written to and queried by both vector search and jieba full-text search. It needs a JDK and nothing else — no Docker, no Maven, no zvec checkout.

scripts/smoke-test.sh --jar target/zvec-java-0.8.1-SNAPSHOT.jar

# Or resolve the JAR out of a Maven-layout directory, e.g. a Central Portal
# deployment bundle downloaded before publishing it
scripts/smoke-test.sh --repo /tmp/central-staging --version 0.8.0
scripts/smoke-test.sh --repo /tmp/central-staging --version 0.8.0 --classifier linux-arm64

To prove the shipped .so files really load on an older distro, run it on a machine whose glibc is at or below the documented floor — passing on a newer one proves nothing. The CI Publish JAR workflow does exactly this in its smoke-test job: inside manylinux_2_28 on both linux architectures, after the bundle has been uploaded to the Central Portal but while it is still sitting at VALIDATED, before anyone clicks Publish.

Project Structure

zvec-java/
├── pom.xml                                          # Maven build (two-stage JavaCPP plugin: parse + build)
├── zvec/                                            # git submodule: Zvec core
└── src/
    ├── main/java/org/zvec/binding/
    │   ├── presets/ZvecConfig.java                  # JavaCPP InfoMapper: guides parsing of c_api.h
    │   ├── ZvecNative.java                          # [generated] low-level JNI binding (do not edit; git-ignored)
    │   ├── NativeSupport.java                        # Bridging helpers: String <-> const char*, etc.
    │   ├── NativeLoader.java                         # Three-tier native library loader
    │   ├── Zvec.java                                # Top-level entry: init, version, Collection factory
    │   ├── Collection.java                          # Collection ops (CRUD, search)
    │   ├── CollectionOptions.java / CollectionSchema.java / CollectionStats.java
    │   ├── FieldSchema.java                         # Field schema definitions
    │   ├── Doc.java                                 # Document CRUD (read/write typed fields)
    │   ├── IndexParams.java                         # Index params (HNSW/IVF/Flat/Invert/Vamana/DiskANN/IVF-RaBitQ)
    │   ├── VectorQuery.java / GroupByVectorQuery.java / MultiQuery.java / SubQuery.java
    │   ├── FlatQueryParams.java / HnswQueryParams.java / IvfQueryParams.java /
    │   │   IvfRabitqQueryParams.java / DiskAnnQueryParams.java /
    │   │   VamanaQueryParams.java / FtsQueryParams.java  # typed query params, one per index family
    │   ├── FtsPayload.java                          # jieba full-text search payload
    │   ├── DocIterator.java / IteratorOptions.java  # v0.7.0 collection iterator
    │   ├── IoBackendType.java                       # v0.7.0 I/O backend enum
    │   ├── ConfigData.java / LogConfig.java
    │   ├── ZvecException.java
    │   └── DataType / IndexType / MetricType / QuantizeType / LogLevel / DocOperator / ErrorCode (enums)
    └── test/java/org/zvec/binding/
        ├── ZvecTest.java                            # Basic API tests
        ├── TestSupport.java                         # Test base (guarded init + indexed collection/vector helpers)
        ├── DocCoverageTest.java                     # Doc metadata / UTF-8 / exception strong assertions
        ├── SchemaIndexConfigCoverageTest.java       # Schema / IndexParams (out params) / Config / exceptions
        ├── CollectionQueryCoverageTest.java         # DML/DQL strong assertions (query/update/delete/filter)
        ├── ApiCoverageTest.java                     # Wider API surface: enum codes, DiskANN / IVF-RaBitQ / FTS params, multi-query, iterator, I/O backend
        └── SearchIntegrationTest.java               # End-to-end search: FTS-only, jieba on the embedded dict, hybrid vector + FTS, multi-query fan-out

Code Examples

Initialize and shut down

// Initialize with default configuration
Zvec.initialize(null);

// Custom configuration
try (ConfigData config = new ConfigData()) {
    config.setQueryThreadCount(4);
    config.setMemoryLimit(512 * 1024 * 1024L); // 512MB
    config.setConsoleLog(LogLevel.INFO);
    Zvec.initialize(config);
}

// Shut down (call before process exit)
Zvec.shutdown();

Define a schema and create a collection

CollectionSchema schema = new CollectionSchema("my_collection");

// FP32 vector field
try (FieldSchema vecField = new FieldSchema("embedding", DataType.VECTOR_FP32, false, 128)) {
    try (IndexParams hnsw = IndexParams.createHNSW(MetricType.L2, 32, 200)) {
        vecField.setIndexParams(hnsw);
    }
    schema.addField(vecField);
}

// Metadata field
try (FieldSchema titleField = new FieldSchema("title", DataType.STRING, true, 0)) {
    schema.addField(titleField);
}

// Create and open the collection
Collection coll = Zvec.createAndOpen("/tmp/my_db", schema, null);
schema.close();

Insert and query

// Insert documents
List<Doc> docs = new ArrayList<>();
Doc doc = new Doc();
doc.setPK("doc_001");
doc.addStringField("title", "Hello Zvec");
doc.addVectorFP32Field("embedding", new float[128]); // illustrative: all-zero vector
docs.add(doc);
coll.insert(docs);
Doc.freeDocs(docs);
coll.flush();

// Vector query
try (VectorQuery query = new VectorQuery()) {
    query.setTopK(10);
    query.setFieldName("embedding");
    query.setQueryVector(new float[128]); // query vector

    List<Doc> results = coll.query(query);
    for (Doc d : results) {
        System.out.printf("id=%s, score=%.4f%n", d.getPK(), d.getScore());
    }
    Doc.freeDocs(results); // results are backed by native memory; free them after use
}
coll.close();

Index types

IndexParams hnsw   = IndexParams.createHNSW(MetricType.L2, 32, 200);
IndexParams hnswQ  = IndexParams.createHNSWQuantized(MetricType.IP, 32, 200, QuantizeType.FP16);
IndexParams ivf    = IndexParams.createIVF(MetricType.COSINE, 256, 100, false);
IndexParams flat   = IndexParams.createFlat(MetricType.L2);
IndexParams invert = IndexParams.createInvert(true, false); // inverted, for text/tags

Dependencies

Dependency Version Purpose License
org.bytedeco:javacpp 1.5.14 JNI code generation + cross-platform native loading Apache-2.0 or GPL-2.0-or-later or GPL-2.0-with-classpath-exception; used under Apache-2.0
org.junit.jupiter:junit-jupiter 5.10.2 Unit tests (test scope only) EPL-2.0

Architecture

  Java application code
       │
       ▼
  High-level API (Zvec / Collection / Doc / VectorQuery …)   type-safe + AutoCloseable
       │
       ▼
  ZvecNative (JavaCPP-generated JNI binding)
       │  JavaCPP-generated JNI glue (libjniZvecNative)
       ▼
  libzvec_c_api.(so|dylib|dll)
       │
       ▼
  Zvec C++ core engine

Build flow: JavaCPP Parser parses c_api.h (guided by presets/ZvecConfig) to generate ZvecNative.java → compile → JavaCPP Generator/Compiler generates and compiles the JNI glue into libjniZvecNative, linked against zvec_c_api → both are packed into the JAR's platform-arch directory.

FAQ

How are native libraries loaded?

The native libraries (zvec_c_api + the JavaCPP JNI glue jnizvec) are resolved by NativeLoader using a three-tier priority (highest to lowest):

  1. Tier 1 · Explicit path: set -Dzvec.native.path=/dir or the ZVEC_NATIVE_PATH environment variable to resolve zvec_c_api from that directory first (implemented internally via JavaCPP's pathsFirst + platform.preloadpath). Intended for local development or custom-built native libraries.
  2. Tier 2 · Classpath / fat JAR: the default behavior — extract from the platform-arch resource directory inside the JAR to a temp directory and load, with no library-path configuration needed.
  3. Tier 3 · System library path: fall back to java.library.path / LD_LIBRARY_PATH / DYLD_LIBRARY_PATH / PATH.

Loading is attempted Tier 1 → Tier 2 → Tier 3; if all three fail, an actionable UnsatisfiedLinkError listing the sources above is thrown to aid diagnosis.

# Tier 1: point at a locally built native library directory
java -Dzvec.native.path=/path/to/zvec/build/lib -jar app.jar
# Or via environment variable
ZVEC_NATIVE_PATH=/path/to/zvec/build/lib java -jar app.jar

A local mvn package produces a fat JAR containing only the native library for the platform it was built on; the CI Publish JAR workflow builds on each platform and aggregates a multi-platform fat JAR bundling native libraries for all platforms (see .github/workflows/publish-jar.yml). That multi-platform JAR is what gets published to Maven Central as org.zvec:zvec-java, alongside the single-platform classifier JARs and the nolib JAR described in Installation.

How does the jieba FTS tokenizer find its dictionary?

It no longer has to look for one. Since zvec v0.8.0 the default cppjieba dictionary and HMM model are LZ4-compressed and compiled into libzvec_c_api, and the tokenizer decompresses them in memory on first use: nothing is written to disk, there is no cache directory, and there is no registration call, so creating a jieba FTS index works with no setup at all.

An external dictionary still takes precedence when you supply one. Resolution order at tokenization time, highest first:

  1. the per-field jieba_dict_dir extra param — pass it as the extraParams JSON of IndexParams.createFTS("jieba", filters, "{\"jieba_dict_dir\":\"/opt/dict\"}")
  2. the ZVEC_JIEBA_DICT_DIR environment variable
  3. the dictionary embedded in the native library

An external directory has to contain jieba.dict.utf8 and hmm_model.utf8. user_dict_path adds terms on top of whichever dictionary was selected, including the embedded one.

Collection.fetch() returns empty / InvalidArgument

fetch() requires the target field to have a forward index. Make sure a forward index is configured in the schema for the fields you want to fetch, or use query() instead.

Memory management

  • Every AutoCloseable object (Collection, Doc, IndexParams, VectorQuery, etc.) should be used within a try-with-resources block, or explicitly close()d in a finally block.
  • The List<Doc> returned by Collection.query() / Collection.fetch() is backed by native memory and must be released with Doc.freeDocs(list) after use.

Doc.validate(...)

The Zvec C API provides no document-level validation function (zvec_doc_validate does not exist); this method throws UnsupportedOperationException. Use CollectionSchema.validate() / FieldSchema.validate() instead.

Contributing

Issues and pull requests are welcome. CONTRIBUTING.md covers the build setup, the conventions the binding follows, how NOTICE is kept in sync with the zvec submodule and how a release is cut. Please report security problems privately, as described in SECURITY.md. This project follows the Code of Conduct, and notable changes are tracked in CHANGELOG.md.

License

This project is licensed under the Apache License 2.0, consistent with the main Zvec project; see zvec/LICENSE.

Third-party license notes

  • JavaCPP (org.bytedeco:javacpp:1.5.14) is triple-licensed: Apache-2.0 OR GPL-2.0-or-later OR GPL-2.0-with-classpath-exception. This project uses JavaCPP under the Apache-2.0 terms.
  • JUnit 5 is used only in the test scope and is licensed under the EPL-2.0. It is not included in the released JAR.
  • The native zvec_c_api library (built from the zvec submodule) statically links RocksDB, which is dual-licensed under the Apache License 2.0 and the GPLv2; it is used here under the Apache-2.0 terms. The only GPL-exclusive part of RocksDB is the PerconaFT-derived range_tree lock manager under utilities/transactions/lock/range/, and zvec never uses pessimistic transactions, so those objects are not linked into the released binaries. Both release pipelines enforce that: every shipped native library is scanned for range_tree / locktree / PessimisticTransaction symbols, and the build fails if any of them show up.
  • The default cppjieba dictionary and HMM model are compiled into the native zvec_c_api library; they come from cppjieba and are licensed under the MIT license.

Releases

Packages

Contributors

Languages