# snapdir for Java The Java binding wraps the same Rust core (`snapdir-api` → `snapdir-core`) as every other binding and the CLI, so the manifests and snapshot IDs it produces are **bit-identical**: a snapshot ID computed in Java is the exact 64-character lowercase hex string you get from `snapdir id`. It talks to the native `libsnapdir_ffi` C ABI through the JDK Foreign Function API. ## Install Add the Maven Central coordinate (Maven `org.snapdir:snapdir:1.11.0` or Gradle `implementation("org.snapdir:snapdir:1.11.0")`). The jar is **prebuilt**: it embeds the native `libsnapdir_ffi` library and extracts it at runtime, so no Rust toolchain or C compiler is needed. Because it uses the incubator Foreign Function module, compile and run on **JDK 17** with the module enabled: ```sh java --add-modules jdk.incubator.foreign --enable-native-access=ALL-UNNAMED ... ``` See [Install snapdir for Java](/install/java/) for the full setup. ## Usage The surface mirrors every other binding: synchronous `id` / `manifest`, plus async `push` / `pull` / `fetch` / `diff` that return `CompletableFuture`. The store URI is always an **argument** — `file://$PWD/store` for a local store, `gs://bucket/prefix` for Google Cloud, `s3://…` for S3, and so on. ```java import io.snapdir.DiffEntry; import io.snapdir.DiffStatus; import io.snapdir.Snapdir; import java.util.List; public class Example { public static void main(String[] args) throws Exception { String dir = "./demo"; String store = "file://" + System.getProperty("user.dir") + "/store"; // Synchronous: hash the directory and read its canonical manifest. String id = Snapdir.id(dir, null); // 64-char lowercase hex String manifest = Snapdir.manifest(dir, null); System.out.println(id); // Async: push returns the same id; get() blocks on the CompletableFuture. String pushed = Snapdir.push(dir, store, null).get(); // Materialise a snapshot into a fresh directory (verifies on fetch). Snapdir.pull(pushed, store, "./restored", null).get(); // fetch() populates the local cache without materialising a tree. Snapdir.fetch(pushed, store, null).get(); // diff() compares two STORE contents; statuses A / D / M / = . String storeB = "gs://my-bucket/snapshots"; List entries = Snapdir.diff(store, storeB, null).get(); for (DiffEntry e : entries) { System.out.printf("%s\t%s%n", e.status(), e.path()); } } } ``` To diff two **pinned** snapshots, follow the `store@id` convention — the last `@` splits the store URI from the 64-hex ID: ```java // "file://$PWD/store@" → { "file://$PWD/store", "" } static String[] parseRef(String ref) { int at = ref.lastIndexOf('@'); if (at == -1) return new String[]{ref, ""}; return new String[]{ref.substring(0, at), ref.substring(at + 1)}; } ``` ## Errors All fallible calls throw a checked `SnapdirException` (unwrapped from the `ExecutionException` that `CompletableFuture.get()` raises). Its code is drawn from the stable 8-code taxonomy, shared byte-for-byte with every other binding: ```java import io.snapdir.Snapdir; import io.snapdir.SnapdirException; try { Snapdir.pull(id, store, "./restored", null).get(); } catch (java.util.concurrent.ExecutionException wrapped) { if (wrapped.getCause() instanceof SnapdirException e) { switch (e.code()) { case IO_ERROR -> {} // filesystem read/write failure case HASH_MISMATCH -> {} // fetched object failed re-hash case STORE_ERROR -> {} // remote store / transport failure case IN_FLUX -> {} // source changed mid-snapshot case CATALOG_ERROR -> {} // catalog / index inconsistency case INVALID_ID -> {} // not a 64-hex snapshot id case INVALID_STORE -> {} // unparseable / unsupported store URI case CONFLICT -> {} // destination already exists } } } ``` ## Platform support Every binding is CI-verified against its live public registry before release. | Platform | Supported | | --- | --- | | Linux glibc (x64 + arm64) | yes | | Linux musl / Alpine (x64 + arm64) | no (planned 1.11.1) | | macOS (x64 + arm64) | yes | | Windows | no | The embedded native library is **glibc-linked**, so the Java jar is **glibc-only** today — it will not load on an Alpine or other musl JVM (`UnsatisfiedLinkError`). This is the one binding-wide exception: Node, Python, Go, C/C++, Zig, and Rust all run on Alpine musl today. Musl support for Java is planned for 1.11.1. Windows is unsupported, since snapdir is Unix-only.