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.
<dependency>
<groupId>io.github.xxvw</groupId>
<artifactId>cloudflare-d1-java</artifactId>
<version>0.1.2</version>
</dependency>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
Set credentials with environment variables:
export CLOUDFLARE_ACCOUNT_ID="your-account-id"
export D1_DATABASE_ID="your-d1-database-id"
export CLOUDFLARE_API_TOKEN="your-api-token"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);
}You 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.
| Operation | Method | Default retry behavior |
|---|---|---|
| Query rows | query(...) |
Retries transient failures |
| Query first row | queryFirst(...) |
Retries transient failures |
| Execute writes | execute(...) |
Does not retry by default |
| Batch statements | batch(...) |
Does not retry by default |
| Typed row mapping | query(..., User.class) |
Same as query |
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.
Queries retry by default on 429, 500, 502, 503, and 504.
Default policy:
retryQuery = true
retryExecute = false
retryBatch = false
maxRetries = 2
baseDelay = 200ms
maxDelay = 2s
jitter = true
respectRetryAfter = true
Custom policy:
D1RetryPolicy retryPolicy = D1RetryPolicy.builder()
.retryQuery(true)
.retryExecute(false)
.retryBatch(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.
The async methods are preview APIs and are marked deprecated to avoid treating their signatures as stable:
CompletableFuture<D1Result> future = d1.queryAsync("SELECT 1");Failures complete the future exceptionally.
- 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 | Check |
|---|---|
D1AuthenticationException |
The API token is missing, expired, malformed, or not being passed to the client. |
D1AuthorizationException |
The token does not have permission for the account or database. |
D1QueryException |
SQL failed at D1. Inspect errors() and, when appropriate, sql(). |
D1RateLimitException |
Respect retryAfter() and reduce request rate. |
| Empty result rows | Confirm the SQL query, target database ID, and whether the statement returns rows. |
- Quick Start
- Typed Mapping
- Retry Policy
- Error Handling
- Custom Transport
- Release Readiness
- Implementation Requirements
- 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.
See CONTRIBUTING.md. Good first contributions include documentation improvements, additional tests around edge cases, and small examples that do not add runtime dependencies.