Skip to content
Java jdbc 7 min read

Connecting to MySQL

Connecting Java to MySQL is one of the most practical things you can learn with JDBC. In just a few steps — add the driver JAR, craft a connection URL, and call DriverManager.getConnection() — you have a live link from your Java code to a running MySQL server.

Prerequisites

Before writing any Java code, make sure you have:

  • A running MySQL server (local install, Docker, or a cloud instance)
  • A database, a table, and a user account with the right privileges
  • Java 11+ and Maven or Gradle (or a plain JAR on the classpath)

If you want a zero-install sandbox, spin up MySQL in Docker:

docker run --name mysql-demo -e MYSQL_ROOT_PASSWORD=secret \
  -e MYSQL_DATABASE=mydb -p 3306:3306 -d mysql:8

Step 1 — Add the MySQL JDBC Driver

MySQL’s official JDBC driver is MySQL Connector/J. It is a pure Type 4 driver (pure Java, no native libraries required — see JDBC Drivers for background on driver types).

Maven

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

Gradle

implementation 'com.mysql:mysql-connector-j:8.3.0'

Plain JAR (no build tool)

Download mysql-connector-j-8.x.x.jar from MySQL Downloads and add it to your classpath:

javac -cp mysql-connector-j-8.3.0.jar MyApp.java
java  -cp .:mysql-connector-j-8.3.0.jar MyApp

Note: The group ID changed from mysql:mysql-connector-java to com.mysql:mysql-connector-j in version 8.0.31. Use the new artifact ID for any dependency added from 2022 onwards.

Step 2 — Understand the JDBC URL Format

The JDBC URL tells DriverManager which driver to use, which host to connect to, and which database to open. For MySQL it looks like this:

jdbc:mysql://<host>:<port>/<database>?<key>=<value>&<key>=<value>
PartExampleMeaning
jdbc:mysql://jdbc:mysql://Selects the MySQL driver
<host>localhostMySQL server hostname or IP
<port>3306MySQL port (default 3306)
<database>mydbDatabase (schema) to use
Query paramsuseSSL=falseDriver behaviour flags

Common query parameters you will encounter:

ParameterDefaultWhen to use
useSSL=falsetrue (8.x)Disable SSL in local dev to silence warnings
allowPublicKeyRetrieval=truefalseRequired by some auth plugins in dev
serverTimezone=UTCJVM TZPrevents timezone mismatch errors
characterEncoding=UTF-8Enforces UTF-8 for text columns
autoReconnect=truefalseReconnects after a dropped connection

A realistic development URL:

jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8

Warning: Never disable SSL in production. Use useSSL=true and point trustCertificateKeyStoreUrl at your CA certificate.

Step 3 — Open a Connection

Since JDBC 4.0 (Java 6), you do not need to call Class.forName(). The driver registers itself automatically via Java’s ServiceLoader mechanism as long as the JAR is on the classpath.

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

public class MySQLConnect {

    private static final String URL  =
        "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC";
    private static final String USER = "root";
    private static final String PASS = "secret";

    public static void main(String[] args) {
        try (Connection conn = DriverManager.getConnection(URL, USER, PASS)) {
            System.out.println("Connected to: " + conn.getCatalog());
            System.out.println("Driver:       " + conn.getMetaData().getDriverName());
            System.out.println("MySQL version: " +
                conn.getMetaData().getDatabaseProductVersion());
        } catch (SQLException e) {
            System.err.println("Connection failed: " + e.getMessage());
        }
    }
}

Output:

Connected to: mydb
Driver:       MySQL Connector/J
MySQL version: 8.0.36

Tip: Always wrap Connection, Statement, and ResultSet in try-with-resources. They all implement AutoCloseable, so Java closes them for you even if an exception is thrown — preventing connection leaks.

Step 4 — Create a Table and Insert Data

Let us create a products table and populate it so we have data to query.

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

public class SetupDatabase {

    private static final String URL =
        "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC";

