Steps to Connect a Database
Connecting to a database in Java follows a clear, repeatable sequence. Once you know the five steps — load the driver, open a connection, create a statement, execute SQL, close resources — you can talk to any relational database with confidence.
The Five Steps at a Glance
Every JDBC program, no matter how simple or complex, walks through the same pipeline:
| Step | What happens |
|---|---|
| 1. Register the driver | Tell the JVM which Driver class handles your database URL |
| 2. Open a connection | Ask DriverManager for a live Connection |
| 3. Create a statement | Wrap your SQL in a Statement or PreparedStatement |
| 4. Execute SQL & process results | Run the query and iterate over the ResultSet |
| 5. Close resources | Release the connection, statement, and result set |
Let’s walk through each step in detail.
Step 1 — Register the Driver
A JDBC driver is a vendor-supplied JAR (e.g. mysql-connector-j-8.x.x.jar) that translates JDBC calls into the wire protocol your database speaks. Before JDBC 4.0 (Java 6) you had to load the driver class explicitly:
// Old style — required before JDBC 4.0 / Java 6
Class.forName("com.mysql.cj.jdbc.Driver");
Since JDBC 4.0, drivers register themselves automatically via java.util.ServiceLoader. As long as the driver JAR is on the classpath, you do not need Class.forName() at all. Modern code skips this step entirely.
Tip: If you use Maven or Gradle, just add the driver as a dependency and JDBC auto-discovery does the rest. See JDBC Drivers for how driver types work under the hood.
Step 2 — Open a Connection
Call DriverManager.getConnection() with three things: the JDBC URL, a username, and a password.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class ConnectDemo {
public static void main(String[] args) throws SQLException {
String url = "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC";
String user = "root";
String pass = "secret";
Connection conn = DriverManager.getConnection(url, user, pass);
System.out.println("Connected! Catalog: " + conn.getCatalog());
conn.close();
}
}
Output:
Connected! Catalog: mydb
The URL format is always jdbc:<subprotocol>://<host>:<port>/<database>[?options]. Every database vendor defines its own subprotocol:
| Database | Example URL |
|---|---|
| MySQL | jdbc:mysql://localhost:3306/mydb |
| PostgreSQL | jdbc:postgresql://localhost:5432/mydb |
| Oracle | jdbc:oracle:thin:@localhost:1521:mydb |
| SQLite | jdbc:sqlite:/path/to/file.db |
| H2 (in-memory) | jdbc:h2:mem:testdb |
Warning: Never hard-code credentials in source code. Load them from environment variables or a properties file instead. Anyone who can read your
.javaor.classfile will see them.
Step 3 — Create a Statement
With a live Connection, you create a Statement to carry your SQL to the database.
import java.sql.Statement;
Statement stmt = conn.createStatement();
For anything that accepts user-supplied data, use PreparedStatement instead — it prevents SQL injection and often performs better because the database can cache the query plan:
import java.sql.PreparedStatement;
PreparedStatement ps = conn.prepareStatement(
"SELECT id, name FROM users WHERE age > ?");
ps.setInt(1, 18);
Note:
Statementis fine for one-off administrative queries where no user input is involved. For everything else, always preferPreparedStatement. See Statement for a full comparison.
Step 4 — Execute SQL and Process Results
Statement offers three execution methods depending on what you need:
| Method | Use when… | Returns |
|---|---|---|
executeQuery(sql) | SELECT statements | ResultSet |
executeUpdate(sql) | INSERT / UPDATE / DELETE / DDL | int (rows affected) |
execute(sql) | Unknown at compile time | boolean |
Reading rows with ResultSet
import java.sql.ResultSet;
ResultSet rs = stmt.executeQuery("SELECT id, name, age FROM users");
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt("age");
System.out.printf("%d | %-15s | %d%n", id, name, age);
}
Output:
1 | Alice | 25
2 | Bob | 30
3 | Carol | 22
rs.next() advances the internal cursor one row forward and returns false when no more rows remain. You read column values either by name (rs.getString("name")) or by 1-based index (rs.getString(2)). Column names are generally clearer and safer when the query might change.
Writing data
int rowsAffected = stmt.executeUpdate(
"INSERT INTO users (name, age) VALUES ('Dave', 28)");
System.out.println("Inserted " + rowsAffected + " row(s).");
Output:
Inserted 1 row(s).
Step 5 — Close Resources
Open database connections hold server-side resources (memory, file handles, network sockets). Always close ResultSet, Statement, and Connection when you are done — in that order, innermost first.
The cleanest approach is try-with-resources, which closes everything automatically even if an exception is thrown:
import java.sql.*;
public class FullExample {
public static void main(String[] args) throws SQLException {
String url = "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC";
String user = "root";
String pass = "secret";
try (Connection conn = DriverManager.getConnection(url, user, pass);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT id, name FROM users")) {
while (rs.next()) {
System.out.println(rs.getInt("id") + " — " + rs.getString("name"));
}
}
// conn, stmt, rs are all closed automatically here
}
}
Warning: Forgetting to close a
Connectionis one of the most common JDBC bugs. Over time, unclosed connections exhaust the database’s connection limit and crash your application. Try-with-resources makes this impossible to forget.
Putting It All Together — A Complete Working Example
Here is a self-contained program that creates a table, inserts rows, queries them, and cleans up — all five steps in one place:
import java.sql.*;
public class JdbcStepsDemo {
static final String URL = "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC";
static final String USER = "root";
static final String PASS = "secret";
public static void main(String[] args) throws SQLException {
// Steps 2–5 handled via try-with-resources
try (Connection conn = DriverManager.getConnection(URL, USER, PASS)) {
// DDL — create table if it doesn't exist
try (Statement stmt = conn.createStatement()) {
stmt.executeUpdate(
"CREATE TABLE IF NOT EXISTS products (" +
" id INT AUTO_INCREMENT PRIMARY KEY," +
" name VARCHAR(100) NOT NULL," +
" price DECIMAL(10,2) NOT NULL" +
")");
}
// INSERT with PreparedStatement (Step 3 + 4)
String insertSql = "INSERT INTO products (name, price) VALUES (?, ?)";
try (PreparedStatement ps = conn.prepareStatement(insertSql)) {
ps.setString(1, "Widget");
ps.setDouble(2, 9.99);
ps.executeUpdate();
ps.setString(1, "Gadget");
ps.setDouble(2, 24.95);
ps.executeUpdate();
}
// SELECT and print results (Step 4)
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT id, name, price FROM products ORDER BY id")) {
System.out.printf("%-5s %-15s %s%n", "ID", "Name", "Price");
System.out.println("-".repeat(30));
while (rs.next()) {
System.out.printf("%-5d %-15s %.2f%n",
rs.getInt("id"),
rs.getString("name"),
rs.getDouble("price"));
}
}
}
}
}
Output:
ID Name Price
------------------------------
1 Widget 9.99
2 Gadget 24.95
Under the Hood
When DriverManager.getConnection() returns, a real TCP socket (or local pipe on some databases) is open between your JVM and the database server. That socket stays alive until you call conn.close().
Auto-commit is enabled by default, meaning each executeUpdate() is immediately committed as its own transaction. If you need multi-statement atomicity, call conn.setAutoCommit(false) before your statements and then conn.commit() or conn.rollback(). See Transaction Management for details.
Connection pooling — In real applications, opening a new TCP connection for every request is too slow (typically 50–200 ms). A connection pool (HikariCP is the industry standard) maintains a warm pool of pre-opened connections and lends them out on demand. From your code’s perspective you still call getConnection() and close the connection when done; the pool just recycles the underlying socket rather than closing it.
Fetch size — By default, some drivers fetch all rows from the server into client memory at once. For large result sets, call stmt.setFetchSize(50) to stream rows in batches, keeping memory usage flat.
Common Errors and Fixes
| Error message | Likely cause | Fix |
|---|---|---|
No suitable driver found for jdbc:... | Driver JAR not on classpath | Add the driver dependency to Maven/Gradle |
Communications link failure | Wrong host/port or database not running | Check the URL and that the DB server is up |
Access denied for user 'root'@'localhost' | Wrong credentials | Double-check username and password |
Unknown database 'mydb' | Database does not exist | Create it: CREATE DATABASE mydb; |
Too many connections | Connections not closed | Use try-with-resources; add a connection pool |
Related Topics
- JDBC Drivers — Understand the four driver types and how JDBC auto-discovery works before you connect.
- DriverManager — Deep dive into how
DriverManager.getConnection()finds the right driver and when to switch toDataSource. - PreparedStatement — The right way to pass parameters to SQL — safer and faster than plain
Statement. - Transaction Management — Control commit, rollback, and savepoints to keep multi-step operations atomic.
- Connection Interface — Everything a
Connectionobject can do beyond just running queries. - Connecting to MySQL — Step-by-step guide to setting up a real MySQL database and running your first query against it.