Skip to content
Java jdbc 6 min read

ResultSetMetaData

ResultSetMetaData is a JDBC interface that describes the structure of a ResultSet — how many columns it has, what each column is named, what SQL type it holds, whether values can be NULL, and more. It is especially powerful when you do not know the query shape ahead of time, such as when building a generic database browser, a CSV exporter, or a dynamic reporting tool.

Getting a ResultSetMetaData Object

Every ResultSet carries metadata. Call getMetaData() right after executing your query:

import java.sql.*;

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

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

            ResultSetMetaData meta = rs.getMetaData();

            int columnCount = meta.getColumnCount();
            System.out.println("Number of columns: " + columnCount);

            for (int i = 1; i <= columnCount; i++) {
                System.out.printf("Column %d: %-15s type: %s%n",
                    i,
                    meta.getColumnName(i),
                    meta.getColumnTypeName(i));
            }
        }
    }
}

Output:

Number of columns: 3
Column 1: id              type: INT
Column 2: name            type: VARCHAR
Column 3: grade           type: CHAR

Note: Column indices in ResultSetMetaData are 1-based, just like ResultSet getters.

Core Methods at a Glance

Here are the methods you will reach for most often:

MethodReturnsWhat it tells you
getColumnCount()intTotal number of columns in the result
getColumnName(i)StringThe column name as defined in the table
getColumnLabel(i)StringAlias from AS clause, or column name if none
getColumnTypeName(i)StringDatabase-specific type name (VARCHAR, INT, …)
getColumnType(i)intJDBC type code from java.sql.Types
getColumnDisplaySize(i)intMax characters needed to display the value
getPrecision(i)intDecimal digits (numeric types) or char length
getScale(i)intDigits to the right of the decimal point
getTableName(i)StringTable the column came from (driver-dependent)
getSchemaName(i)StringSchema name (driver-dependent)
getCatalogName(i)StringCatalog/database name (driver-dependent)
isNullable(i)int0 = no null, 1 = nullable, 2 = unknown
isAutoIncrement(i)booleanAuto-increment / identity column?
isCaseSensitive(i)booleanIs string comparison case-sensitive?
isReadOnly(i)booleanCannot be updated through the ResultSet?
isWritable(i)booleanMay be updated through the ResultSet?
isSigned(i)booleanHolds signed numeric values?

getColumnName vs getColumnLabel

These two trip people up when using AS aliases in SQL:

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

ResultSetMetaData meta = rs.getMetaData();

System.out.println(meta.getColumnName(1));   // id          (original column)
System.out.println(meta.getColumnLabel(1));  // student_id  (alias)

Output:

id
student_id

Tip: Always prefer getColumnLabel() when building UI headers or CSV files — it respects any AS aliases your query defines, which is what the end user expects to see.

Checking Nullability

The isNullable(i) method returns one of three integer constants defined in ResultSetMetaData itself:

import java.sql.*;

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

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

            ResultSetMetaData meta = rs.getMetaData();

            for (int i = 1; i <= meta.getColumnCount(); i++) {
                int nullable = meta.isNullable(i);
                String status = switch (nullable) {
                    case ResultSetMetaData.columnNoNulls     -> "NOT NULL";
                    case ResultSetMetaData.columnNullable    -> "NULLABLE";
                    case ResultSetMetaData.columnNullableUnknown -> "UNKNOWN";
                    default -> "?";
                };
                System.out.println(meta.getColumnLabel(i) + " => " + status);
            }
        }
    }
}

Output:

id    => NOT NULL
name  => NULLABLE
grade => NULLABLE

Real-World Use: Generic ResultSet Printer

Here is a practical utility that prints any query result as a neatly aligned table — without knowing the schema in advance:

import java.sql.*;

public class GenericResultPrinter {

    public static void printResult(ResultSet rs) throws SQLException {
        ResultSetMetaData meta = rs.getMetaData();
        int cols = meta.getColumnCount();

        // Print header row
        for (int i = 1; i <= cols; i++) {
            System.out.printf("%-20s", meta.getColumnLabel(i));
        }
        System.out.println();
        System.out.println("-".repeat(20 * cols));

        // Print data rows
        while (rs.next()) {
            for (int i = 1; i <= cols; i++) {
                Object val = rs.getObject(i);
                System.out.printf("%-20s", val == null ? "NULL" : val);
            }
            System.out.println();
        }
    }

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