    public static void main(String[] args) throws SQLException {
        try (Connection conn = DriverManager.getConnection(URL, "root", "secret");
             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
                )
            """);

            stmt.executeUpdate(
                "INSERT INTO products (name, price) VALUES ('Keyboard', 79.99)");
            stmt.executeUpdate(
                "INSERT INTO products (name, price) VALUES ('Mouse', 39.99)");
            stmt.executeUpdate(
                "INSERT INTO products (name, price) VALUES ('Monitor', 349.00)");

            System.out.println("Table created and seeded.");
        }
    }
}

Output:

Table created and seeded.

Note: Text blocks ("""...""") require Java 15+. For older Java, use a plain String with \n escapes or concatenation.

Step 5 — Query Data Safely with PreparedStatement

Use PreparedStatement for any query that includes user-supplied values. It prevents SQL injection by sending the SQL template and the parameters separately — the database never combines them into a raw string.

import java.sql.*;

public class QueryProducts {

    private static final String URL =
        "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC";

    public static void main(String[] args) throws SQLException {
        String search = "M";   // find products whose name starts with 'M'

        String sql = "SELECT id, name, price FROM products WHERE name LIKE ? ORDER BY price";

        try (Connection conn = DriverManager.getConnection(URL, "root", "secret");
             PreparedStatement ps = conn.prepareStatement(sql)) {

            ps.setString(1, search + "%");   // bind the parameter safely

            try (ResultSet rs = ps.executeQuery()) {
                System.out.printf("%-5s %-20s %10s%n", "ID", "Name", "Price");
                System.out.println("-".repeat(38));
                while (rs.next()) {
                    System.out.printf("%-5d %-20s %10.2f%n",
                        rs.getInt("id"),
                        rs.getString("name"),
                        rs.getDouble("price"));
                }
            }
        }
    }
}

Output:

ID    Name                      Price
--------------------------------------
2     Mouse                     39.99
3     Monitor                   349.00

Step 6 — Insert with Generated Keys

When you insert a row with an AUTO_INCREMENT primary key, you can retrieve the generated ID immediately:

String sql = "INSERT INTO products (name, price) VALUES (?, ?)";

try (Connection conn = DriverManager.getConnection(URL, "root", "secret");
     PreparedStatement ps = conn.prepareStatement(
             sql, Statement.RETURN_GENERATED_KEYS)) {

    ps.setString(1, "Webcam");
    ps.setDouble(2, 89.99);
    ps.executeUpdate();

    try (ResultSet keys = ps.getGeneratedKeys()) {
        if (keys.next()) {
            System.out.println("New product ID: " + keys.getLong(1));
        }
    }
}

Output:

New product ID: 4

Step 7 — Use Transactions for Multi-Step Operations

By default, JDBC runs in auto-commit mode — every statement commits immediately. For operations that must succeed or fail together, disable auto-commit and manage the transaction yourself. See Transaction Management for the full story.

try (Connection conn = DriverManager.getConnection(URL, "root", "secret")) {
    conn.setAutoCommit(false);   // begin transaction

    try (PreparedStatement ps1 = conn.prepareStatement(
                "UPDATE accounts SET balance = balance - ? WHERE id = ?");
         PreparedStatement ps2 = conn.prepareStatement(
                "UPDATE accounts SET balance = balance + ? WHERE id = ?")) {

        ps1.setBigDecimal(1, new java.math.BigDecimal("100.00"));
        ps1.setInt(2, 1);
        ps1.executeUpdate();

        ps2.setBigDecimal(1, new java.math.BigDecimal("100.00"));
        ps2.setInt(2, 2);
        ps2.executeUpdate();

        conn.commit();   // both updates succeed — commit
        System.out.println("Transfer complete.");

    } catch (SQLException e) {
        conn.rollback(); // one update failed — undo everything
        throw e;
    }
}

Common Errors and Fixes

Error messageLikely causeFix
Communications link failureMySQL not running or wrong host/portCheck server status and URL
Access denied for user 'root'@'localhost'Wrong username or passwordVerify credentials with mysql -u root -p
Unknown database 'mydb'Database does not existRun CREATE DATABASE mydb; in MySQL
Public Key Retrieval is not allowedcaching_sha2_password auth pluginAdd allowPublicKeyRetrieval=true to URL
The server time zone value is unrecognizedJVM/MySQL TZ mismatchAdd serverTimezone=UTC to URL
No suitable driver foundDriver JAR not on classpathVerify the dependency or JAR path

Under the Hood

When DriverManager.getConnection(url, user, pass) is called:

  1. ServiceLoader scan — At JVM startup the DriverManager class triggers a ServiceLoader scan of all JARs on the classpath for META-INF/services/java.sql.Driver. MySQL Connector/J registers com.mysql.cj.jdbc.Driver this way.
  2. URL routingDriverManager calls driver.acceptsURL(url) on each registered driver. The MySQL driver returns true only for URLs beginning with jdbc:mysql:// (and its variants like jdbc:mysql+srv:// for DNS SRV).
  3. TCP handshake — The driver opens a TCP socket to host:port, performs the MySQL protocol handshake (capability negotiation, authentication — SHA-256 or caching_sha2_password in MySQL 8), and selects the target database.
  4. Connection object — A ConnectionImpl is returned, wrapping the socket, a send/receive buffer, and the session state (current database, charset, transaction isolation).
  5. Statement execution — When you call ps.executeQuery(), the driver serialises the prepared statement handle and bound parameters into the MySQL binary protocol packet, writes it to the socket buffer, and blocks for a response.

Every ResultSet row is network data decoded on demand. The default fetch size is 0 (fetch all rows at once). For large result sets call ps.setFetchSize(Integer.MIN_VALUE) to stream rows one at a time and avoid out-of-memory errors.

Tip: In any real application, replace DriverManager.getConnection() with a HikariCP connection pool. HikariCP keeps a pool of pre-authenticated TCP connections and hands them out in microseconds, compared to the 5–50 ms cost of opening a fresh socket per request.

<!-- HikariCP Maven dependency -->
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>5.1.0</version>
</dependency>
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;

HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC");
config.setUsername("root");
config.setPassword("secret");
config.setMaximumPoolSize(10);

HikariDataSource ds = new HikariDataSource(config);

try (Connection conn = ds.getConnection()) {
    // use conn exactly as before
}
  • JDBC Overview — understand the full JDBC picture before diving into MySQL specifics
  • JDBC Drivers — learn the four driver types and why MySQL Connector/J is a Type 4 driver
  • Steps to Connect a Database — a generic, step-by-step walkthrough of the JDBC connection process
  • PreparedStatement — the safest and most performant way to execute parameterised SQL
  • Transaction Management — commit, rollback, savepoints, and isolation levels
  • ResultSet — navigate and read rows returned by your queries
Last updated June 13, 2026
Was this helpful?