How do I handle SQL exceptions in JDBC for MySQL properly?

For MySQL (mysql-connector-j), “proper” SQLException handling is the same core approach as JDBC in general, plus a few MySQL-specific signals (SQLState + error code) that are worth using for translation/retry decisions.

1) Keep the important diagnostics (MySQL error code + SQLState)

MySQL gives you two invaluable fields:

  • e.getErrorCode()MySQL vendor error code (e.g., 1062 for duplicate key)
  • e.getSQLState()SQLState (often 23000, 40001, etc.)

A good pattern is: wrap once with context, but preserve those fields.

package org.kodejava.jdbc;

import java.sql.SQLException;

public final class MySqlExceptions {
    public static RuntimeException translate(String operation, SQLException e) {
        String msg = operation
                     + " failed (SQLState=" + e.getSQLState()
                     + ", errorCode=" + e.getErrorCode() + ")";

        // Keep e as the cause.
        return switch (e.getErrorCode()) {
            case 1062 ->
                    new IllegalStateException(msg + " - duplicate key", e); // unique constraint violation
            case 1213 ->
                    new IllegalStateException(msg + " - deadlock", e);      // often retryable
            case 1205 ->
                    new IllegalStateException(msg + " - lock wait timeout", e); // often retryable
            default -> new RuntimeException(msg, e);
        };
    }
}

2) MySQL error codes you’ll commonly care about

These are the ones that usually drive different handling:

Situation MySQL error code Typical SQLState What to do
Unique constraint violation (“Duplicate entry”) 1062 23000 Return “already exists” / map to 409 / domain error
Deadlock found 1213 40001 Often safe to retry the whole transaction
Lock wait timeout exceeded 1205 often 41000 Often retry or surface “please retry”
Foreign key constraint fails 1451/1452 23000 Map to domain validation (cannot delete/insert due to FK)
Connection/link failure varies 08xxx / 08S01 Treat as transient infra failure; maybe retry with backoff

Rule of thumb: prefer the vendor error code for MySQL-specific branching (it’s the most consistent), and keep SQLState for general categorization/logging.

3) Retrying safely (only for the right failures)

Only retry if:

  • the operation is idempotent, or you’re retrying the entire transaction from the beginning, and
  • the failure is one of the known transient classes (deadlock / lock timeout / connection hiccup).

A minimal “should retry?” helper:

package org.kodejava.jdbc;

import java.sql.SQLException;

public final class MySqlRetry {
    public static boolean isRetryable(SQLException e) {
        int code = e.getErrorCode();
        String state = e.getSQLState();

        // MySQL deadlock / lock wait timeout
        if (code == 1213 || code == 1205) return true;

        // Connection exception class (SQLState starts with "08")
        if (state != null && state.startsWith("08")) return true;

        return false;
    }
}

If you do retry, keep it small (e.g., 2–3 attempts) with jittered backoff, and log the final failure with the full chain (getNextException()) and suppressed exceptions.

4) Transactions: rollback without hiding the original error

With MySQL, rollback can also throw if the connection is broken. Best practice: attach rollback failure as suppressed so you don’t lose the root cause.

package org.kodejava.jdbc;

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

public final class TxUtil {
    public static void rollbackQuietly(Connection con, SQLException original) {
        try {
            con.rollback();
        } catch (SQLException rb) {
            original.addSuppressed(rb);
        }
    }
}

5) MySQL Connector/J note: you usually don’t need Class.forName(...)

With modern JDBC drivers (including MySQL Connector/J 8+), the driver is auto-registered via the Service Provider mechanism. Calling Class.forName("com.mysql.cj.jdbc.Driver") is typically unnecessary unless you’re in a very unusual classloading environment.

6) What to log (and what not to log)

Log:

  • operation name (e.g., "insert user")
  • SQLState, error code
  • exception chain (getNextException())
  • safe parameter identifiers (e.g., user id), not secrets

Avoid logging:

  • credentials
  • sensitive values (passwords, tokens)
  • huge SQL strings with embedded data (use prepared statements so you don’t have that problem)

How do I connect to a MySQL database using JDBC?

Connecting to a MySQL database using JDBC involves a few straightforward steps: adding the driver, defining your connection credentials, and using the DriverManager to open a session.

1. Add the MySQL Connector Dependency

First, ensure you have the MySQL JDBC driver in your project. If you are using Maven, add this to your pom.xml:

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>9.1.0</version> <!-- Use the latest version -->
</dependency>

2. Basic Connection Code

Here is a clean example of how to establish a connection and execute a simple query:

package org.kodejava.jdbc;

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

