A pure-Java JDBC driver, client library, and protocol codec for libSQL and sqld databases over the Hrana protocol.
- Standard JDBC Driver: Drop-in
java.sqlcompliance (jdbc:libsql:...) for applications, connection pools (HikariCP), and ORMs. - Pure Java: No JNI, C-bindings, or native dependencies required.
- Hrana 3 Protocol Support: High-performance binary and JSON protocol codecs for libSQL/sqld.
- Lightweight & Fast: Built for modern Java (LTS).
- Batteries Included: Multi-module architecture providing fine-grained dependencies.
| Module | Description |
|---|---|
libsql-hrana-codec |
Core codec implementation for the Hrana 3 protocol |
libsql-client |
High-level client API for libSQL queries and streaming |
libsql-jdbc |
Standard JDBC 4.3 driver (jdbc:libsql:...) |
libsql-itests |
Integration tests against sqld running in Testcontainers |
libsql-conformance |
Selected xerial/sqlite-jdbc tests running against libSQL |
libsql-perf |
Driver-side codec and JDBC benchmark used for the PR performance comment |
Artifacts are hosted on GitHub Packages. Add the repository and configure credentials as described in Publishing.
<repositories>
<repository>
<id>github</id>
<url>https://maven.pkg.github.com/magnusp/libsql-java</url>
</repository>
</repositories><dependency>
<groupId>io.github.magnusp.libsql</groupId>
<artifactId>libsql-jdbc</artifactId>
<version>0.2.0</version>
</dependency>Prerelease builds from main use 0.2.0-rc.<shortsha>. No -SNAPSHOT versions are published.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class Example {
public static void main(String[] args) throws Exception {
String url = "jdbc:libsql:http://localhost:8080";
try (Connection conn = DriverManager.getConnection(url);
PreparedStatement stmt = conn.prepareStatement("SELECT 1 AS col")) {
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
System.out.println("Result: " + rs.getInt("col"));
}
}
}
}
}- Java: Java LTS
- Tooling: Managed via mise and Maven Wrapper (
./mvnw) - Docker: For running integration tests against
sqld
# Compile and run unit tests
mise exec -- ./mvnw clean test
# Run full integration test suite (requires Docker)
mise exec -- ./mvnw clean verify
# Format code with Spotless (Google Java Format)
mise exec -- ./mvnw spotless:applyRun the Appendix B unit suite.
./mvnw -q -pl libsql-jdbc -Dtest='Jdbc4AppendixB*' -Dsurefire.failIfNoSpecifiedTests=false testRun the full libsql-jdbc unit suite.
./mvnw -q -pl libsql-jdbc testRun the Appendix B server suite. The install step puts the current driver in the local repository. Under the nono sandbox, the Docker config path is required.
./mvnw -q -DskipTests install -pl libsql-hrana-codec,libsql-client,libsql-jdbc
DOCKER_CONFIG="$PWD/libsql-conformance/src/test/docker-config" ./mvnw -pl libsql-itests -Dit.test=Jdbc4AppendixBTypeMappingIT -Dtest=NoSuchTest -Dsurefire.failIfNoSpecifiedTests=false verifyRun the xerial conformance suite.
./mvnw -pl libsql-conformance -am verifyRun the full gate.
DOCKER_CONFIG="$PWD/libsql-conformance/src/test/docker-config" ./mvnw clean verifyThe compliance profile is in docs/jdbc-appendix-b-compliance.md.
Liquibase's SQLite implementation claims the jdbc:sqlite: prefix and selects its
dialect from the database product name. The driver maps jdbc:sqlite:http://... to
sqld and reports the product name as exactly SQLite, so the two work together.
Liquibase still resolves a JDBC driver class from the URL before it opens a connection.
It maps jdbc:sqlite: to org.sqlite.JDBC, which is not on the classpath, so name
LibsqlDriver explicitly or pass a DataSource.
Use one of these configurations:
API with an explicit driver class
import io.github.magnusp.libsql.jdbc.LibsqlDriver;
import liquibase.database.Database;
import liquibase.database.DatabaseFactory;
import liquibase.resource.ClassLoaderResourceAccessor;
Database database = DatabaseFactory.getInstance().openDatabase(
"jdbc:sqlite:http://localhost:8080",
null, // username
null, // password
LibsqlDriver.class.getName(), // driver
null, // database class
null, // driver properties file
null, // property provider class
new ClassLoaderResourceAccessor());Liquibase Maven plugin or liquibase.properties
url=jdbc:sqlite:http://localhost:8080
driver=io.github.magnusp.libsql.jdbc.LibsqlDriverSpring Boot or a connection pool
Pass a DataSource and set the driver class explicitly:
spring.datasource.url=jdbc:sqlite:http://localhost:8080
spring.datasource.driver-class-name=io.github.magnusp.libsql.jdbc.LibsqlDriverUse the jdbc:sqlite: prefix so Liquibase's SQLite implementation claims the URL.
jdbc:libsql: also reaches the driver, but Liquibase cannot resolve a driver from it
and still needs the explicit driver class.
Flyway selects its database plugin from the URL before it opens a connection. Its
SQLite plugin claims jdbc:sqlite:, jdbc:sqldroid:, and the p6spy prefixes, so a
jdbc:libsql: URL is rejected with:
No Flyway database plugin found to handle jdbc:libsql:********
The jdbc:sqlite: alias is claimed, but its plugin resolves the driver to
org.sqlite.JDBC. Programmatic Flyway.configure().dataSource(url, ...) builds a
DriverDataSource eagerly with a null driver class, so a later driver(...) call is
ignored.
Use one of these configurations:
DataSource with either URL form (recommended)
jdbc:libsql: works when Flyway receives a DataSource, because the plugin lookup
has already happened by the time the connection is opened:
Flyway flyway = Flyway.configure()
.dataSource(dataSource) // any pool or DriverManager-backed DataSource
.locations("classpath:db/migration")
.load();
flyway.migrate();Spring Boot configuration, using the application DataSource that Flyway receives
automatically:
spring.datasource.url=jdbc:libsql:http://localhost:8080
spring.datasource.driver-class-name=io.github.magnusp.libsql.jdbc.LibsqlDriverURL configuration with an explicit driver
URL-based configuration must use the jdbc:sqlite: alias and name the driver
explicitly. This mirrors the Flyway Maven plugin and flyway.properties path:
ClassicConfiguration configuration = new ClassicConfiguration();
configuration.setUrl("jdbc:sqlite:http://localhost:8080");
configuration.setDriver("io.github.magnusp.libsql.jdbc.LibsqlDriver");
Flyway flyway = new Flyway(configuration);# flyway.properties
url=jdbc:sqlite:http://localhost:8080
driver=io.github.magnusp.libsql.jdbc.LibsqlDriverjdbc:libsql: cannot be used with URL-based configuration at all, even with an
explicit driver, because the plugin lookup fails first.
Auto-commit statements on one JDBC connection share a Hrana stream, so a
connection-init statement that sets session state such as PRAGMA foreign_keys
applies to the migrations that follow on that connection.
Flyway wraps clean() in a transaction, and SQLite ignores PRAGMA foreign_keys
inside a transaction. Run the pragma when the DataSource hands over the connection,
before Flyway begins that transaction:
# HikariCP / Spring Boot
spring.datasource.hikari.connection-init-sql=PRAGMA foreign_keys = OFFFor a programmatic DataSource, run the same statement in getConnection() before
returning the connection. Do not use an AFTER_CONNECT callback for this: Flyway has
already disabled auto-commit by then, so the driver starts a transaction before the
pragma and SQLite ignores it.
The driver splits Flyway's PRAGMA foreign_keys = OFF; DROP TABLE ...; PRAGMA foreign_keys = ON scripts and runs every statement on the connection's stream (see
#258). sqld expires an idle
stream after about ten seconds and session state resets with it, so configure state
that must outlive an idle period in the pool's connection initialization.
We welcome contributions! Please see CONTRIBUTING.md for contribution guidelines and development workflow, and CODE_OF_CONDUCT.md for community standards.
Please report security issues according to our Security Policy.
This project is licensed under the MIT License.