tursodatabase/turso · error · java.sql.SQLException

The resultSet is not open

Error message

The resultSet is not open

What it means

next() checks the open flag before stepping; on a closed set it throws this SQLException instead of returning false. The set is closed explicitly by close() and implicitly by next() itself after an invalid native step result. Note the wording differs from the equivalent guards elsewhere in the same class ('The result set is not open' in consumeAll, 'ResultSet closed' in checkOpen), so branching on exact message text is fragile.

Solutions

  1. Guard iteration when the set is shared: while (rs.isOpen() && rs.next())
  2. Give exactly one component ownership of close(); prefer try-with-resources at the outermost caller only
  3. After catching a step() invalid-result error, discard the set and re-execute instead of stepping again

Example fix

// before
while (rs.next()) { /* ... */ } // throws if closed elsewhere

// after
while (rs.isOpen() && rs.next()) { /* ... */ }
// one owner closes unconditionally afterwards
Defensive patterns

Strategy: validation

Validate before calling

while (rs.isOpen() && rs.next()) {
  // process row
}

Try / catch

try {
  while (rs.next()) { /* ... */ }
} catch (SQLException e) {
  // set is dead (auto-closed): stop iterating; re-execute on a fresh statement if needed
}

Prevention

When it happens

Trigger: Calling next() after close(); continuing a while (rs.next()) loop after another thread or helper closed the set; stepping again after a prior invalid-state step auto-closed the set.

Common situations: Utility methods closing result sets they do not own while the outer loop keeps stepping; cancellation/timeout paths racing with iteration; double-processing in retry logic.

Related errors


AI-assisted analysis of tursodatabase/turso@244cde92a7 (2026-08-20). Data as JSON: /api/errors/90ce55f8fcdb9b0a. Report an issue: GitHub.

Appendix: source

Thrown at bindings/java/src/main/java/tech/turso/core/TursoResultSet.java:74

    while (next()) {}
  }

  /**
   * Moves the cursor forward one row from its current position. A {@link TursoResultSet} cursor is
   * initially positioned before the first fow; the first call to the method <code>next</code> makes
   * the first row the current row; the second call makes the second row the current row, and so on.
   * When a call to the <code>next</code> method returns <code>false</code>, the cursor is
   * positioned after the last row.
   *
   * <p>Note that turso only supports <code>ResultSet.TYPE_FORWARD_ONLY</code>, which means that the
   * cursor can only move forward.
   *
   * @return true if the new current row is valid; false if there are no more rows
   * @throws SQLException if a database access error occurs
   */
  public boolean next() throws SQLException {
    if (!open) {
      throw new SQLException("The resultSet is not open");
    }

    if (isEmptyResultSet || pastLastRow) {
      return false; // completed ResultSet
    }

    if (maxRows != 0 && row == maxRows) {
      return false;
    }

    lastStepResult = this.statement.step();
    log.debug("lastStepResult: {}", lastStepResult);
    if (lastStepResult.isRow()) {
      row++;
    }

    if (lastStepResult.isInInvalidState()) {
      open = false;

View on GitHub (pinned to 244cde92a7)