public class MySqlConnection {
    // JDBC URL: jdbc:mysql://[host]:[port]/[database_name]
    private static final String URL = "jdbc:mysql://localhost:3306/your_database";
    private static final String USER = "root";
    private static final String PASSWORD = "your_password";

    public static void main(String[] args) {
        // Try-with-resources automatically closes Connection, Statement, and ResultSet
        try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD)) {

            if (conn != null) {
                System.out.println("Connected to the database!");

                // Example: Execute a simple query
                String sql = "SELECT * FROM users";
                try (Statement stmt = conn.createStatement();
                     ResultSet rs = stmt.executeQuery(sql)) {

                    while (rs.next()) {
                        System.out.println("User: " + rs.getString("username"));
                    }
                }
            }

        } catch (SQLException e) {
            System.err.println("SQL State: " + e.getSQLState());
            System.err.println("Error Code: " + e.getErrorCode());
            System.err.println("Message: " + e.getMessage());
        }
    }
}

Key Components:

  • JDBC URL: For MySQL, it always starts with jdbc:mysql://. You can also append parameters like ?useSSL=false&serverTimezone=UTC to handle specific environment requirements.
  • DriverManager: The factory class that manages JDBC drivers and creates connections.
  • SQLException: Always wrap your JDBC code in a try-catch block, as database operations are prone to external failures (network issues, incorrect credentials, etc.).
  • Try-with-resources: In modern Java, you don’t need to manually call .close() in a finally block if you declare the resources inside the try (...) parentheses.

How do I export MySQL database schema into markdown format?

The following code example demonstrate how to export MySQL database schema into markdown table format. We get the table structure information by executing MySQL’s DESCRIBE statement.

The steps we do in the code snippet below:

  • Connect to the database.
  • We obtain the list of table name from the database / schema.
  • Executes DESCRIBE statement for each table name.
  • Read table structure information such as field, type, null, key, default and extra.
  • Write the information into markdown table format and save it into table.md.

And here are the complete code snippet.

