cloudflare-d1-java is an unofficial Java client for the Cloudflare D1 REST API.
It is a lightweight SDK for Java applications that need direct REST API access to D1. It is not a JDBC driver, ORM, SQL builder, migration tool, Spring Boot starter, or Cloudflare Workers binding client.
The D1 REST API is a good fit for server-side tools, administration, batch jobs, and migration-style workflows. For latency-sensitive application traffic, also consider a Cloudflare Worker proxy or the Workers D1 binding.
- Use typed Java APIs instead of hand-written HTTP requests and response parsing.
- Keep Java 8 compatibility for older tools and deployment environments.
- Run synchronous or asynchronous D1 REST API calls from server-side Java code.
- Map query rows to immutable maps or Jackson-backed model classes.
- Get built-in retry handling for transient read failures and rate limits.
- Avoid extra runtime infrastructure dependencies beyond
jackson-databind.
Use the Workers D1 binding for code running inside Cloudflare Workers. Use a Worker proxy when application traffic needs lower latency, custom authorization, or tighter control over public access.
<dependency>
<groupId>io.github.xxvw</groupId>
<artifactId>cloudflare-d1-java</artifactId>
<version>0.1.4</version>
</dependency>Gradle:
implementation "io.github.xxvw:cloudflare-d1-java:0.1.4"Requirements:
- Java 8 or newer.
- Maven, Gradle, or another build tool that can consume Maven Central artifacts.
- A Cloudflare account with a D1 database.
- A Cloudflare API token with Account, D1, Edit permission for write operations. Use the minimum permission that fits your application.
Runtime dependency:
jackson-databind
| Area | Support |
|---|---|
| Java | Java 8 or newer |
| Distribution | Maven Central |
| Runtime dependency | jackson-databind |
| API style | Synchronous D1Client and asynchronous D1AsyncClient |
| D1 operations | Query, query first row, execute, batch |
| Mapping | Map<String, Object> rows and Jackson-backed typed rows |
| Retries | Enabled for transient read failures by default |
| Non-goals | JDBC, ORM, SQL builder, migrations, Spring Boot starter |
Set credentials with environment variables. For local manual testing, start from the example file:
cp .env.example .env
$EDITOR .env
set -a
. ./.env
set +aThe .env file is ignored by Git. Never commit real Cloudflare account IDs, database IDs, or API
tokens.
Use the client:
import io.github.xxvw.cloudflare.d1.D1Client;
import io.github.xxvw.cloudflare.d1.D1Result;
try (D1Client d1 = D1Client.fromEnv()) {
D1Result result = d1.query("SELECT id, name FROM users WHERE id = ?", 1);
result.rows().forEach(System.out::println);
}For a one-minute local check, run the bundled example:
mvn -f examples/quickstart/pom.xml compile exec:javaYou can also configure values explicitly:
D1Client d1 = D1Client.builder()
.accountId(System.getenv("CLOUDFLARE_ACCOUNT_ID"))
.databaseId(System.getenv("D1_DATABASE_ID"))
.apiToken(System.getenv("CLOUDFLARE_API_TOKEN"))
.build();See Quick Start for a complete copy-paste friendly example.
The repository includes standalone Maven examples in examples/quickstart.
set -a
. ./.env
set +a
mvn -f examples/quickstart/pom.xml compile exec:javaExpected output for the default query:
SQL: SELECT 1 AS value
Rows: 1
{value=1}
Pass a SQL statement as exec.args to try another read query:
mvn -f examples/quickstart/pom.xml exec:java -Dexec.args="SELECT 42 AS answer"Run typed mapping and opt-in write examples:
mvn -f examples/quickstart/pom.xml compile exec:java \
-Dexec.mainClass=example.MappingAndWriteExamples
mvn -f examples/quickstart/pom.xml compile exec:java \
-Dexec.mainClass=example.MappingAndWriteExamples \
-Dexec.args="--write"| Operation | Method | Default retry behavior |
|---|---|---|
| Query rows | query(...) |
Retries transient failures |
| Query first row | queryFirst(...) |
Retries transient failures |
| Raw rows | raw(...) |
Retries transient failures |
| Execute writes | execute(...) |
Does not retry by default |
| Batch statements | batch(...) |
Does not retry by default |
| Raw batch statements | rawBatch(...) |
Does not retry by default |
| Typed row mapping | query(..., User.class) |
Same as query |
| Async operations | D1AsyncClient |
Same as the underlying operation |
Parameters may be String, Number, Boolean, or null. Convert dates, JSON values, and custom objects to strings before passing them as SQL parameters.
D1Result result = d1.query(
"SELECT id, name, email FROM users WHERE active = ?",
true
);
List<Map<String, Object>> rows = result.rows();
Optional<Map<String, Object>> first = result.firstRow();public class User {
public long id;
public String name;
public String email;
}
List<User> users = d1.query(
"SELECT id, name, email FROM users WHERE active = ?",
User.class,
true
);Typed mapping uses Jackson. Unknown row properties are ignored by the default mapping ObjectMapper. A mapping failure in any row throws D1MappingException.
See Typed Mapping for custom mapper guidance.
Optional<Map<String, Object>> row = d1.queryFirst(
"SELECT id, name FROM users WHERE id = ?",
1
);
Optional<User> user = d1.queryFirst(
"SELECT id, name, email FROM users WHERE id = ?",
User.class,
1
);queryFirst returns Optional.empty() for zero rows and the first row when one or more rows are returned.
D1Result result = d1.execute(
"INSERT INTO users(name, email) VALUES (?, ?)",
"Taro",
"taro@example.com"
);
long changes = result.meta().changes();
OptionalLong lastRowId = result.meta().lastRowId();execute does not retry by default because write operations may not be idempotent.
List<D1Result> results = d1.batch(
D1Query.of("INSERT INTO users(name) VALUES (?)", "Taro"),
D1Query.of("SELECT id, name FROM users")
);Use batch(...) for multiple operations instead of relying on semicolon-separated SQL statements. Empty batches are rejected.
Use raw(...) when you want rows returned as arrays with a separate column list:
D1RawResult result = d1.raw(
"SELECT id, name FROM users WHERE active = ?",
true
);
List<String> columns = result.columns();
List<List<Object>> rows = result.rows();See Raw Query API for raw batch and async examples.
Queries retry by default on 429, 500, 502, 503, and 504.
Default policy:
retryQuery = true
retryExecute = false
retryBatch = false
retryRaw = true
retryRawBatch = false
maxRetries = 2
baseDelay = 200ms
maxDelay = 2s
jitter = true
respectRetryAfter = true
Custom policy:
D1RetryPolicy retryPolicy = D1RetryPolicy.builder()
.retryQuery(true)
.retryExecute(false)
.retryBatch(false)
.retryRaw(true)
.retryRawBatch(false)
.maxRetries(3)
.baseDelay(Duration.ofMillis(300))
.maxDelay(Duration.ofSeconds(5))
.jitter(true)
.respectRetryAfter(true)
.build();Disable retries:
D1RetryPolicy retryPolicy = D1RetryPolicy.none();See Retry Policy for retry tradeoffs.
try {
d1.query("SELECT * FROM missing_table");
} catch (D1QueryException e) {
e.sql().ifPresent(sql -> System.err.println("Query failed"));
} catch (D1RateLimitException e) {
e.retryAfter().ifPresent(delay -> System.err.println("Retry after: " + delay));
} catch (D1AuthenticationException | D1AuthorizationException e) {
System.err.println("Check API token permissions");
} catch (D1ApiException e) {
System.err.println("D1 API error: " + e.statusCode());
} catch (D1TransportException | D1TimeoutException e) {
System.err.println("Network failure");
}Exception messages do not include API tokens or SQL parameter values. D1QueryException.sql() may expose SQL text for diagnostics.
See Error Handling for the exception hierarchy.
The default transport uses the Java standard library and does not add runtime dependencies. You may provide a custom transport:
import java.util.Collections;
D1Client client = D1Client.builder()
.accountId(System.getenv("CLOUDFLARE_ACCOUNT_ID"))
.databaseId(System.getenv("D1_DATABASE_ID"))
.apiToken(System.getenv("CLOUDFLARE_API_TOKEN"))
.transport(request -> {
return new D1TransportResponse(200, Collections.emptyMap(), "{\"success\":true,\"result\":[]}");
})
.build();See Custom Transport for implementation notes.
Use D1AsyncClient for supported asynchronous operations:
try (D1AsyncClient d1 = D1AsyncClient.fromEnv()) {
CompletableFuture<D1Result> future = d1.queryAsync("SELECT 1 AS value");
D1Result result = future.join();
System.out.println(result.firstRow());
}Async operations run on a Java Executor and complete futures exceptionally with the same public exception types as synchronous operations.
Run the read-only async examples:
mvn -f examples/quickstart/pom.xml compile exec:java \
-Dexec.mainClass=example.AsyncExamplesRun the opt-in write example:
mvn -f examples/quickstart/pom.xml compile exec:java \
-Dexec.mainClass=example.AsyncExamples \
-Dexec.args="--write"See Async API for executor configuration and failure handling.
- Account ID: Cloudflare dashboard account details, or Wrangler output.
- Database ID: D1 database details in the Cloudflare dashboard, or
wrangler d1 list. - API token: Cloudflare dashboard API Tokens page. Store it outside source control.
The default REST API base URL is:
https://api.cloudflare.com/client/v4
| Symptom | First check |
|---|---|
D1AuthenticationException |
Confirm CLOUDFLARE_API_TOKEN is present, current, and passed to the client. |
D1AuthorizationException |
Confirm token permissions, account ID, and database ID. |
D1QueryException |
Confirm the SQL works in D1 and inspect errors(). |
D1RateLimitException |
Respect retryAfter() and reduce request rate. |
D1MappingException |
Confirm selected column names match the target Java model. |
D1TimeoutException |
Check network connectivity and request timeout settings. |
See Troubleshooting for common setup, API, and mapping failures.
- Quick Start
- Async API
- Typed Mapping
- Raw Query API
- Retry Policy
- Error Handling
- Custom Transport
- Compatibility and Support
- Production Usage Notes
- Troubleshooting
- Release Readiness
- Implementation Requirements
- Cloudflare D1 REST API import tutorial: https://developers.cloudflare.com/d1/tutorials/import-to-d1-with-rest-api/
- Cloudflare Worker proxy tutorial: https://developers.cloudflare.com/d1/tutorials/build-an-api-to-access-d1/
Published versions are available from Maven Central. GitHub Releases also attach versioned jars, signatures, SHA-256 checksums, and an offline Javadocs zip for manual download:
- Maven Central: https://central.sonatype.com/artifact/io.github.xxvw/cloudflare-d1-java
- GitHub Releases: https://github.com/Stella-Co-Ltd-Development/cloudflare-d1-java/releases
- Web Javadocs: https://javadoc.io/doc/io.github.xxvw/cloudflare-d1-java
- Do not hard-code API tokens.
- Use environment variables or a secret management system.
- Do not log
Authorizationheaders. - Do not include API tokens, account IDs, database IDs, or production data in issues, test fixtures, logs, or screenshots.
- Use fake values such as
test-token,test-account-id, andtest-database-idin tests.
- No JDBC driver support.
- No JPA or ORM integration.
- No SQL builder.
- No migration framework.
- No Spring Boot starter.
- No Cloudflare Workers Binding API support.
- No logging framework integration.
The 0.x series is intended to stay small and practical while the public API is refined. Public classes in io.github.xxvw.cloudflare.d1 are the supported API surface. Internal classes under io.github.xxvw.cloudflare.d1.internal may change without notice.
See CONTRIBUTING.md. Good first contributions include documentation improvements, additional tests around edge cases, and small examples that do not add runtime dependencies.