DatabaseMetaData
DatabaseMetaData is a JDBC interface that lets you ask the database itself what it supports and what it contains — all at runtime, without reading any config files or writing database-specific code. You can list every table in a schema, check whether the driver supports stored procedures, discover primary and foreign keys, and much more.
Getting a DatabaseMetaData Object
You retrieve DatabaseMetaData directly from an open Connection:
import java.sql.*;
public class DatabaseMetaDataBasic {
public static void main(String[] args) throws Exception {
String url = "jdbc:mysql://localhost:3306/school";
try (Connection conn = DriverManager.getConnection(url, "root", "secret")) {
DatabaseMetaData meta = conn.getMetaData();
System.out.println("Database: " + meta.getDatabaseProductName());
System.out.println("Version : " + meta.getDatabaseProductVersion());
System.out.println("Driver : " + meta.getDriverName());
System.out.println("JDBC URL: " + meta.getURL());
System.out.println("User : " + meta.getUserName());
}
}
}
Output:
Database: MySQL
Version : 8.0.33
Driver : MySQL Connector/J
JDBC URL: jdbc:mysql://localhost:3306/school
User : root@localhost
Note:
DatabaseMetaDatais a snapshot taken at the momentgetMetaData()is called. It stays valid as long as theConnectionis open; close the connection and the object becomes unusable.
Database and Driver Information
DatabaseMetaData exposes dozens of methods for discovering general information about the database engine and the JDBC driver in use:
| Method | What it returns |
|---|---|
getDatabaseProductName() | Database engine name ("MySQL", "PostgreSQL", …) |
getDatabaseProductVersion() | Engine version string |
getDatabaseMajorVersion() | Major version as int |
getDatabaseMinorVersion() | Minor version as int |
getDriverName() | JDBC driver name |
getDriverVersion() | Driver version string |
getJDBCMajorVersion() | JDBC spec major version the driver implements |
getJDBCMinorVersion() | JDBC spec minor version |
getURL() | Connection URL used |
getUserName() | Connected database user |
getMaxConnections() | Max simultaneous connections (0 = no limit / unknown) |
getDefaultTransactionIsolation() | Default isolation level constant |
Checking Feature Support
Before using an advanced feature, you can ask whether the database actually supports it:
DatabaseMetaData meta = conn.getMetaData();
System.out.println("Supports transactions : " + meta.supportsTransactions());
System.out.println("Supports stored procedures : " + meta.supportsStoredProcedures());
System.out.println("Supports batch updates : " + meta.supportsBatchUpdates());
System.out.println("Supports full outer joins : " + meta.supportsFullOuterJoins());
System.out.println("Is read-only : " + meta.isReadOnly());
Output:
Supports transactions : true
Supports stored procedures : true
Supports batch updates : true
Supports full outer joins : false
Is read-only : false
Tip: These boolean checks are invaluable when writing database-agnostic libraries. Instead of hardcoding “MySQL doesn’t do X”, you ask the driver at runtime.
Listing Catalogs and Schemas
Databases organize objects in a hierarchy: catalog → schema → table. DatabaseMetaData lets you explore each level.
Catalogs
In MySQL a catalog is the same as a database name. In other systems it maps to a top-level namespace:
import java.sql.*;
public class ListCatalogs {
public static void main(String[] args) throws Exception {
try (Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/", "root", "secret")) {
DatabaseMetaData meta = conn.getMetaData();
try (ResultSet catalogs = meta.getCatalogs()) {
System.out.println("Catalogs (databases):");
while (catalogs.next()) {
System.out.println(" " + catalogs.getString("TABLE_CAT"));
}
}
}
}
}
Output:
Catalogs (databases):
information_schema
school
sys
Schemas
Schemas sit one level below catalogs. PostgreSQL uses schemas heavily; MySQL treats them as an alias for catalogs:
try (ResultSet schemas = meta.getSchemas()) {
while (schemas.next()) {
System.out.println(schemas.getString("TABLE_SCHEM")
+ " / " + schemas.getString("TABLE_CAT"));
}
}
Listing Tables
getTables() is one of the most-used methods. It accepts four filter parameters — catalog, schema pattern, table name pattern, and an array of table types — and returns a ResultSet with one row per match:
import java.sql.*;
public class ListTables {
public static void main(String[] args) throws Exception {
String url = "jdbc:mysql://localhost:3306/school";
try (Connection conn = DriverManager.getConnection(url, "root", "secret")) {
DatabaseMetaData meta = conn.getMetaData();
// null wildcards mean "match everything"
try (ResultSet tables = meta.getTables("school", null, "%", new String[]{"TABLE"})) {
System.out.printf("%-30s %-15s%n", "Table Name", "Type");
System.out.println("-".repeat(45));
while (tables.next()) {
System.out.printf("%-30s %-15s%n",
tables.getString("TABLE_NAME"),
tables.getString("TABLE_TYPE"));
}
}
}
}
}
Output:
Table Name Type
---------------------------------------------
courses TABLE
enrollments TABLE
students TABLE
The full list of columns available in the returned ResultSet includes TABLE_CAT, TABLE_SCHEM, TABLE_NAME, TABLE_TYPE, and REMARKS.
Tip: Pass
new String[]{"TABLE", "VIEW"}to include views in the results, ornullto return everything (tables, views, system tables, synonyms, aliases).
Inspecting Columns
getColumns() returns metadata about every column in one or more tables — exactly what you need when building schema migration tools or ORM code generators:
import java.sql.*;
public class ListColumns {
public static void main(String[] args) throws Exception {
String url = "jdbc:mysql://localhost:3306/school";
try (Connection conn = DriverManager.getConnection(url, "root", "secret")) {
DatabaseMetaData meta = conn.getMetaData();
try (ResultSet cols = meta.getColumns("school", null, "students", "%")) {
System.out.printf("%-20s %-15s %-10s %-10s%n",
"Column", "Type", "Size", "Nullable");
System.out.println("-".repeat(60));
while (cols.next()) {
System.out.printf("%-20s %-15s %-10s %-10s%n",
cols.getString("COLUMN_NAME"),
cols.getString("TYPE_NAME"),
cols.getInt("COLUMN_SIZE"),
cols.getString("IS_NULLABLE"));
}
}
}
}
}
Output:
Column Type Size Nullable
------------------------------------------------------------
id INT 11 NO
name VARCHAR 100 YES
grade CHAR 1 YES
created_at DATETIME 19 YES
Primary Keys and Foreign Keys
Primary Keys
try (ResultSet pk = meta.getPrimaryKeys("school", null, "enrollments")) {
while (pk.next()) {
System.out.println("PK column : " + pk.getString("COLUMN_NAME"));
System.out.println("Key name : " + pk.getString("PK_NAME"));
}
}
Output:
PK column : id
Key name : PRIMARY
Foreign Keys
getImportedKeys() tells you which columns in the given table reference another table (i.e., incoming foreign keys from the table’s perspective):
try (ResultSet fk = meta.getImportedKeys("school", null, "enrollments")) {
while (fk.next()) {
System.out.printf("FK: %s.%s → %s.%s%n",
fk.getString("FKTABLE_NAME"),
fk.getString("FKCOLUMN_NAME"),
fk.getString("PKTABLE_NAME"),
fk.getString("PKCOLUMN_NAME"));
}
}
Output:
FK: enrollments.student_id → students.id
FK: enrollments.course_id → courses.id
Use getExportedKeys() for the reverse direction — which other tables reference a given table.
Discovering Indexes
getIndexInfo() reveals every index on a table, including whether it is unique:
try (ResultSet idx = meta.getIndexInfo("school", null, "students", false, false)) {
while (idx.next()) {
System.out.printf("Index: %-20s Column: %-15s Unique: %b%n",
idx.getString("INDEX_NAME"),
idx.getString("COLUMN_NAME"),
!idx.getBoolean("NON_UNIQUE"));
}
}
Output:
Index: PRIMARY Column: id Unique: true
Index: idx_name Column: name Unique: false
Stored Procedures
You can enumerate stored procedures in the database without reading any DDL scripts:
try (ResultSet procs = meta.getProcedures("school", null, "%")) {
while (procs.next()) {
System.out.println("Procedure: " + procs.getString("PROCEDURE_NAME"));
}
}
To inspect the parameters of a specific procedure, use getProcedureColumns().
SQL Keywords and Limits
A few methods help you stay within database-specific limits when building dynamic SQL:
DatabaseMetaData meta = conn.getMetaData();
System.out.println("SQL keywords : " + meta.getSQLKeywords());
System.out.println("Max table name len : " + meta.getMaxTableNameLength());
System.out.println("Max column name len: " + meta.getMaxColumnNameLength());
System.out.println("String functions : " + meta.getStringFunctions());
System.out.println("Numeric functions : " + meta.getNumericFunctions());
getSQLKeywords() returns a comma-separated list of keywords that are not in SQL-92 but are reserved by this database — useful for quoting identifiers correctly.
Under the Hood
DatabaseMetaData is implemented entirely inside the JDBC driver, not in the JDK. When you call conn.getMetaData(), the driver returns its own implementation of the interface (e.g., com.mysql.cj.jdbc.DatabaseMetaDataUsingInfoSchema). Most methods issue a query against the database’s information schema — a set of system views (INFORMATION_SCHEMA.TABLES, INFORMATION_SCHEMA.COLUMNS, etc.) — to answer your questions.
Key points for experienced readers:
- Each call may hit the database. Unlike ResultSetMetaData, which reads data the driver already has from the query response,
DatabaseMetaDatamethod calls likegetTables()andgetColumns()often execute a real SQL query against the information schema. Cache results if you call them in a tight loop. - Pattern arguments use SQL wildcards. The
%wildcard means “match any sequence of characters” and_matches a single character — the same asLIKEin SQL. Passnullto skip filtering on that parameter entirely. - Driver quality varies. The JDBC spec defines all these methods, but some drivers implement only a subset. Always test with your actual database and driver version, especially for methods like
getProcedures()orgetExportedKeys(). - Transaction isolation constants.
getDefaultTransactionIsolation()returns one of theConnection.TRANSACTION_*constants. You can compare this againstConnection.TRANSACTION_READ_COMMITTEDetc. to understand the default behavior before opening any transaction. - Connection is still needed.
DatabaseMetaDatadoes not hold a standalone connection — it references the one you obtained it from. If you close the connection, any pendingResultSetfrom a metadata call becomes invalid.
Warning: Never call
meta.getTables()ormeta.getColumns()inside a hot path (e.g., once per request in a web application). Cache schema information at startup or use a background refresh strategy.
Related Topics
- ResultSetMetaData — column-level metadata for a specific query result, complementing database-level metadata
- ResultSet — iterate over query rows;
DatabaseMetaDatahelps you understand the structure those rows come from - Connection Interface — where you call
getMetaData()from; manages transactions and session state - PreparedStatement — parameterised queries whose input types can be explored via
ParameterMetaData - JDBC Steps — end-to-end walkthrough of connecting to a database and executing queries
- Statement — the basic interface for running SQL; pairs naturally with schema discovery from
DatabaseMetaData