JDBC driver (Java)¶
DecentDB ships an in-tree Java stack under bindings/java/:
bindings/java/driver/— JDBC driver (com.decentdb.jdbc.DecentDBDriver)bindings/java/native/— JNI bridge (libdecentdb_jni.*)bindings/java/dbeaver-extension/— DBeaver packagingtests/bindings/java/Smoke.java— low-level FFM smoke for the raw C ABI
The JDBC driver targets the stable DecentDB C ABI through JNI. Public behavior should match the engine and C ABI, rather than inventing Java-only semantics.
Supported JDBC URLs¶
The driver accepts URLs of the form:
jdbc:decentdb:/absolute/path/to/db.ddb
jdbc:decentdb:/absolute/path/to/db.ddb?mode=open
jdbc:decentdb:/absolute/path/to/db.ddb?readOnly=true
jdbc:decentdb:/absolute/path/to/db.ddb?write_queue_enabled=true&write_queue_capacity=128
Supported connection properties:
mode—openOrCreate(default),open, orcreatereadOnly—trueorfalse(defaultfalse)write_queue_enabled,write_queue_capacity,write_queue_default_timeout_ms,write_queue_strict_group_commit,write_queue_max_batch, andwrite_queue_max_group_delay_usare passed through to the native open options.
The JNI bridge maps the stable write-queue status codes distinctly: DDB_ERR_BUSY, DDB_ERR_TIMEOUT, DDB_ERR_CANCELED, DDB_ERR_QUEUE_FULL, and DDB_ERR_QUEUE_CLOSED. The raw Java FFM smoke test also validates the new queued C ABI entry points. JDBC prepared statements stay on the direct prepared path until the C ABI adds queued prepared-statement execution.
Build locally¶
From the repository root:
This builds the core shared library, the JNI bridge, and the JDBC jar. The jar embeds the matching native libraries for the current OS/arch from target/release/.
Validate the Java binding¶
The Java build itself targets Java 17 bytecode. In this repository, the Gradle test path is currently validated with JDK 21:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk
export PATH="$JAVA_HOME/bin:$PATH"
cd bindings/java
./gradlew :driver:test
Run the standalone JDBC example¶
The driver now includes a runnable CRUD example that covers schema creation, prepared statements, DECIMAL, BOOL, TIMESTAMP, rollback, metadata, and basic error handling:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk
export PATH="$JAVA_HOME/bin:$PATH"
cd bindings/java
./gradlew :driver:runCrudExample
To target a specific database path:
Run the benchmark¶
From bindings/java/:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk
export PATH="$JAVA_HOME/bin:$PATH"
./gradlew :driver:benchmarkFetch -PbenchmarkArgs="--count 100000 --point-reads 5000 --fetchmany-batch 1024 --db-prefix java_bench_fetch"
Supported benchmark options:
--engine <all|decentdb|sqlite>--count <n>--point-reads <n>--fetchmany-batch <n>--point-seed <n>--db-prefix <prefix>--sqlite-jdbc <jar_path>--keep-db
When --engine all is used, the benchmark will try to auto-discover a local sqlite-jdbc jar from common DBeaver, Rider, and Maven cache locations. You can also pass it explicitly with --sqlite-jdbc.
Run the low-level FFM smoke¶
tests/bindings/java/Smoke.java validates the raw C ABI independently of the JDBC driver. With JDK 21, this path uses the preview Foreign Function & Memory API:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk
export PATH="$JAVA_HOME/bin:$PATH"
cargo build -p decentdb
javac --enable-preview --release 21 tests/bindings/java/Smoke.java
java --enable-preview --enable-native-access=ALL-UNNAMED -cp tests/bindings/java Smoke
The smoke test auto-detects target/debug/ or target/release/ for the core shared library.
JDBC usage example¶
import com.decentdb.jdbc.DecentDBConnection;
import com.decentdb.jdbc.DecentDBDataSource;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Timestamp;
import java.time.Instant;
DecentDBDataSource dataSource = new DecentDBDataSource("jdbc:decentdb:/tmp/shop.ddb");
dataSource.setMode("openOrCreate");
try (Connection connection = dataSource.getConnection()) {
connection.setAutoCommit(false);
try (PreparedStatement create = connection.prepareStatement(
"CREATE TABLE IF NOT EXISTS products (" +
"id INT64 PRIMARY KEY, " +
"name TEXT NOT NULL, " +
"price DECIMAL(12,2) NOT NULL, " +
"active BOOL NOT NULL, " +
"updated_at TIMESTAMP)"
)) {
create.executeUpdate();
}
try (PreparedStatement insert = connection.prepareStatement(
"INSERT INTO products (id, name, price, active, updated_at) VALUES ($1, $2, $3, $4, $5)"
)) {
insert.setLong(1, 1L);
insert.setString(2, "Keyboard");
insert.setBigDecimal(3, new BigDecimal("129.99"));
insert.setBoolean(4, true);
insert.setTimestamp(5, Timestamp.from(Instant.now()));
insert.executeUpdate();
}
connection.commit();
try (PreparedStatement query = connection.prepareStatement(
"SELECT id, name, price FROM products WHERE id = $1"
)) {
query.setLong(1, 1L);
try (ResultSet rs = query.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getString("name") + " => " + rs.getBigDecimal("price"));
}
}
}
if (connection instanceof DecentDBConnection decent) {
System.out.println("Engine version: " + decent.getEngineVersion());
decent.checkpoint();
}
}
DecentDB's Java driver currently follows the engine's native positional placeholder style ($1, $2, ...) in prepared SQL.
Type Mapping¶
The JDBC driver maps native DecentDB values to standard JDBC types where the JDK has a natural representation:
| DecentDB type | JDBC metadata | ResultSet.getObject() |
|---|---|---|
INT64 | Types.BIGINT | Long |
FLOAT64 | Types.DOUBLE | Double |
BOOL | Types.BOOLEAN | Boolean |
TEXT | Types.VARCHAR | String |
BLOB | Types.BINARY | byte[] |
DECIMAL | Types.DECIMAL | BigDecimal |
UUID | Types.BINARY | UUID when bytes are UUID-shaped, otherwise byte[] |
TIMESTAMP | Types.TIMESTAMP | Timestamp |
DATE | Types.DATE | java.sql.Date |
TIME | Types.TIME | java.sql.Time |
TIMESTAMPTZ | Types.TIMESTAMP | Timestamp normalized to UTC |
ENUM | Types.VARCHAR | String |
IPADDR / INET | Types.VARCHAR | canonical String |
CIDR | Types.VARCHAR | canonical String |
INTERVAL | Types.VARCHAR | "months days micros" String |
MACADDR / MACADDR8 | Types.OTHER | canonical lowercase String |
String parameters can be used for semantic columns when SQL provides the target column context, including inline enum labels and network address literals.
DecentDB-specific connection helpers¶
DecentDBConnection adds a small number of engine-truth helpers beyond the standard JDBC Connection surface:
isInTransaction()— queries the engine viaddb_db_in_transactiongetAbiVersion()— exposesddb_abi_versiongetEngineVersion()— exposesddb_versioncheckpoint()— runsddb_db_checkpointsaveAs(path)— runsddb_db_save_as
saveAs(path) requires a destination path that does not already exist.
Thread-safety and pooling¶
DecentDB uses a single-process, one-writer / many-readers model.
Connectionmethods are serialized internally per connectionStatement,PreparedStatement, andResultSetare not thread-safeDecentDBDataSourceis a thin configuration wrapper, not a pooling implementation
If you place DecentDB behind an external pool, keep write-heavy workloads at a maximum size of 1.
Metadata and framework support¶
The driver includes:
- a
DataSourceimplementation:com.decentdb.jdbc.DecentDBDataSource - JDBC metadata for tables, columns, indexes, keys, and type mapping
- DecentDB-specific schema helpers on
DecentDBDatabaseMetaDatafor table DDL and trigger listing - stable tooling metadata helpers on
DecentDBDatabaseMetaData:getToolingMetadataJson()anddescribeQueryJson(sql)
For DBeaver integration details, see the DBeaver guide.