ResultSet

Every SELECT you run through JDBC hands back a ResultSet. It's easy to treat it like a list of rows, but it actually behaves more like a cursor moving through the results one row at a time. Understanding that shape avoids some of the most common JDBC beginner mistakes.

ResultSet Is a Cursor, Not a List

When a query first returns, the cursor is positioned before the first row — not on it. Calling rs.next() moves the cursor forward one row and returns true if there was a row to move to, or false once you've run past the last row. You must call next() before reading any column, even if you only expect one row back.

ResultSet rs = stmt.executeQuery("SELECT id, name FROM students");

while (rs.next()) { // moves cursor forward, false when done
  int id = rs.getInt("id");
  String name = rs.getString("name");
  System.out.println(id + ": " + name);
}
Common mistake: Calling rs.getString(...) before ever calling rs.next() throws an exception, because the cursor isn't on a row yet.

The getXxx() Methods

ResultSet has a matching getter for each SQL type. Use the one that matches the column's actual type.

MethodUsed for
getString(col)VARCHAR, TEXT, CHAR columns
getInt(col)Whole numbers (INT)
getDouble(col)Decimal numbers (DOUBLE, FLOAT)
getDate(col)DATE columns, returned as java.sql.Date
getBoolean(col)BOOLEAN / TINYINT(1) columns
while (rs.next()) {
  String name = rs.getString("name");
  double fees = rs.getDouble("fees");
  Date joined = rs.getDate("joined_on");
  boolean active = rs.getBoolean("is_active");
}

Column Name vs. Column Index

Every getter method comes in two flavors: pass a column name (String) or pass a column position (int). Both work the same way underneath, but there's an important gotcha — column indexes in JDBC are 1-based, not 0-based. The first column is index 1, not 0.

// by name - clearer, resists column reordering
String name = rs.getString("name");

// by index - 1-based! index 1 is the FIRST column, not 0
String sameName = rs.getString(1);

Reading by name is usually preferred because it keeps working even if the column order in the SELECT changes. Reading by index can be marginally faster, but only if you remember that index 1 means the first column — a mistake here is a very common source of off-by-one bugs for developers coming from 0-based languages.

Handling SQL NULL Values Safely

A database column can hold SQL NULL, which doesn't map cleanly onto Java primitives like int or double. If a column is NULL and you call a primitive getter like getInt(), JDBC returns 0 instead of throwing — which is indistinguishable from an actual stored 0 unless you check further.

Two ways to detect a real NULL safely:

// Option 1: getObject() returns a real null for NULL columns
Object value = rs.getObject("age");
if (value == null) {
  System.out.println("age is NULL");
}

// Option 2: call wasNull() right after a primitive getter
int age = rs.getInt("age");
if (rs.wasNull()) {
  System.out.println("age is NULL, not really 0");
}

ResultSetMetaData: Inspecting Columns Dynamically

Sometimes you don't know the shape of the query ahead of time — for example, a generic table-viewer tool that runs arbitrary SELECT statements. ResultSetMetaData, obtained from rs.getMetaData(), lets you inspect the column count, names, and types at runtime.

ResultSetMetaData meta = rs.getMetaData();
int columnCount = meta.getColumnCount();

for (int i = 1; i <= columnCount; i++) {
  System.out.println(meta.getColumnName(i) + " -> " + meta.getColumnTypeName(i));
}

Scrollable and Updatable ResultSets

By default, a ResultSet only moves forward with next() and can't be edited. JDBC also supports scrollable result sets (you can move backward, jump to a row, or go back to the start) and updatable result sets (you can edit a row and push the change back to the database). These are created by passing extra arguments to createStatement():

Statement stmt = conn.createStatement(
  ResultSet.TYPE_SCROLL_INSENSITIVE,
  ResultSet.CONCUR_UPDATABLE);

This is a more advanced, less commonly needed feature — just know it exists for cases where forward-only, read-only reading isn't enough.

Next steps: Head to Transactions to learn how to group several statements so they all succeed or all fail together.