JDBC Exception Handling

Almost every JDBC method — connecting, preparing a statement, running a query — can throw a SQLException. It's a checked exception, which means the Java compiler forces you to either catch it or declare it with throws on your method. This page covers what's inside a SQLException, the modern (and much safer) way to close JDBC resources, and how batch operations can report more than one failure at once.

SQLException: What's Inside It

A SQLException carries more than just a message. Three pieces of information are especially useful when something goes wrong:

MethodWhat it gives you
getMessage()A human-readable description of what went wrong.
getSQLState()A standard 5-character code identifying the class of error (e.g. constraint violation, syntax error).
getErrorCode()A database-vendor-specific numeric error code (MySQL, Oracle, etc. each define their own).
try {
  Connection conn = DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
  System.out.println("Message: " + e.getMessage());
  System.out.println("SQLState: " + e.getSQLState());
  System.out.println("Error code: " + e.getErrorCode());
}

The Old Way: Closing in a finally Block

Before Java 7, closing JDBC resources meant a finally block with a null check for every single resource — Connection, Statement, and ResultSet each needed their own guarded close() call, and they had to be closed in the right order (result set first, then statement, then connection).

Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
  conn = DriverManager.getConnection(url, user, password);
  stmt = conn.createStatement();
  rs = stmt.executeQuery("SELECT * FROM users");
  // ... use rs ...
} finally {
  if (rs != null) rs.close();
  if (stmt != null) stmt.close();
  if (conn != null) conn.close();
}
Why this is risky: it's easy to forget one close() call, close resources in the wrong order, or have a close() itself throw and skip the rest. Any of those quietly leaks a database connection.

The Modern Way: try-with-resources

Connection, PreparedStatement, and ResultSet all implement AutoCloseable. That means you can declare all three in a single try-with-resources header, and Java closes them automatically — in the reverse order they were opened — even if an exception is thrown partway through.

try (
  Connection conn = DriverManager.getConnection(url, user, password);
  PreparedStatement ps = conn.prepareStatement("SELECT id, name FROM users WHERE active = 1");
  ResultSet rs = ps.executeQuery()
) {
  while (rs.next()) {
    System.out.println(rs.getInt("id") + " - " + rs.getString("name"));
  }
} catch (SQLException e) {
  System.err.println("Query failed: " + e.getMessage());
}
// rs, then ps, then conn are all closed automatically here, in that order
No finally block, no null checks, no risk of forgetting a resource. The compiler-generated cleanup code closes rs first, then ps, then conn — the exact reverse of the order they were declared.

Chained Exceptions in Batch Operations

A single SQLException only tells you about one failure. But when you run a batch of statements (see Batch Processing), more than one statement in the batch can fail. JDBC lets a SQLException carry a chain of additional exceptions, accessible with getNextException().

catch (SQLException e) {
  while (e != null) {
    System.err.println(e.getMessage());
    e = e.getNextException();
  }
}

Walking this chain is how you find out exactly which rows in a batch failed, instead of just knowing that "something" in the batch went wrong.

Next up: Opening a new Connection for every request is expensive. Connection Pooling shows you how real applications reuse connections instead.