JDBC in Depth: Connecting Java to Databases
JDBC (Java Database Connectivity) is the standard Java API for connecting to and operating on relational databases like PostgreSQL, MySQL, or Oracle. It gives you a uniform set of interfaces — Connection, Statement, PreparedStatement, ResultSet — so the same Java code works across databases by swapping a driver. Even when you later use Hibernate or Spring Data, those tools run on top of JDBC, so understanding it directly makes you a far stronger backend developer.
The core flow
Every JDBC interaction follows the same four steps:
- Get a connection to the database (via a URL, username, password, or a pool).
- Prepare a statement — ideally a
PreparedStatementwith placeholders. - Execute the query and read the
ResultSet(for SELECT) or an update count (for INSERT/UPDATE/DELETE). - Close everything to release resources.
Modern JDBC uses try-with-resources so closing happens automatically, even when exceptions occur.
Always use PreparedStatement
The single most important habit in JDBC is using PreparedStatement with parameter placeholders (?) instead of building SQL by concatenating strings. This prevents SQL injection — a critical security flaw — and lets the database cache the query plan.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class StudentDao {
private static final String URL = "jdbc:postgresql://localhost:5432/lms";
// Returns the email for a given student id, or null if not found
public String findEmailById(long studentId) throws Exception {
// Parameterised SQL — the ? is a placeholder, never string concatenation
String sql = "SELECT email FROM students WHERE id = ?";
// try-with-resources closes Connection, PreparedStatement, and ResultSet
try (Connection conn = DriverManager.getConnection(URL, "app_user", "secret");
PreparedStatement ps = conn.prepareStatement(sql)) {
// Bind the value safely to the first placeholder
ps.setLong(1, studentId);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return rs.getString("email"); // read by column name
}
return null; // no matching row
}
}
}
}
Want to learn this properly?
Join the waitlist for our courses — beginner-friendly, project-first classes in Jalgaon.
Browse coursesTransactions
By default each statement auto-commits. When several statements must succeed or fail together (e.g. recording a payment and updating a balance), turn off auto-commit and control the transaction yourself:
try (Connection conn = DriverManager.getConnection(URL, "app_user", "secret")) {
conn.setAutoCommit(false); // start a manual transaction
try (PreparedStatement debit = conn.prepareStatement(
"UPDATE accounts SET balance = balance - ? WHERE id = ?");
PreparedStatement credit = conn.prepareStatement(
"UPDATE accounts SET balance = balance + ? WHERE id = ?")) {
debit.setBigDecimal(1, amount);
debit.setLong(2, fromId);
debit.executeUpdate();
credit.setBigDecimal(1, amount);
credit.setLong(2, toId);
credit.executeUpdate();
conn.commit(); // both succeeded — make it permanent
} catch (Exception e) {
conn.rollback(); // anything failed — undo everything
throw e;
}
}
Connection pooling
Opening a database connection is expensive. In any real application you do not call DriverManager.getConnection per request. Instead you use a connection pool (such as HikariCP) that keeps a set of reusable connections open. You then call dataSource.getConnection(), and "closing" it simply returns it to the pool. Spring Boot configures HikariCP for you automatically.
Common mistakes
- String-concatenating SQL with user input. This is the classic SQL injection hole. Always use
PreparedStatementplaceholders. - Not closing resources. Leaked connections exhaust the pool and crash the app. Use try-with-resources everywhere.
- Calling
getConnection()per request without a pool. It works in tutorials but collapses under real load. - Leaving auto-commit on for multi-step operations. Without an explicit transaction, a half-finished operation can corrupt data.
- Reading columns by index when names are clearer.
rs.getString("email")survives column reordering;rs.getString(1)is brittle.
FAQ
Do I need JDBC if I use Hibernate or Spring Data? Those frameworks use JDBC internally. Knowing it helps you debug connection, transaction, and performance issues the abstractions cannot fully hide.
Which driver do I need? One that matches your database — for example the PostgreSQL JDBC driver for Postgres. You add it as a dependency and JDBC discovers it automatically.
Keep learning
JDBC is the layer beneath ORMs. See how Hibernate and JPA build on it, how a REST API exposes this data, or explore the Advanced Java hub.
Want to master Java data access end to end? Join the waitlist for the Advanced Java course at Infoplanet, Jalgaon.
Want to learn this properly?
Join the waitlist for our courses — beginner-friendly, project-first classes in Jalgaon.
Browse coursesFounder, Infoplanet
Atul Kabra founded Infoplanet in 2001 and has spent over two decades teaching programming — C, C++, Java, databases and more — to students across Maharashtra.
Related guides
Advanced Java / Backend Roadmap
A clear, ordered roadmap for learning Advanced Java and backend development — from core Java through servlets, Spring Boot, REST APIs, and deployment.
Deploying a Java Web App
A practical guide to deploying a Spring Boot app: build an executable JAR, run it on a server, containerise with Docker, and check production essentials.
Hibernate & JPA Basics
Hibernate is the leading JPA implementation that maps Java objects to database tables. This guide explains ORM, entities, and the Jakarta Persistence annotations.