        try (Connection conn = DriverManager.getConnection(url, "root", "secret");
             Statement stmt = conn.createStatement();
             ResultSet rs   = stmt.executeQuery("SELECT id, name, grade FROM students")) {
            printResult(rs);
        }
    }
}

Output:

id                  name                grade
------------------------------------------------------------
1                   Alice               A
2                   Bob                 B
3                   Carol               A

Because printResult is driven entirely by ResultSetMetaData, it works with any query you pass — tables, joins, views, stored procedure outputs.

Mapping JDBC Types to Java Types

getColumnType(i) returns an integer constant from java.sql.Types. You can use it to pick the right getter at runtime:

import java.sql.*;

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

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

            ResultSetMetaData meta = rs.getMetaData();

            while (rs.next()) {
                for (int i = 1; i <= meta.getColumnCount(); i++) {
                    int type = meta.getColumnType(i);
                    Object value = switch (type) {
                        case Types.INTEGER, Types.SMALLINT, Types.TINYINT -> rs.getInt(i);
                        case Types.BIGINT  -> rs.getLong(i);
                        case Types.DOUBLE, Types.FLOAT  -> rs.getDouble(i);
                        case Types.DECIMAL, Types.NUMERIC -> rs.getBigDecimal(i);
                        case Types.BOOLEAN -> rs.getBoolean(i);
                        default            -> rs.getString(i);
                    };
                    System.out.printf("%s = %s  ", meta.getColumnLabel(i), value);
                }
                System.out.println();
            }
        }
    }
}

This pattern is the foundation of ORMs, query builders, and testing frameworks that need to handle arbitrary schemas.

Building a CSV Exporter

ResultSetMetaData makes writing a generic CSV exporter straightforward:

import java.sql.*;
import java.io.*;

public class CsvExporter {

    public static void exportToCsv(ResultSet rs, PrintWriter writer) throws SQLException {
        ResultSetMetaData meta = rs.getMetaData();
        int cols = meta.getColumnCount();

        // Write header
        for (int i = 1; i <= cols; i++) {
            writer.print(meta.getColumnLabel(i));
            if (i < cols) writer.print(",");
        }
        writer.println();

        // Write rows
        while (rs.next()) {
            for (int i = 1; i <= cols; i++) {
                String val = rs.getString(i);
                writer.print(val == null ? "" : val.replace(",", ";"));
                if (i < cols) writer.print(",");
            }
            writer.println();
        }
    }
}

Pass any ResultSet and a PrintWriter wrapping your output file, and this method handles the rest — headers and all.

Under the Hood

When your JDBC driver executes a query, the database server sends back a result set descriptor before the first data row. This descriptor lists every column: its name, SQL type code, precision, scale, nullability flag, table origin, and more. The driver wraps this wire-level descriptor in an object that implements ResultSetMetaData.

Key points for experienced readers:

  • No extra round-trip. The metadata arrives with the query response. Calling rs.getMetaData() is essentially free — it reads from in-memory data the driver already received.
  • Driver quality matters. Methods like getTableName(), getSchemaName(), and getCatalogName() are marked optional in the JDBC spec. Some drivers return an empty string; others return accurate data. Always test with your specific driver.
  • getColumnType() vs getColumnTypeName(). The integer code from Types is portable across databases. The string from getColumnTypeName() is database-specific — MySQL returns "INT", PostgreSQL returns "int4" for the same concept. Use the Types constant when you need cross-database logic.
  • Prepared statement metadata. You can also call PreparedStatement.getMetaData() before executing, if the driver supports it. This lets you inspect expected result columns at preparation time, not just at execution time.
  • ParameterMetaData. For PreparedStatement inputs (the ? parameters), there is a sibling interface ParameterMetaData retrieved via ps.getParameterMetaData(). It tells you the type and nullability of each input parameter, which is useful for generic query-building frameworks.

Warning: Never cache a ResultSetMetaData reference after closing the ResultSet. The underlying data may be released, and the reference becomes invalid.

  • ResultSet — navigate and read the actual data rows; ResultSetMetaData describes their shape
  • DatabaseMetaData — database-level metadata: tables, schemas, supported features, and driver capabilities
  • PreparedStatement — execute parameterised SQL safely; exposes getMetaData() and getParameterMetaData()
  • Statement — the base interface for executing queries that produce a ResultSet
  • JDBC Steps — the end-to-end process for connecting and querying a database
  • Reflection API — another layer of runtime introspection, this time for Java classes rather than SQL schemas
Last updated June 13, 2026
Was this helpful?