pentaho/pentaho-kettle · error · KettleDatabaseException

Unable to move resultset to after the last position

Error message

Unable to move resultset to after the last position

What it means

KettleDatabaseException wrapper for ResultSet.afterLast() failures. afterLast() repositions the cursor after the final row, which requires a scrollable result set; the underlying SQLException is attached as cause.

Solutions

  1. Create the Statement with a scrollable result set type
  2. Use a COUNT(*) query instead of seeking afterLast to size the result set
  3. Check rs.getType() before cursor navigation calls
  4. Keep the result set/connection open for the duration of navigation

Example fix

// before
Statement st = conn.createStatement();
db.afterLast(rs);
// after
Statement st = conn.createStatement(
  ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
rs = st.executeQuery(sql);
Defensive patterns

Strategy: validation

Validate before calling

if (rs.getType() == ResultSet.TYPE_FORWARD_ONLY) {
  throw new IllegalStateException("afterLast() needs a scrollable result set");
}

Type guard

null

Try / catch

try {
  db.afterLast(rs);
} catch (KettleDatabaseException e) {
  // fallback: use COUNT(*) query to size result instead
}

Prevention

When it happens

Trigger: Calling Database.afterLast(rs) on a TYPE_FORWARD_ONLY result set or one whose driver doesn't support afterLast(); result set already closed.

Common situations: Trying to count rows by jumping to the end on a default statement; iterating backwards after afterLast() on drivers with limited scroll support.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of pentaho/pentaho-kettle@f3058517a1 (2026-09-13). Data as JSON: /api/errors/b7d920b28e9f6dd7. Report an issue: GitHub.

Appendix: source

Thrown at core/src/main/java/org/pentaho/di/core/database/Database.java:2961

      return rs.absolute( position );
    } catch ( SQLException e ) {
      throw new KettleDatabaseException( "Unable to move resultset to position " + position, e );
    }
  }

  public boolean relative( ResultSet rs, int rows ) throws KettleDatabaseException {
    try {
      return rs.relative( rows );
    } catch ( SQLException e ) {
      throw new KettleDatabaseException( "Unable to move the resultset forward " + rows + " rows", e );
    }
  }

  public void afterLast( ResultSet rs ) throws KettleDatabaseException {
    try {
      rs.afterLast();
    } catch ( SQLException e ) {
      throw new KettleDatabaseException( "Unable to move resultset to after the last position", e );
    }
  }

  public void first( ResultSet rs ) throws KettleDatabaseException {
    try {
      rs.first();
    } catch ( SQLException e ) {
      throw new KettleDatabaseException( "Unable to move resultset to the first position", e );
    }
  }

  /**
   * Get a row from the resultset. Do not use lazy conversion
   *
   * @param rs The resultset to get the row from
   * @return one row or null if no row was found on the resultset or if an error occurred.
   */
  public Object[] getRow( ResultSet rs ) throws KettleDatabaseException {

View on GitHub (pinned to f3058517a1)