LearnContact
Lesson 7726 min read

MySQL with Java (JDBC)

Learn how to connect a Java application to MySQL using JDBC, run queries safely with PreparedStatement, and manage connections correctly.

Introduction

So far you have written SQL directly against a MySQL server using a client tool. In real applications, though, SQL is usually issued from application code — a web server, a desktop program, or a backend service. Java is one of the most common languages used for exactly this, and JDBC is the standard bridge between Java code and a database like MySQL.

In this lesson you will learn what JDBC is, how to connect a Java program to a MySQL database, how to run queries and read the results back, and — critically — how to do this safely using PreparedStatement so your application is not vulnerable to SQL injection.

What You Will Learn
  • What JDBC is and the role it plays between Java and MySQL.
  • How to add the MySQL JDBC driver to a Java project.
  • How to open a connection with DriverManager.getConnection().
  • How to run a query with Statement and read rows from a ResultSet.
  • How to use PreparedStatement to safely handle user input.
  • How to close connections correctly using try-with-resources.

What is JDBC?

JDBC stands for Java Database Connectivity. It is the standard Java API for connecting to relational databases and sending them SQL. JDBC itself does not know anything about MySQL specifically — it defines a common set of interfaces (Connection, Statement, ResultSet, and so on) that any database vendor can implement.

To actually talk to MySQL, your project needs the MySQL JDBC driver — a library that implements those JDBC interfaces for MySQL's specific network protocol. Because your code is written against the generic JDBC interfaces, the same code pattern works for MySQL, PostgreSQL, or any other JDBC-supported database, with only the driver and connection URL changing.

A Standard API

JDBC defines Connection, Statement, and ResultSet interfaces that work the same way across databases.

A Driver Does the Talking

The MySQL JDBC driver translates JDBC calls into MySQL's actual network protocol.

Portable Code

Code written against JDBC interfaces changes very little if you ever switch databases.

Part of the JDK

The core JDBC interfaces (java.sql package) ship with Java itself; only the driver is a separate dependency.

Adding the MySQL JDBC Driver

The MySQL JDBC driver is distributed as a library called mysql-connector-j. If you are using Maven, add it as a dependency in your pom.xml.

pom.xml
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.3.0</version>
</dependency>

If you are using Gradle instead, the equivalent dependency line looks like this.

build.gradle
implementation 'com.mysql:mysql-connector-j:8.3.0'
Note

Modern JDBC drivers register themselves automatically, so you generally no longer need to call Class.forName("com.mysql.cj.jdbc.Driver") manually before connecting, though you may still see it in older tutorials and codebases.

Connecting to MySQL

A connection is opened using DriverManager.getConnection(), passing a JDBC URL, a username, and a password. The URL identifies the database server, port, and database name.

ConnectExample.java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class ConnectExample {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/school";
        String user = "app_user";
        String password = "secret123";

        try (Connection conn = DriverManager.getConnection(url, user, password)) {
            System.out.println("Connected to MySQL successfully!");
        } catch (SQLException e) {
            System.out.println("Connection failed: " + e.getMessage());
        }
    }
}
Output
Connected to MySQL successfully!

The URL format jdbc:mysql://host:port/database tells the driver which server and database to connect to. Here, localhost:3306 is the default MySQL server address and port, and school is the target database.

Running a Query with Statement

Once connected, a Statement object lets you send SQL to the server. Running a SELECT returns a ResultSet, which you step through one row at a time with next().

QueryExample.java
import java.sql.*;

public class QueryExample {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/school";

        try (Connection conn = DriverManager.getConnection(url, "app_user", "secret123");
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery("SELECT id, name, marks FROM students")) {

            while (rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                int marks = rs.getInt("marks");
                System.out.println(id + " - " + name + " - " + marks);
            }
        } catch (SQLException e) {
            System.out.println("Query failed: " + e.getMessage());
        }
    }
}
Output
1 - Alex - 78
2 - Sam - 65
3 - Priya - 91

rs.next() advances the cursor to the next row and returns false once there are no more rows, which is why it works perfectly as a while-loop condition. The getXxx() methods then read a specific column from the current row, either by name or by position.

Using PreparedStatement

A plain Statement becomes dangerous the moment you build SQL by concatenating user input directly into the string, because an attacker can inject extra SQL. PreparedStatement solves this by using placeholders (?) for values, which the driver sends separately from the SQL itself — the database never confuses user input with SQL syntax.

PreparedStatementExample.java
import java.sql.*;

public class PreparedStatementExample {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/school";
        int minMarks = 60;

        String sql = "SELECT name, marks FROM students WHERE marks >= ?";

        try (Connection conn = DriverManager.getConnection(url, "app_user", "secret123");
             PreparedStatement pstmt = conn.prepareStatement(sql)) {

            pstmt.setInt(1, minMarks);

            try (ResultSet rs = pstmt.executeQuery()) {
                while (rs.next()) {
                    System.out.println(rs.getString("name") + " - " + rs.getInt("marks"));
                }
            }
        } catch (SQLException e) {
            System.out.println("Query failed: " + e.getMessage());
        }
    }
}
Output
Alex - 78
Priya - 91
Never Build SQL with String Concatenation

