JDBC & Databases

Introduction to JDBC

15 min Lesson 1 of 13

Introduction to JDBC

JDBC — Java Database Connectivity — is the standard Java API for connecting to, querying, and updating relational databases. It ships as part of the JDK (in the java.sql and javax.sql packages), so you need no third-party dependency just to open a connection. Everything from JPA and Hibernate to Spring Data and jOOQ ultimately calls JDBC under the hood.

This tutorial assumes you are comfortable with Java generics, lambdas, streams, and resource management via try-with-resources. We will not revisit those concepts; we will use them freely as tools.

Why JDBC still matters

You might wonder why you should learn a 1997-era API when higher-level frameworks exist. Two reasons:

  • Debugging and tuning. Every ORM exception stack trace eventually bottoms out in JDBC. Understanding the API means you can diagnose slow queries, connection-pool exhaustion, and transaction boundary bugs that look mysterious at the framework level.
  • Control. Bulk batch inserts, stored-procedure calls, custom type mappings, and vendor-specific features are often cleaner in raw JDBC than wrestling with ORM annotations.
JDBC is an abstraction layer, not a protocol. It does not define how to talk to a database — it defines a portable Java API. The actual wire protocol is handled by a driver written by the database vendor (or community). Your code calls JDBC; the driver calls the database.

The four-layer model

Understanding JDBC requires understanding its layered architecture:

  1. Your application code — you call standard java.sql interfaces such as Connection, Statement, and ResultSet.
  2. JDBC API — the interfaces and abstract classes that define the contract (defined in java.sql and javax.sql).
  3. JDBC Driver Manager / DataSource — the bridge that loads the correct driver and hands you a Connection.
  4. JDBC Driver — vendor-supplied JAR that implements the JDBC interfaces and speaks the actual database protocol (e.g. MySQL wire protocol, PostgreSQL's libpq-style binary protocol).

Because your code only ever touches interfaces defined in layer 2, switching from PostgreSQL to MySQL is — in theory — a driver swap plus a connection-string change, with no changes to your application logic.

Driver types

The JDBC specification originally defined four driver types. In practice you will only encounter Type 4 today:

  • Type 1 — JDBC-ODBC bridge (removed in Java 8): translated JDBC calls to ODBC. Long deprecated.
  • Type 2 — native-API driver: wraps a database's native C library. Requires native code on the machine.
  • Type 3 — network-protocol driver: sends calls to a middleware server. Rarely used.
  • Type 4 — pure-Java driver: a pure Java JAR that speaks the database's wire protocol directly. This is what mysql-connector-j, postgresql, h2, and every modern driver are. Zero native dependencies; just add the JAR to the classpath.
Always use a Type 4 driver. Add it via Maven or Gradle and you are done. For PostgreSQL that is org.postgresql:postgresql; for MySQL it is com.mysql.cj:mysql-connector-j; for H2 (in-memory, great for tests) it is com.h2database:h2.

The key JDBC interfaces

The entire API is built around a handful of interfaces you will use in every JDBC program:

  • java.sql.Connection — represents a single physical connection to the database. It owns transactions. Always closed in a finally block or try-with-resources.
  • java.sql.Statement — executes a static SQL string. Never use this with user-supplied data (SQL injection risk).
  • java.sql.PreparedStatement — pre-compiles a parameterised SQL string. The correct choice for almost all queries.
  • java.sql.CallableStatement — calls stored procedures.
  • java.sql.ResultSet — a cursor over rows returned by a query. Rows are accessed one at a time with next().
  • javax.sql.DataSource — a factory for Connection objects; the production alternative to DriverManager. Connection pools implement this interface.

A minimal end-to-end sketch

Before diving into each piece in detail over the following lessons, here is the smallest possible JDBC program so you can see how the pieces fit together. It uses H2 in in-memory mode — no external database required:

import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class JdbcHello { public static void main(String[] args) throws Exception { // 1. Obtain a connection — DriverManager locates the H2 driver // automatically because H2's JAR is on the classpath. String url = "jdbc:h2:mem:demo;DB_CLOSE_DELAY=-1"; try (Connection conn = DriverManager.getConnection(url, "sa", "")) { // 2. Create a Statement and run DDL try (Statement stmt = conn.createStatement()) { stmt.execute("CREATE TABLE greeting (message VARCHAR(100))"); stmt.execute("INSERT INTO greeting VALUES ('Hello from JDBC')"); } // 3. Query and read the ResultSet try (Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT message FROM greeting")) { while (rs.next()) { System.out.println(rs.getString("message")); } } } // conn auto-closed here } }

Output: Hello from JDBC

Notice the nested try-with-resources. Connection, Statement, and ResultSet all implement AutoCloseable, so the JVM closes them in reverse order when each block exits — even on exceptions. Forgetting to close these objects is the most common JDBC resource leak.

Never use Statement with user input. The example above uses a hard-coded string, which is safe. As soon as a user's value appears in an SQL string concatenation you have a SQL injection vulnerability. The PreparedStatement lesson covers the correct approach — but keep this rule in mind from the start.

How the driver is loaded

Since Java 6 you do not need to call Class.forName("org.postgresql.Driver") manually. The JDK uses the ServiceLoader mechanism: every JDBC driver JAR declares itself in META-INF/services/java.sql.Driver. When DriverManager.getConnection(url, ...) is called, it iterates registered drivers, offers each one the URL, and the first driver that recognises the URL prefix (e.g. jdbc:postgresql:) accepts the call and returns a Connection.

// Old style (Java 5 era) — no longer needed: // Class.forName("org.postgresql.Driver"); // Modern style — just call getConnection; ServiceLoader handles discovery: Connection conn = DriverManager.getConnection( "jdbc:postgresql://localhost:5432/mydb", "alice", "secret");

JDBC URL anatomy

Every JDBC URL follows the pattern jdbc:<subprotocol>:<subname>. The subname is vendor-specific. Examples:

  • jdbc:h2:mem:testdb — H2 in-memory database named "testdb"
  • jdbc:postgresql://localhost:5432/mydb — PostgreSQL on localhost
  • jdbc:mysql://localhost:3306/mydb?useSSL=false — MySQL with a query parameter
  • jdbc:sqlserver://host:1433;databaseName=mydb — SQL Server

DriverManager vs DataSource

DriverManager.getConnection() opens a new physical TCP connection every time it is called. In a real application that is unacceptably expensive (each open/close costs ~10–100 ms and a file-descriptor). The solution is a connection pool — a DataSource implementation that keeps a pool of already-open connections and hands them out on request, returning them to the pool on close().

Popular pool libraries — HikariCP, c3p0, DBCP — all implement javax.sql.DataSource, so your DAO code only depends on the interface. We will examine pooling in detail later in this tutorial.

Rule of thumb for production code: use a DataSource (connection pool) everywhere. Use DriverManager only in quick standalone scripts, tests, or educational examples like this lesson.

Summary

JDBC is Java's vendor-neutral API for relational database access. Its layered model — your code calls interfaces, the driver implements them — means you can switch databases with minimal code changes. The five types of objects you will manipulate are Connection, Statement, PreparedStatement, ResultSet, and DataSource. The driver is loaded automatically via ServiceLoader; no manual class registration is needed. In production always use a pooled DataSource; DriverManager is for simple scripts and tests. The next lesson digs into how to configure and obtain a connection properly.