JDBC looks the same for every database, but the JDK doesn’t include a driver for SQLite, so DriverManager.getConnection("jdbc:sqlite:contacts.db") fails with No suitable driver found until you add one. A single jar fixes that, with no server to install. In this article I’ll walk you through building a small contacts program in Java with SQLite, from the first connection to transactions.
TLDR
- Add the
org.xerial:sqlite-jdbcdriver (3.53.4.0 here). It bundles SQLite itself, so there’s nothing else to install. - Connect with
DriverManager.getConnection("jdbc:sqlite:contacts.db"). The file is created if it doesn’t exist. - Use
PreparedStatementwith?placeholders for every value you insert or filter on. - For a transaction, turn off auto-commit with
setAutoCommit(false). Then callcommit()orrollback(). No suitable driver found for jdbc:sqlite:…means the driver jar isn’t on the classpath.
What is SQLite JDBC and what will we build?
Java talks to databases through JDBC, and the sqlite-jdbc driver (by Xerial) is the JDBC driver for SQLite. Its README says it “requires no configuration since native libraries for major OSs, including Windows, macOS, Linux etc., are assembled into a single JAR” (sqlite-jdbc README). The SQLite engine runs inside your Java process, and the database is one file.
To show you the full cycle, I will build ContactsApp.java. It creates a contacts table and adds two people. Then it runs a transaction that fails halfway and prints what’s left.
Prerequisites
| Requirement | Version used here |
|---|---|
| JDK | Java 21 (eclipse-temurin:21-jdk), which runs a single source file with java ContactsApp.java |
| SQLite JDBC driver | org.xerial:sqlite-jdbc:3.53.4.0, the latest release on Maven Central on 7 October 2026 |
| Build tool (optional) | Maven or Gradle, or the plain jar as below |
Building a contacts program with Java and SQLite
Everything fits in one Java file plus the driver jar.
Step 1: Add the SQLite JDBC driver
With Maven, add the dependency to pom.xml (not executed here; I used the jar directly):
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.53.4.0</version>
</dependency>
With Gradle (not executed here):
implementation 'org.xerial:sqlite-jdbc:3.53.4.0'
Without a build tool, download the jar from Maven Central. That’s what I did for this test:
$ mkdir lib && curl -sSL -o lib/sqlite-jdbc-3.53.4.0.jar https://repo1.maven.org/maven2/org/xerial/sqlite-jdbc/3.53.4.0/sqlite-jdbc-3.53.4.0.jar && ls lib
sqlite-jdbc-3.53.4.0.jar
Step 2: Write the program
Save this as ContactsApp.java. Each block does one job, and the comments say which:
import java.sql.*;
public class ContactsApp {
public static void main(String[] args) throws SQLException {
// Creates contacts.db in the current folder if it doesn't exist.
try (Connection conn = DriverManager.getConnection("jdbc:sqlite:contacts.db")) {
System.out.println("SQLite version: " + conn.getMetaData().getDatabaseProductVersion());
try (Statement st = conn.createStatement()) {
st.execute("CREATE TABLE IF NOT EXISTS contacts ("
+ "id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE)");
}
// Insert with a PreparedStatement: values are bound, never pasted into the SQL.
String insert = "INSERT INTO contacts (name, email) VALUES (?, ?)";
try (PreparedStatement ps = conn.prepareStatement(insert, Statement.RETURN_GENERATED_KEYS)) {
for (String[] c : new String[][] {{"Ada", "ada@example.com"}, {"Linus", "linus@example.com"}}) {
ps.setString(1, c[0]);
ps.setString(2, c[1]);
ps.executeUpdate();
try (ResultSet keys = ps.getGeneratedKeys()) {
keys.next();
System.out.println("Inserted " + c[0] + " with id " + keys.getLong(1));
}
}
}
// Update and delete return the number of rows changed.
try (PreparedStatement ps = conn.prepareStatement("UPDATE contacts SET email = ? WHERE name = ?")) {
ps.setString(1, "ada@lovelace.dev");
ps.setString(2, "Ada");
System.out.println("Updated rows: " + ps.executeUpdate());
}
// A transaction: both inserts or neither.
conn.setAutoCommit(false);
try (PreparedStatement ps = conn.prepareStatement(insert)) {
ps.setString(1, "Grace");
ps.setString(2, "grace@example.com");
ps.executeUpdate();
ps.setString(1, "Grace again");
ps.setString(2, "grace@example.com"); // violates UNIQUE
ps.executeUpdate();
conn.commit();
} catch (SQLException e) {
conn.rollback();
System.out.println("Rolled back: " + e.getMessage());
} finally {
conn.setAutoCommit(true);
}
try (Statement st = conn.createStatement();
ResultSet rs = st.executeQuery("SELECT id, name, email FROM contacts ORDER BY id")) {
while (rs.next()) {
System.out.println(rs.getInt("id") + " | " + rs.getString("name") + " | " + rs.getString("email"));
}
}
}
}
}
What to notice in the code:
- The connection string
jdbc:sqlite:contacts.dbopens the file, or creates it, relative to the folder you run from. Use a full path to put it elsewhere. - try-with-resources closes every JDBC object for you.
RETURN_GENERATED_KEYSgives you the new row’sidstraight after the insert.- The transaction inserts Grace twice. The second insert breaks the
UNIQUErule onemail, sorollback()undoes the first insert too.
Running it
Run the file with the driver on the classpath:
$ java -cp lib/sqlite-jdbc-3.53.4.0.jar ContactsApp.java
SQLite version: 3.53.4
Inserted Ada with id 1
Inserted Linus with id 2
Updated rows: 1
Rolled back: [SQLITE_CONSTRAINT_UNIQUE] A UNIQUE constraint failed (UNIQUE constraint failed: contacts.email)
1 | Ada | ada@lovelace.dev
2 | Linus | linus@example.com
Grace isn’t in the final list, because the rollback removed her first insert along with the failed one. The program created contacts.db in the working folder:
$ ls contacts.db
contacts.db
Common problems
SQLITE_CONSTRAINT_UNIQUEon a second run: the program inserts the same emails every time, so running it again on the same file fails on the first insert:
$ java -cp lib/sqlite-jdbc-3.53.4.0.jar ContactsApp.java 2>&1 | grep -m1 SQLITE_CONSTRAINT
Exception in thread "main" org.sqlite.SQLiteException: [SQLITE_CONSTRAINT_UNIQUE] A UNIQUE constraint failed (UNIQUE constraint failed: contacts.email)
Delete contacts.db between runs, or use INSERT OR IGNORE. See SQLite UNIQUE constraint.
No suitable driver found for jdbc:sqlite:contacts.db: the driver jar isn’t on the classpath. Here’s the same program run without it:
$ java ContactsApp.java 2>&1 | head -1
Exception in thread "main" java.sql.SQLException: No suitable driver found for jdbc:sqlite:contacts.db
Add the dependency or the -cp flag. If you build a fat jar with the Maven Shade plugin, the README notes you may need a shade transformer to fix this error.
database is locked: another connection is writing. Keep one connection per thread and close it when you’re done. See database is locked.
Conclusion
SQLite in Java needs one dependency and a jdbc:sqlite: connection string. Use PreparedStatement for values and try-with-resources for cleanup. Turn off auto-commit when several writes must succeed together.
Further reading
- SQLite UPDATE query
- SQLite UNIQUE constraint
- SQLite primary keys
- SQLite create database
- Driver docs: sqlite-jdbc on GitHub
Tested with sqlite-jdbc 3.53.4.0 (SQLite 3.53.4) on OpenJDK 21.0.12 on 7 October 2026.
