Thanks for your interest! zvec-java is a thin, resource-safe Java layer over the
zvec C API: JavaCPP parses c_api.h, and the
hand-written wrappers in org.zvec.binding turn the generated pointers into
AutoCloseable Java objects.
By participating you agree to the Code of Conduct. Security problems go to SECURITY.md, not to a public issue.
| Tool | Minimum | Purpose |
|---|---|---|
| JDK | 8 | Compile and run (CI builds on Temurin 11) |
| Maven | 3.6 | Build |
| C++ compiler | C++17 | JavaCPP compiles the JNI glue |
| CMake + Ninja | 3.30 / 1.11 | Build the zvec C library |
git clone https://github.com/zvec-ai/zvec-java.git
cd zvec-java
git submodule update --init --recursive
# 1. Build the zvec C library the binding links against
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
cd ../..
# 2. Build and test the binding
mvn test
mvn package # fat JAR with this platform's natives
mvn checkstyle:check # style gate CI runs (rules: config/checkstyle.xml)Point Maven at an existing zvec checkout instead of the submodule with
-Dzvec.home=/path/to/zvec, or at prebuilt libraries with
-Dzvec.lib.path=/path/to/build/lib.
To check an already-built artifact the way a consumer resolves it, run
scripts/smoke-test.sh --jar target/zvec-java-<version>.jar (needs a JDK only).
Checkstyle is deliberately narrow — it only flags things that are actual bugs
(a switch that falls through, equals() without hashCode(), a string
compared with ==, an unused import). Indentation and line length are left to
your IDE, so do not "fix" formatting that the build does not complain about.
presets/ZvecConfig.javais the JavaCPPInfoMapper: it tells the parser how to readzvec/src/include/zvec/c_api.h(which macros to neutralize, how C strings and out-parameters map to Java).- The parser generates
src/main/java/org/zvec/binding/ZvecNative.java. That file is generated and git-ignored — never edit it. To change how a type or function is mapped, editZvecConfig.javaand regenerate. - The JavaCPP generator/compiler turns
ZvecNativeinto the JNI glue (libjniZvecNative), linked againstzvec_c_api. - The classes in
org.zvec.bindingwrapZvecNativeand are the public API.
- Every object that owns native memory implements
AutoCloseableand itsclose()is idempotent;close()nulls the handle so a double close cannot turn into a double free. - Marshal through
NativeSupport(utf8,string,strArray,stringArray) rather than callingBytePointerdirectly, and release native strings withZvecNative.zvec_free. - Convert every C return code with
ZvecException.throwIfError(...); do not swallow non-zero codes. - Helpers that are not part of the public API stay package-private
(
NativeSupport). - Library code does not print to
System.out.
- Bump the
zvecsubmodule to the release that has it and rebuild the C library, thenmvn packageto regenerateZvecNative.java. - Add the wrapper in the matching class, following the conventions above.
- Add a test under
src/test/java/org/zvec/binding/. Prefer strong assertions (read back what you wrote, check ordering and counts) over "did not throw". Skip platform-gated features withassumeTrue(...)so the suite stays green on every OS. - Update
README.mdandREADME_CN.md— the two are kept in sync. - If the submodule bump changed
zvec/NOTICE, regenerateNOTICE(see below).
- Conventional commits (
feat:,fix:,ci:,docs:,test:,build:); explain the why in the body. - CI must pass:
lintruns Checkstyle, thesource-testmatrix builds zvec from the submodule and runs the suite on macOS ARM64, macOS Intel, Linux x86_64, Windows x86_64 and Windows ARM64; thevendor-testmatrix does the same against prebuilt libraries dropped intolib/<platform>/. That directory is gitignored and empty on a fresh clone, sovendor-testskips itself unless a contributor populates it first. - Keep both READMEs and the javadoc of anything public up to date — the javadoc JAR is published to Maven Central.
NOTICE is what Apache-2.0 §4(d) requires us to ship with the redistributed
zvec binaries: our own header followed by zvec/NOTICE reproduced verbatim.
Regenerate it whenever the submodule bump changes the upstream file:
python3 - <<'REGEN'
import io
marker = 'zvec/NOTICE, reproduced verbatim\n' + '=' * 80 + '\n\n'
head = io.open('NOTICE', encoding='utf-8').read().split(marker)[0] + marker
up = io.open('zvec/NOTICE', encoding='utf-8').read()
io.open('NOTICE', 'w', encoding='utf-8').write(head + up)
REGENThat keeps our own header (everything above the zvec/NOTICE, reproduced verbatim banner) and replaces the rest with the current upstream file. The
Publish JAR workflow fails if any line of zvec/NOTICE is missing from ours,
so drift cannot ship silently.
Releases are driven by the zvec version the binding targets; the Maven version
is that version without the leading v (v0.7.0 → 0.7.0).
- Bump the
zvecsubmodule to the release tag and commit the gitlink only — the submodule's own working tree stays dirty from nested submodules andthirdparty/protobuf/build products, which must not be committed. - Update
<version>inpom.xml, the version strings in both READMEs, and regenerateNOTICEif needed. mvn testlocally (all tests green) andmvn checkstyle:check, then push.- Tag and push:
git tag vX.Y.Z && git push origin vX.Y.Z. This runsrelease.yml, which buildszvec_c_apifor all six platforms, verifies the Linux glibc floor, the macOS deployment target, every leg's CPU architecture and the absence of GPL-only RocksDB code, and publishes a GitHub Release with the C library archives and their.sha256. - Dry-run the JAR pipeline:
gh workflow run publish-jar.yml --repo zvec-ai/zvec-java -f tag=vX.Y.Z -f deploy_central=false - Deploy for real with
-f deploy_central=true. This builds the whole artifact family insidemanylinux_2_28, signs it with GPG, and uploads one bundle to the Sonatype Central Portal. BecauseautoPublish=falseandwaitUntil=validated, the bundle stops at VALIDATED: nothing is public yet. - Review the deployment at https://central.sonatype.com/publishing/deployments and click Publish. This is irreversible — Maven Central never deletes, overwrites or reissues a version, so a mistake costs a version number.
What gets published: the main multi-platform JAR, the macosx-arm64,
macosx-x86_64, linux-x86_64, linux-arm64, windows-x86_64,
windows-arm64 and nolib classifier JARs, plus sources and javadoc.
Required repository secrets: CENTRAL_TOKEN_USER, CENTRAL_TOKEN_PASSWORD
(a Central Portal user token, not account credentials), GPG_SIGNING_KEY
(base64 of the armored private key) and GPG_SIGNING_PASSPHRASE. The
org.zvec namespace must be verified in the Portal, and the signing key must be
on a public keyserver — Central fetches it from keyserver.ubuntu.com,
keys.openpgp.org or pgp.mit.edu, so publishing to any one of them is enough.
The release key is 1E35478A9977D23F (fingerprint
7D0332FD912CE0CBD1043EE21E35478A9977D23F,
zvec-java maintainers <zvec@alibaba-inc.com>), published on
keyserver.ubuntu.com. Check that it is still there before a release:
curl -sS "https://keyserver.ubuntu.com/pks/lookup?op=index&options=mr&search=0x1E35478A9977D23F"A pub: line plus a uid: line means the key is published and Central can
verify signatures with it.
When rotating the key, push the new one over HKPS rather than the default
hkp://. Port 11371 is blocked on many corporate networks, where --send-keys
fails with Network is unreachable even though HTTPS to the same host works
fine — easy to misread as "the key never got published":
gpg --keyserver hkps://keyserver.ubuntu.com --send-keys <NEW_FINGERPRINT>Then refresh GPG_SIGNING_KEY and GPG_SIGNING_PASSPHRASE from the new
private key and update the fingerprint above.
testCompilefails withincompatible types: org.zvec.binding.X cannot be converted to X. An Eclipse-JDT-based IDE (or its language server) has written its own compiler output intotarget/classeswhile Maven was running, and those class files carryUnresolved compilation problems. Runmvn cleanand rebuild, and keep the IDE's builder offtarget/.UnsatisfiedLinkErrorat runtime. Work through the three tiers in the README FAQ;-Dzvec.native.path=/path/to/zvec/build/libis the quickest way to test against a local build.- Version reported as
v0.0.0-g<sha>. The natives were built without-DOVERRIDE_GIT_DESCRIBE; the release workflows set it from the tag.