snapdir for Java

The Java binding wraps the same Rust core (snapdir-apisnapdir-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:

java --add-modules jdk.incubator.foreign --enable-native-access=ALL-UNNAMED ...

See Install snapdir for 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 argumentfile://$PWD/store for a local store, gs://bucket/prefix for Google Cloud, s3://… for S3, and so on.

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<DiffEntry> 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:

// "file://$PWD/store@<snapshot-id>" → { "file://$PWD/store", "<snapshot-id>" }
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:

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.