tursodatabase/turso · error · SQLException
Statement is closed
Error message
Statement is closed
What it means
ensureOpen() is the shared guard at the top of JDBC4Statement's executeQuery, execute, getResultSet, addBatch, clearBatch and executeBatch (executeUpdate reaches it through execute). Once the `closed` flag is set it throws SQLException("Statement is closed"). Statements are closed both by an explicit close() and when their connection is closed, so the original close is often not visible at the failure site.
Solutions
- Check stmt.isClosed() before use, or recreate the statement per unit of work
- Create statements with try-with-resources scoped to one task; let the pool manage connections only
- Keep the connection open for the statement's whole lifetime and verify conn.isValid() when a pool is involved
- Catch the SQLException and treat 'Statement is closed' as a signal to rebuild the statement and retry once
Example fix
// before
private final Statement stmt; // cached field
public List<User> users() throws SQLException {
return query(stmt.executeQuery("SELECT ...")); // fails after first close
}
// after
public List<User> users() throws SQLException {
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT ...")) {
return query(rs);
}
} Defensive patterns
Strategy: validation
Validate before calling
if (stmt.isClosed()) {
stmt = conn.createStatement(); // recreate and reconfigure as needed
}
ResultSet rs = stmt.executeQuery(sql); Try / catch
try {
return stmt.executeQuery(sql);
} catch (SQLException e) {
if (e.getMessage() != null && e.getMessage().contains("Statement is closed")) {
try (Statement fresh = conn.createStatement()) {
return fresh.executeQuery(sql); // rebuild and retry once
}
}
throw e;
} Prevention
- Scope statements to one unit of work with try-with-resources; never cache them in fields
- Remember connection.close() cascades to its statements — check conn.isValid() when pooling
- Verify statement and connection state in long-lived background jobs before each use
When it happens
Trigger: Reusing a statement after close(); execute/addBatch/executeBatch on a statement whose connection was closed (connection close cascades to its statements); statements stored in fields or app-level caches that outlive their lifespan; double-processing a request where the second pass hits an already-closed statement.
Common situations: App-level statement caching in servlets or service singletons; background jobs holding statements while a connection pool reclaims connections (idle timeout, eviction on validation failure); helper classes that hide statement creation but expose execute methods; tests that close resources early.
Related errors
- statement is closed
- Invalid argument
- Query timeout must be greater than 0
- Cannot change isolation level while transaction is active.
- createBlob not supported
AI-assisted analysis of tursodatabase/turso@244cde92a7 (2026-08-20).
Data as JSON: /api/errors/fb71198eed972681.
Report an issue: GitHub.
Appendix: source
Thrown at bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Statement.java:500
/**
* Functional interface for SQL callable operations.
*
* @param <T> the return type
*/
@FunctionalInterface
protected interface SQLCallable<T> {
/**
* Executes the SQL operation.
*
* @return the result of the operation
* @throws SQLException if a database access error occurs
*/
T call() throws SQLException;
}
private void ensureOpen() throws SQLException {
if (closed) {
throw new SQLException("Statement is closed");
}
}
}
View on GitHub (pinned to 244cde92a7)