Writing "SELECT * FROM students WHERE name = '" + userInput + "'" lets an attacker supply input like ' OR '1'='1 to change the meaning of your query entirely. PreparedStatement with ? placeholders prevents this class of bug completely.

Inserting Data Safely

PreparedStatement is not just for SELECT — it is the standard way to run any parameterized statement, including INSERT, UPDATE, and DELETE. Use executeUpdate() for statements that modify data rather than return rows.

InsertExample.java
import java.sql.*;

public class InsertExample {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/school";
        String sql = "INSERT INTO students (name, marks) VALUES (?, ?)";

        try (Connection conn = DriverManager.getConnection(url, "app_user", "secret123");
             PreparedStatement pstmt = conn.prepareStatement(sql)) {

            pstmt.setString(1, "Jordan");
            pstmt.setInt(2, 88);

            int rowsInserted = pstmt.executeUpdate();
            System.out.println(rowsInserted + " row(s) inserted.");
        } catch (SQLException e) {
            System.out.println("Insert failed: " + e.getMessage());
        }
    }
}
Output
1 row(s) inserted.

Closing Connections Properly

Every Connection, Statement, and ResultSet holds real resources on both the Java side and the MySQL server side, so each one must be closed when you are done with it. Forgetting to close connections is a common cause of an application slowly running out of available database connections.

The try-with-resources syntax used in every example above handles this automatically: Connection, Statement, and ResultSet all implement AutoCloseable, so they are closed the moment the try block ends — even if an exception is thrown.

ManualCloseExample.java
Connection conn = null;
try {
    conn = DriverManager.getConnection(url, user, password);
    // ... use the connection ...
} catch (SQLException e) {
    System.out.println("Error: " + e.getMessage());
} finally {
    if (conn != null) {
        try {
            conn.close();
        } catch (SQLException e) {
            System.out.println("Failed to close connection: " + e.getMessage());
        }
    }
}
Prefer try-with-resources

The manual try/finally pattern above works but is verbose and easy to get wrong. Always prefer try-with-resources when your JDBC objects implement AutoCloseable, which Connection, Statement, PreparedStatement, and ResultSet all do.

Common Mistakes

Avoid These Mistakes
  • Building SQL strings by concatenating user input instead of using PreparedStatement placeholders.
  • Forgetting to close connections, statements, and result sets, eventually exhausting the connection pool.
  • Opening a brand-new connection for every single query instead of reusing or pooling connections.
  • Ignoring SQLException instead of logging or handling it meaningfully.
  • Hardcoding database credentials directly in source code instead of using configuration or environment variables.

Best Practices

  • Always use PreparedStatement for any query that includes a value from outside your code.
  • Use try-with-resources so connections and statements are always closed, even on error.
  • Keep database credentials outside of source code, in configuration or environment variables.
  • In real applications, use a connection pool (such as HikariCP) rather than opening a raw connection per request.
  • Catch and log SQLException with enough detail to diagnose failures without exposing sensitive details to end users.

Frequently Asked Questions

Do I need to call Class.forName() to load the driver?

With modern JDBC drivers (JDBC 4.0 and later, which includes all current MySQL Connector/J versions) this is done automatically via a service loader mechanism, so it is no longer required, though it is harmless to include.

What is the difference between Statement and PreparedStatement?

Statement sends a SQL string as-is and is unsafe with untrusted input. PreparedStatement precompiles the SQL with placeholders and binds values separately, which is both safer and often faster for repeated queries.

Why did I get "No suitable driver found"?

This usually means the MySQL Connector/J dependency is missing from your project, or the JDBC URL is malformed. Double-check your build file dependency and that the URL starts with jdbc:mysql://.

Should every request open a new connection?

No — opening a connection is relatively expensive. Real applications use a connection pool that maintains a small set of reusable open connections instead of creating one from scratch each time.

Can JDBC code work with other databases besides MySQL?

Yes. Because JDBC is a standard API, the same Connection/Statement/ResultSet code pattern works with PostgreSQL, SQL Server, and others — only the driver dependency and connection URL need to change.

Key Takeaways

  • JDBC (Java Database Connectivity) is the standard Java API for talking to relational databases.
  • The MySQL JDBC driver (mysql-connector-j) implements those interfaces for MySQL specifically.
  • DriverManager.getConnection(url, user, password) opens a connection using a jdbc:mysql:// URL.
  • Statement and ResultSet run queries and read rows, but PreparedStatement with ? placeholders is required whenever a query includes external input.
  • Connection, Statement, and ResultSet should always be closed, ideally using try-with-resources.

Summary

JDBC gives Java applications a standard, portable way to talk to MySQL. You open a connection with DriverManager, run queries through Statement or, far more safely, through PreparedStatement, read results row by row from a ResultSet, and close everything properly with try-with-resources.

In this lesson, you connected to MySQL from Java, ran a query and read back its results, and learned why PreparedStatement is the correct way to handle any value coming from outside your code. Next, you will do the same thing from Node.js, using the mysql2 package.

Lesson 77 Completed
  • You understand what JDBC is and the role the MySQL driver plays.
  • You can open a connection with DriverManager.getConnection().
  • You can run queries with Statement and read rows from a ResultSet.
  • You can safely bind values using PreparedStatement to prevent SQL injection.
  • You know why closing connections with try-with-resources matters.
Next Lesson →

MySQL with Node.js