SDKs

Java SDK

The Java SDK is the easiest way to query the Spice Cloud Platform from Java.

It uses Apache Arrow Flight to efficiently stream data to the client and Apache Arrow Records as data frames.

Supported Java Versions

The library targets Java 11 and above, and is tested against the following implementations:

  • Microsoft OpenJDK 11, 17, 21
  • Eclipse Temurin 21, 23, 24
  • Oracle JDK 17, 21, 23, 24, 25

Installation

<dependency>
    <groupId>ai.spice</groupId>
    <artifactId>spiceai</artifactId>
    <version>0.8.0</version>
    <scope>compile</scope>
</dependency>

Usage

1. Import the package.

import ai.spice.SpiceClient;

2. Create a SpiceClient by providing your API key. Get your free API key at spice.ai.

SpiceClient implements AutoCloseable, so use it in a try-with-resources block.

try (SpiceClient spice = SpiceClient.builder()
        .withApiKey(ApiKey)
        .withSpiceCloud()
        .build()) {
    // query here
}

The builder also accepts withFlightAddress(URI), withHttpAddress(URI), withUserAgent(String), withMaxRetries(int), and withArrowMemoryLimitMB(long). The API key must be in appId|key form.

For mutual TLS, connection pooling, and query deadlines, see Mutual TLS and Performance tuning.

3. Execute a query and get back a FlightStream.

FlightStream stream = spice.sql("SELECT * FROM tpch.lineitem LIMIT 10");

4. Iterate through the FlightStream to access the records.

while (stream.next()) {
    try (VectorSchemaRoot batches = stream.getRoot()) {
        System.out.println(batches.contentToTSVString());
    }
}

Check full example to learn more.

Parameterized queries

sqlWithParams(String sql, Object... params) binds positional $1, $2 placeholders and returns an ArrowReader, which the caller closes.

try (ArrowReader reader = spice.sqlWithParams(
        "SELECT * FROM tpch.lineitem WHERE l_quantity > $1 LIMIT 10", 10)) {
    while (reader.loadNextBatch()) {
        System.out.println(reader.getVectorSchemaRoot().contentToTSVString());
    }
}

Parameterized queries run on Apache Arrow Flight SQL prepared statements over the same connections as sql(), so they inherit the client's TLS, retry, and keep-alive settings.

Prepared statements are cached and reused, which removes the prepare round trips from every repeated query. The cache holds 64 statements by default; withPreparedStatementCacheSize(0) disables caching:

SpiceClient spice = SpiceClient.builder()
    .withPreparedStatementCacheSize(128)
    .build();

Failures surface as a FlightRuntimeException wrapped in an ExecutionException.

Asynchronous queries

query(String sql) and queryWithParams(String sql, Object... params) submit a query for asynchronous execution and return an AsyncQuery handle instead of streaming results. They require the runtime to be running in distributed (scheduler) mode.

AsyncQuery asyncQuery = spice.query("SELECT * FROM taxi_trips");

try (ArrowReader reader = asyncQuery.results()) { // blocks until the query completes
    while (reader.loadNextBatch()) {
        System.out.println(reader.getVectorSchemaRoot().contentToTSVString());
    }
}

The handle also exposes getQueryId(), status() for a single poll, waitForCompletion(Duration) to bound the wait, and cancel().

Usage with local Spice.ai OSS runtime

Follow the quickstart guide to install and run spice locally. The builder defaults to the local runtime:

try (SpiceClient spice = SpiceClient.builder()
        .build()) {
    // query here
}

Or using custom flight address:

try (SpiceClient spice = SpiceClient.builder()
        .withFlightAddress(new URI("grpc://my_remote_spice_instance:50051"))
        .build()) {
    // query here
}

Check Spice OSS documentation or Java SDK Sample to learn more

Default endpoints

TargetArrow FlightHTTP
Spice.ai Cloudhttps://flight.spiceai.io:443https://data.spiceai.io
Local runtimehttp://localhost:50051http://localhost:8090

