# 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.