Quick answer
'Unable to retrieve JDBC result set' means the query reached the database but no rows came back to the driver — usually an unqualified or wrong-case table name, the reserved word TABLE used unquoted, a missing SELECT privilege, or a Statement/Connection that was already closed. Read the nested SQLException, run the raw SQL as the same user, and fix the specific cause.
Short answer: The message means the query reached the database but no rows came back to the driver — usually an unqualified or wrong-case table name, the reserved word TABLE used unquoted, a missing SELECT privilege, or a Statement/Connection already closed. Don't guess: read the nested SQLException, run the raw SQL as the same user, and fix the specific cause it names.
"Unable to retrieve JDBC result set for SELECT * FROM TABLE" is one of the least helpful messages the JDBC layer produces. It tells you the query made it to the database but nothing usable came back — and then hides the actual reason inside a wrapped exception. The fix is never "try random things"; it's to read the real error and match it to one of a handful of concrete causes.
What the error actually means
JDBC separates sending a query from retrieving rows. This message fires on the retrieval side: the driver asked the database to run your statement and hand back a ResultSet, and that failed. The tool you're using — a raw JDBC call, Spark, Hibernate, DBeaver, a BI connector — wraps the database's original SQLException in its own generic text. So the first move is always the same: stop reading the wrapper and find the cause.
catch (SQLException e) {
// Walk the entire chain — the useful message is usually the last one.
for (Throwable t = e; t != null; t = t.getCause()) {
System.err.println(t.getClass().getSimpleName() + ": " + t.getMessage());
}
// For SQLException specifically, also walk getNextException():
SQLException next = e;
while ((next = next.getNextException()) != null) {
System.err.println("SQL cause: " + next.getMessage() + " [SQLState " + next.getSQLState() + "]");
}
}Whatever prints last is what you actually fix.
The four causes, and how to tell them apart
1. The table name can't be resolved
By far the most common cause. The database can't find the object because of schema qualification, case sensitivity, or the wrong catalog:
- Qualification:
orderslives in schemasales, but your JDBC session'ssearch_path(Postgres) or default schema doesn't includesales. It resolves in your GUI client (different session settings) but not from Java. - Case: PostgreSQL folds unquoted identifiers to lowercase. If the table was created as
"Orders"(quoted, capital O), thenSELECT * FROM Orderslooks forordersand fails. You must query"Orders". - Catalog: On MySQL/SQL Server the connection is pointed at the wrong database.
The tell: the nested error says relation "..." does not exist, Table '...' doesn't exist, or Invalid object name '...'.
2. TABLE is a reserved word
SELECT * FROM TABLE — with the literal word TABLE — is a syntax error, because TABLE is reserved in every major SQL dialect. The parser sees a keyword where it wants an identifier. (This bites people who genuinely named a column or table with a reserved word, or who copied a placeholder query verbatim.) The tell: a syntax error near TABLE.
3. The connection's user lacks SELECT
The table exists and resolves, but the JDBC account has no SELECT privilege on it. Some drivers and tools report this cleanly ("permission denied"); others mask it as the generic retrieval failure. The tell: permission denied for table ..., SELECT command denied to user ..., or The SELECT permission was denied.
4. The Statement or Connection is already closed
You're calling executeQuery() on a Statement whose Connection was returned to the pool, or reading a ResultSet after its Statement was closed in a finally block. A ResultSet is only valid while the Statement that created it is open. The tell: Connection is closed, Statement is closed, ResultSet is closed, or Operation not allowed after ResultSet closed.
Diagnose it in 60 seconds
Run the exact SQL in a database client while logged in as the same user your application uses:
- It works there too → your Java code is the problem: different schema/catalog, a closed connection, or
executeQuery()used on a non-SELECTstatement. - It fails there the same way → the SQL or the grants are the problem: reserved word, wrong name/case, or missing
SELECT.
That single test collapses six possibilities into two.
The fixes
Qualify and (if needed) quote the identifier. Always name the schema in application code; quote only when the object really uses mixed case or a reserved word:
-- PostgreSQL / Oracle
SELECT * FROM sales."Orders";
-- MySQL
SELECT * FROM sales.`order`;
-- SQL Server
SELECT * FROM sales.[dbo].[Order];Grant the privilege (least privilege, but grant what the app needs):
GRANT SELECT ON sales.orders TO app_user;Never read from a closed statement. Use try-with-resources so the ResultSet, Statement, and Connection close in the right order, only after you've consumed the rows:
String sql = "SELECT id, total FROM sales.orders WHERE status = ?";
try (Connection cx = dataSource.getConnection();
PreparedStatement ps = cx.prepareStatement(sql)) {
ps.setString(1, "paid");
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
// read every row you need HERE, before the try block closes rs.
}
}
}Use the right execute method. executeQuery() is only for statements that return rows. For INSERT/UPDATE/DELETE use executeUpdate(); if you don't know, use execute() and check getResultSet() vs getUpdateCount().
How to prevent it
- Schema-qualify everything in application SQL. Relying on
search_pathor the default catalog is how "works on my machine" bugs reach production. - Standardize identifier casing. Pick lower_snake_case and never quote — then case never surprises you.
- Verify grants at deploy time, not at first request. A smoke-test query as the app user catches missing privileges before users do.
- Always try-with-resources. Most "closed result set" errors are lifecycle bugs that this pattern makes impossible.
- Log the full exception chain. The generic wrapper wastes hours; the nested
SQLExceptionand itsSQLStatetell you exactly which of the four causes you have.
Match the nested error to the cause, apply the matching fix, and this stops being a mystery.
Sources
Key takeaways
- •The message is a wrapper — the real error is in the nested 'Caused by' SQLException. Always read the full stack trace first.
- •`SELECT * FROM TABLE` fails on most engines because TABLE is a reserved word; quote the identifier ("TABLE", `table`, [TABLE]).
- •Reproduce by running the exact SQL in a DB client logged in as the same user — that instantly separates a permissions problem from a syntax or naming problem.
- •Schema-qualify every table in application code (schema.table); never depend on search_path or the session's current catalog.
- •Wrap Connection, Statement, and ResultSet in try-with-resources so you never read from a closed statement.
Frequently asked questions
Why does SELECT * FROM TABLE fail but SELECT * FROM my_table works?
TABLE is a reserved SQL keyword. Used bare as an identifier it's a syntax error on PostgreSQL, MySQL, SQL Server, and Oracle. Quote it to force identifier interpretation — "TABLE" on Postgres/Oracle, `TABLE` on MySQL, [TABLE] on SQL Server — or, better, rename the object so you never have to.
The SQL runs in my GUI client but fails from Java. Why?
Your Java connection is almost certainly using a different user, schema/search_path, or catalog than your GUI session. The table resolves in one context and not the other. Fully schema-qualify the table name and confirm the JDBC user has SELECT on it.
How do I see the real error instead of the generic wrapper?
Log the whole exception chain. In Java, walk e.getCause() (or getNextException() for SQLException) and print each level. Tools like Spark or DBeaver bury the driver's original SQLException under their own wrapper — the useful message is several levels down.
Can a closed connection cause this error?
Yes. Calling executeQuery() on a closed Statement or Connection, or reading a ResultSet after the Statement that produced it was closed (common with connection pools and try/finally that closes too early), surfaces as a failure to retrieve the result set. Use try-with-resources and keep the ResultSet's Statement open until you're done reading.
Software Engineering Leader & Technical Author · Updated September 9, 2026