These can also be set with the SPICE_FLIGHT_URL and SPICE_HTTP_URL environment variables.

Mutual TLS

Connect to a Spice deployment that requires mutual TLS, or one presenting a certificate signed by a private certificate authority. All three options take a path to a PEM file:

try (SpiceClient spice = SpiceClient.builder()
        .withFlightAddress(new URI("grpc+tls://spice.example-org.com:50051"))
        .withTlsClientCertFile("/etc/spice/client.crt")
        .withTlsClientKeyFile("/etc/spice/client.key")
        .withTlsRootCertFile("/etc/spice/ca.crt")
        .build()) {
    // query here
}
OptionPurpose
withTlsClientCertFileClient certificate presented to the server
withTlsClientKeyFilePrivate key for the client certificate
withTlsRootCertFileCertificate authority used to verify the server

The settings apply to Flight queries, parameterized queries, and HTTP operations. withTlsRootCertFile can be used on its own to trust a private CA without presenting a client certificate.

Health and runtime status

Three methods report runtime state. None throws when the runtime is unreachable, so they are safe to poll from a health probe.

boolean healthy = spice.isHealthy(); // runtime is responding
boolean ready = spice.isReady();     // datasets are loaded and queryable

for (ConnectionDetails details : spice.runtimeStatus()) {
    System.out.printf("%s (%s): %s%n",
        details.getName(), details.getEndpoint(), details.getStatus());
}

isHealthy() suits a liveness check, and isReady() a readiness check — a runtime can respond before its datasets finish loading.

runtimeStatus() returns per-connection detail and throws ExecutionException on failure. Each ConnectionDetails exposes getName(), getEndpoint(), getStatus(), getRawStatus(), and isReady(). getStatus() returns a ComponentStatus: INITIALIZING, READY, DISABLED, ERROR, REFRESHING, SHUTTING_DOWN, NOT_LOADED, or UNKNOWN.

Performance tuning

SpiceClient spice = SpiceClient.builder()
    .withChannelCount(4)
    .withQueryTimeout(Duration.ofSeconds(30))
    .withPreparedStatementCacheSize(128)
    .build();
OptionDefaultDescription
withChannelCount(int)1Size of the round-robin gRPC connection pool, for highly concurrent workloads
withQueryTimeout(Duration)noneDeadline for query planning, prepare, and bind calls; must be positive
withPreparedStatementCacheSize(int)64Prepared statements retained for reuse; 0 disables caching

withQueryTimeout bounds the planning and binding calls, not the streaming of results — a long-running result stream is not cut short by it.

Connection pooling with HikariCP

Spice can also be reached over JDBC using the Apache Arrow Flight SQL JDBC driver, which allows pooling connections with HikariCP. See the spice-java README for a worked example.

Refreshing a dataset

refreshDataset(String dataset) triggers a refresh of an accelerated dataset, optionally taking a RefreshOptions.

Connection retry

The SpiceClient implements connection retry mechanism (3 attempts by default). The number of attempts can be configured with withMaxRetries:

SpiceClient client = SpiceClient.builder()
    .withMaxRetries(5) // Setting to 0 will disable retries
    .build();

Retries are performed for connection and system internal errors. It is the SDK user's responsibility to properly handle other errors, for example RESOURCE_EXHAUSTED (HTTP 429).

Attempts use exponential backoff with random jitter, capped at 10 seconds per wait. The jitter keeps a fleet of clients from retrying in lockstep, and the backoff lets the default three attempts span a load-balancer failover or a runtime restart.

Re-authentication

An expired handshake token is detected and renewed automatically: the client re-handshakes once and retries the request, so long-lived SpiceClient instances keep working without calling reset().

reset() retires existing connections gracefully — in-flight queries and open result streams run to completion while new queries use fresh connections.

Contributing

Contribute to or file an issue with the spice-java library at: https://github.com/spiceai/spice-java