Java SDK for the antd daemon — the gateway to the Autonomi decentralized network.
Targets Java 17+ enterprise/ERP environments. Supports both REST (java.net.http.HttpClient with an internal JSON parser, zero external deps) and gRPC (io.grpc) transports. Immutable data types only (Java records).
dependencies {
implementation("com.autonomi:antd-java:0.1.0")
}dependencies {
implementation 'com.autonomi:antd-java:0.1.0'
}<dependency>
<groupId>com.autonomi</groupId>
<artifactId>antd-java</artifactId>
<version>0.1.0</version>
</dependency>import com.autonomi.antd.AntdClient;
import com.autonomi.antd.models.*;
public class QuickStart {
public static void main(String[] args) {
try (var client = new AntdClient()) {
// Check daemon health
HealthStatus health = client.health();
System.out.println("OK: " + health.ok() + ", Network: " + health.network());
// Store data
DataPutPublicResult result = client.dataPutPublic("Hello, Autonomi!".getBytes());
System.out.printf("Stored at %s (chunks: %d)%n", result.address(), result.chunksStored());
// Retrieve data
byte[] data = client.dataGetPublic(result.address());
System.out.println("Retrieved: " + new String(data));
}
}
}The antd daemon must be running. Start it with:
ant dev start// Default: http://localhost:8082, 5 minute timeout
var client = new AntdClient();
// Custom URL
var client = new AntdClient("http://custom-host:9090");
// Custom URL and timeout
var client = new AntdClient("http://localhost:8082", Duration.ofSeconds(30));
// Custom HTTP client
var httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
var client = new AntdClient("http://localhost:8082", Duration.ofSeconds(30), httpClient);All methods throw AntdException (or a typed subclass) on failure.
| Method | Description |
|---|---|
health() |
Check daemon status — returns HealthStatus carrying antd version, EVM network, uptime, build commit, and payment contract addresses (antd ≥ 0.4.0) |
| Method | Description |
|---|---|
dataPutPublic(data, paymentMode) |
Store public data — returns DataPutPublicResult (DataMap stored on-network) |
dataGetPublic(address) |
Retrieve public data by address |
dataPut(data, paymentMode) |
Store encrypted private data — returns DataPutResult (DataMap returned to caller) |
dataGet(dataMap) |
Retrieve private data using a caller-held DataMap |
dataCost(data, paymentMode) |
Estimate storage cost — returns UploadCostEstimate with size, chunks, gas, payment mode |
| Method | Description |
|---|---|
chunkPut(data) |
Store a raw chunk |
chunkGet(address) |
Retrieve a chunk |
| Method | Description |
|---|---|
filePut(path, paymentMode) |
Upload a file privately — returns FilePutResult (DataMap returned to caller) |
fileGet(dataMap, destPath) |
Download a private file using a caller-held DataMap |
filePutPublic(path, paymentMode) |
Upload a file publicly — returns FilePutPublicResult (DataMap stored on-network) |
fileGetPublic(address, destPath) |
Download a public file by address |
fileCost(path, isPublic, paymentMode) |
Estimate upload cost — returns UploadCostEstimate with size, chunks, gas, payment mode |
The AsyncAntdClient provides non-blocking variants of every method, returning CompletableFuture<T>. It uses HttpClient.sendAsync() internally — no thread-pool wrappers around blocking calls.
import com.autonomi.antd.AsyncAntdClient;
import com.autonomi.antd.models.*;
try (var client = new AsyncAntdClient()) {
// Fire-and-forget style
client.healthAsync()
.thenAccept(h -> System.out.println("Network: " + h.network()));
// Chain operations
client.dataPutPublicAsync("Hello, async!".getBytes())
.thenCompose(result -> client.dataGetPublicAsync(result.address()))
.thenAccept(data -> System.out.println("Got: " + new String(data)))
.join(); // block only at the end
// Parallel uploads
CompletableFuture<DataPutPublicResult> upload1 = client.dataPutPublicAsync("file1".getBytes());
CompletableFuture<DataPutPublicResult> upload2 = client.dataPutPublicAsync("file2".getBytes());
CompletableFuture.allOf(upload1, upload2).join();
System.out.printf("Addresses: %s, %s%n", upload1.join().address(), upload2.join().address());
// Error handling
client.dataGetPublicAsync("bad-address")
.exceptionally(ex -> {
System.out.println("Failed: " + ex.getCause().getMessage());
return null;
})
.join();
}The async client has the same constructors as AntdClient:
var client = new AsyncAntdClient(); // defaults
var client = new AsyncAntdClient("http://custom:9090"); // custom URL
var client = new AsyncAntdClient("http://localhost:8082", Duration.ofSeconds(30)); // custom timeoutAll methods follow the naming convention methodNameAsync() and return CompletableFuture<T> where T matches the sync return type. Void methods return CompletableFuture<Void>.
The GrpcAntdClient provides an alternative transport using gRPC instead of REST. It implements the same 15 methods with identical signatures, so switching transports requires only changing the constructor.
import com.autonomi.antd.GrpcAntdClient;
import com.autonomi.antd.models.*;
// Default: localhost:50051, plaintext
try (var client = new GrpcAntdClient()) {
HealthStatus health = client.health();
System.out.println("OK: " + health.ok() + ", Network: " + health.network());
// Same API as AntdClient
PutResult result = client.dataPutPublic("Hello via gRPC!".getBytes());
byte[] data = client.dataGetPublic(result.address());
System.out.println("Retrieved: " + new String(data));
}
// Custom target
try (var client = new GrpcAntdClient("myhost:50051")) {
client.health();
}The gRPC client uses io.grpc blocking stubs and maps gRPC status codes to the same AntdException hierarchy.
Note: Wallet operations (address, balance, approve) and payment_mode are available via REST only.
| gRPC Status | Exception Type |
|---|---|
INVALID_ARGUMENT |
BadRequestException |
NOT_FOUND |
NotFoundException |
ALREADY_EXISTS |
AlreadyExistsException |
FAILED_PRECONDITION |
PaymentException |
RESOURCE_EXHAUSTED |
TooLargeException |
INTERNAL |
InternalException |
UNAVAILABLE |
NetworkException |
ABORTED whose message starts with Partial upload: |
PartialUploadException (counts, retryable and retentionKnown parsed from the status message); any other ABORTED maps to the generic AntdException |
The build uses the protobuf Gradle plugin to compile .proto files from ../antd/proto and generate Java/gRPC stubs automatically:
./gradlew generateProto # generate stubs (also runs as part of build)
./gradlew build # full build including proto compilationThe gRPC transport adds the following dependencies (managed in build.gradle.kts):
io.grpc:grpc-netty-shaded— Netty-based gRPC transport (shaded to avoid conflicts)io.grpc:grpc-protobuf— Protobuf marshalling for gRPCio.grpc:grpc-stub— Stub classes for gRPCcom.google.protobuf:protobuf-java— Protocol Buffers runtime
All errors are subtypes of AntdException, which extends RuntimeException. Use standard Java exception handling:
try {
byte[] data = client.dataGetPublic(address);
} catch (NotFoundException e) {
System.out.println("Data not found on network");
} catch (PaymentException e) {
System.out.println("Insufficient funds");
} catch (AntdException e) {
System.out.println("Error " + e.getStatusCode() + ": " + e.getMessage());
}| Exception Type | HTTP Status | When |
|---|---|---|
BadRequestException |
400 | Invalid parameters |
PaymentException |
402 | Insufficient funds |
NotFoundException |
404 | Resource not found |
AlreadyExistsException |
409 | Resource exists |
ForkException |
409 | Version conflict |
TooLargeException |
413 | Payload too large |
InternalException |
500 | Server error |
NetworkException |
502 | Network unreachable |
PartialUploadException |
502 (code: PARTIAL_UPLOAD) |
Finalize stored some chunks but not all — see below |
A finalizeUpload / finalizeMerkleUpload / finalizeChunkUpload where some chunks stayed unstored after the daemon's retries throws PartialUploadException (a subclass of NetworkException, so existing catch (NetworkException e) blocks keep working) with getChunksStored() / getChunksFailed() / getTotalChunks(), an isRetryable() flag and an isRetentionKnown() flag. The on-chain payment persists and the stored chunks stay on the network. There are three cases:
isRetryable()(antd ≥ 0.14.0): the daemon kept the paid attempt under the sameupload_id. Call the same finalize method again with the sameupload_idand the same payment artefacts (tx hashes, or the winner pool hash) to store the remainder against the same payment: no re-prepare, no second signature, no double payment. Bound that loop: cap the attempts, and treat agetChunksFailed()that stops shrinking as stuck.isRetentionKnown() && !isRetryable(): the daemon confirmed nothing was retained (for example a merkle finalize with deliberately unpaid batches). Re-prepare the same content; already-stored chunks are skipped.!isRetentionKnown(): retention is unknown. The daemon may still hold the paid attempt, because it records the resume handle before it returns the error. Stop automatic recovery, keep theupload_idand the original payment artefacts, and reconcile before re-preparing or paying again. Never pay again on this signal alone: re-preparing skips chunks that are already stored, not chunks that were paid for and are still unstored. Daemons older than 0.14.0 never sendretryable, so their REST partial uploads read as unknown.
isRetryable() always implies isRetentionKnown().
try {
result = client.finalizeUpload(uploadId, txHashes);
} catch (PartialUploadException e) {
if (e.isRetryable()) {
// same upload_id, same payment: retry finalizeUpload(uploadId, txHashes) with a cap
} else if (e.isRetentionKnown()) {
// the daemon confirmed nothing was retained: re-prepare the same content
} else {
// retention unknown: stop, keep uploadId + txHashes, reconcile before paying again
}
}Over REST the counts and flags come from the structured error body: isRetentionKnown() is true only when the body carries retryable as a JSON boolean, and isRetryable() is that boolean. Over gRPC (status ABORTED whose message starts with the daemon's fixed Partial upload: prefix) they are parsed from the status message (Partial upload: <stored>/<total> chunks stored, <failed> failed after retries: <reason> (<hint>)). isRetentionKnown() is true only when the message starts with that count layout, all three counts convert to a long, and the message ends with one of the daemon's two hints: (paid attempt retained...) sets isRetryable(), and (stored chunks persist; re-prepare the same content...) means the daemon confirmed nothing was retained (daemons older than 0.14.0 write only this one). A layout miss or an overflowing count reads 0 and leaves both flags false, even with the hint. Readable counts with a missing, truncated or unrecognised hint, or with text after it, keep the counts, but both flags stay false. In both cases retention is unknown, so stop and reconcile rather than re-prepare. The prefix match is anchored at the start of the message, as in antd-rust: an ABORTED that does not start with the prefix, including one that merely quotes it further in, is not a partial upload and maps to the generic AntdException. On the REST side a malformed error body never escapes as a parse error: a count that is not a JSON number reads as zero, a retryable that is not a JSON boolean reads as not retryable with retention unknown, and a code that is not the string PARTIAL_UPLOAD keeps the plain NetworkException. See finalizeWithRetry in examples/.../Example07ExternalSigner.java for a bounded retry helper, and docs/external-signer-flow.md §6 for the daemon-side contract.
See the examples/ directory:
Example01Connect— Health checkExample02PublicData— Public data storage and retrievalExample03Files— File upload and downloadExample05ErrorHandling— Typed exception handlingExample06PrivateData— Private (encrypted) data storage
./gradlew build./gradlew test