package org.kodejava.jdbc;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class DescribeMySQLToMarkDown {
    private static final String URL = "jdbc:mysql://localhost/kodejava";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "";

    public static void main(String[] args) {
        String tableQuery = """
                select table_name
                from information_schema.tables
                where table_schema = 'kodejava'
                  and table_type = 'BASE TABLE'
                order by table_name;
                """;

        try (Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD)) {
            Statement stmt = connection.createStatement();
            ResultSet resultSet = stmt.executeQuery(tableQuery);
            List<String> tables = new ArrayList<>();
            while (resultSet.next()) {
                tables.add(resultSet.getString("table_name"));
            }

            System.out.println(tables.size() + " tables found.");

            try (BufferedWriter writer = new BufferedWriter(new FileWriter("table.md"))) {
                for (String table : tables) {
                    System.out.println("Processing table: " + table);
                    Statement statement = connection.createStatement();
                    ResultSet descResult = statement.executeQuery("DESCRIBE " + table);

                    writer.write(String.format("Table Name: **%s**%n%n", table));
                    writer.write("| Field Name | Data Type | Null | Key | Default | Extra |\n");
                    writer.write("|:---|:---|:---|:---|:---|:---|\n");
                    while (descResult.next()) {
                        String field = descResult.getString("field");
                        String type = descResult.getString("type");
                        String nullInfo = descResult.getString("null");
                        String key = descResult.getString("key");
                        String defaultInfo = descResult.getString("default");
                        String extra = descResult.getString("extra");
                        String line = String.format("| %s | %s | %s | %s | %s | %s |%n",
                                field, type, nullInfo, key, defaultInfo, extra);
                        writer.write(line);
                    }
                    writer.write("\n<br/>\n<br/>\n");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

This code snippet will produce something like below. I have tidy up the markdown for a better presentation.

Table Name: **books**

| Field Name | Data Type       | Null | Key | Default | Extra          |
|:-----------|:----------------|:-----|:----|:--------|:---------------|
| id         | bigint unsigned | NO   | PRI | null    | auto_increment |
| isbn       | varchar(30)     | NO   |     | null    |                |

<br/>
<br/>

Maven dependencies

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

Maven Central

How do I read MySQL data from Android using JDBC?

This example show you how to connect and read data from MySQL database directly from Android. The following steps and code snippet will show you how to do it.

Add the MySQL JDBC driver into your project dependencies. Open the app/build.gradle file and add the dependency.

...
...

dependencies {
    ...
    ...
    implementation 'mysql:mysql-connector-java:5.1.49'
}

If you want to connect to MariaDB you can change the JDBC driver dependency using 'org.mariadb.jdbc:mariadb-java-client:1.8.0', also update the JDBC url in the code snippet by replacing mysql with mariadb.

Next, add internet permission to our application in AndroidManifest.xml file.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="org.kodejava.android">

    <uses-permission android:name="android.permission.INTERNET" />

    ...
    ...

</manifest>

Let’s connect, read data from the database and display the information on the screen. In the code snippet we create an AsyncTask to read the information from the database. In the doInBackground() method we open a connection to the database, create a PreparedStatement, execute a query, get a ResultSet and read the information from it. We pack the data into a Map and return it.

After the doInBackground() method finish its execution the onPostExecute() method will be called. In this method we take the result, the Map returned by the doInBackground() method, and set the values into the TextView components for display.

package org.kodejava.android;

import android.annotation.SuppressLint;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.HashMap;
import java.util.Map;

public class MainActivity extends AppCompatActivity {
    private static final String URL = "jdbc:mysql://192.168.0.107:3306/kodejava";
    private static final String USER = "kodejava";
    private static final String PASSWORD = "kodejava";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new InfoAsyncTask().execute();
    }

    @SuppressLint("StaticFieldLeak")
    public class InfoAsyncTask extends AsyncTask<Void, Void, Map<String, String>> {
        @Override
        protected Map<String, String> doInBackground(Void... voids) {
            Map<String, String> info = new HashMap<>();

            try (Connection connection = DriverManager.getConnection(URL, USER, PASSWORD)) {
                String sql = "SELECT name, address, phone_number FROM school_info LIMIT 1";
                PreparedStatement statement = connection.prepareStatement(sql);
                ResultSet resultSet = statement.executeQuery();
                if (resultSet.next()) {
                    info.put("name", resultSet.getString("name"));
                    info.put("address", resultSet.getString("address"));
                    info.put("phone_number", resultSet.getString("phone_number"));
                }                
            } catch (Exception e) {
                Log.e("InfoAsyncTask", "Error reading school information", e);
            }

            return info;
        }

        @Override
        protected void onPostExecute(Map<String, String> result) {
            if (!result.isEmpty()) {
                TextView textViewName = findViewById(R.id.textViewName);
                TextView textViewAddress = findViewById(R.id.textViewAddress);
                TextView textViewPhoneNumber = findViewById(R.id.textViewPhone);

                textViewName.setText(result.get("name"));
                textViewAddress.setText(result.get("address"));
                textViewPhoneNumber.setText(result.get("phone_number"));
            }
        }
    }
}
  • Finally, here is the screenshot of our Android application.
Android - MySQL JDBC

Android – MySQL JDBC

The complete source code can be accesses in our GitHub repository here: android-mysql-example.

How to Create a Database in MySQL

Introduction

When you build up an application, you need a database (db) to save your data. It could be about your order, member, or transactional data. It really depends on business needs from the application that you build. Another purpose is you can initiate improvements based on huge data that you’ve already saved.

Based on Wikipedia, a database is an organized collection of data, generally stored and accessed electronically from a computer system. Where databases are more complex they are often developed using formal design and modeling techniques Wikipedia.

There are many great databases these days, one of it is MySQL. In this section, we will learn from the beginning how to create a database, tables, and query data with MySQL.

Why MySQL:

  • It is open source. However, there are a personal and enterprise version.
  • Fast. Of course with the right indexes when you have huge amount of rows data.
  • Scalability, maintainability.
  • Suitable for web-based application. E-commerce, warehouse, logging, and many more.

Before we start, to create or manage your MySQL database, you need database client/IDE.

Three IDE options:

Personally, I find Sequel Pro is very helpful and powerful for my day-to-day use.

Start and Login to MySQL on your local machine (Mac OS X).

  1. Go to your System Preferences
  2. Find MySQL
  3. Choose to Start MySQL Server

After the MySQL database started, you can log in.

  1. Go to your database client, in this example I am using Sequel Pro.
  2. Connect to your localhost. You need to provide the username and password before login.
  3. Once you connect, you will be able to create your database.

Create new Database:

Create Database Statements
CREATE DATABASE database_name
    [[DEFAULT] CHARACTER SET charset_name]
    [[DEFAULT] COLLATE collation_name];

Example:

CREATE DATABASE learning_mysql 
    CHARACTER SET utf8
    COLLATE utf8_general_ci;

Using Functionality Provided by IDE
  • Go to Database menu, select Add Database…

  • Then fill in the database name

For common cases and non latin, use UTF-8 for character set, and you can use utf8_general_ci for the collation.

Your database is now ready to use. Ensure you choose the right database that you want to manage. The second step is to prepare tables as per your business needs, to save the data from your application.

Happy exploring!