tursodatabase/turso · error · SQLException

SQLite only supports TYPE_FORWARD_ONLY cursors

Error message

SQLite only supports TYPE_FORWARD_ONLY cursors

What it means

TursoConnection.checkCursor() validates the ResultSet hints passed to Connection.createStatement(type, concurrency, holdability) or prepareStatement(...) overloads. The native engine streams rows one at a time via step(), so a cursor can only move forward and any resultSetType other than ResultSet.TYPE_FORWARD_ONLY is rejected up front.

Source

Thrown at bindings/java/src/main/java/tech/turso/core/TursoConnection.java:160

  // TODO: check whether this is still valid for turso
  /**
   * Checks whether the type, concurrency, and holdability settings for a {@link ResultSet} are
   * supported by the SQLite interface. Supported settings are:
   *
   * <ul>
   *   <li>type: {@link ResultSet#TYPE_FORWARD_ONLY}
   *   <li>concurrency: {@link ResultSet#CONCUR_READ_ONLY})
   *   <li>holdability: {@link ResultSet#CLOSE_CURSORS_AT_COMMIT}
   * </ul>
   *
   * @param resultSetType the type setting.
   * @param resultSetConcurrency the concurrency setting.
   * @param resultSetHoldability the holdability setting.
   */
  public void checkCursor(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
      throws SQLException {
    if (resultSetType != ResultSet.TYPE_FORWARD_ONLY) {
      throw new SQLException("SQLite only supports TYPE_FORWARD_ONLY cursors");
    }
    if (resultSetConcurrency != ResultSet.CONCUR_READ_ONLY) {
      throw new SQLException("SQLite only supports CONCUR_READ_ONLY cursors");
    }
    if (resultSetHoldability != ResultSet.CLOSE_CURSORS_AT_COMMIT) {
      throw new SQLException("SQLite only supports closing cursors at commit");
    }
  }

  /**
   * Sets the auto-commit mode for this connection.
   *
   * <p>When auto-commit is enabled (the default), each SQL statement is committed automatically
   * upon completion. When auto-commit is disabled, statements are grouped into transactions that
   * must be explicitly committed or rolled back.
   *
   * <p>If this method is called to enable auto-commit while a transaction is active, the current
   * transaction is committed first.

View on GitHub (pinned to bad083fafb)

Solutions

  1. Pass ResultSet.TYPE_FORWARD_ONLY as the resultSetType argument
  2. Remove scrolling calls (previous(), absolute(), relative(), afterLast()) and iterate with next() only
  3. For pagination, re-execute the query with LIMIT/OFFSET instead of scrolling a cursor
  4. If rows must be re-read, buffer them into a List while iterating forward once

Example fix

// before
Statement st = conn.createStatement(
    ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);

// after
Statement st = conn.createStatement(
    ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
Defensive patterns

Strategy: validation

Validate before calling

int type = ResultSet.TYPE_FORWARD_ONLY;
int concurrency = ResultSet.CONCUR_READ_ONLY;
int holdability = ResultSet.CLOSE_CURSORS_AT_COMMIT;
if (type != ResultSet.TYPE_FORWARD_ONLY) {
  type = ResultSet.TYPE_FORWARD_ONLY; // downgrade before the driver rejects it
}
Statement st = conn.createStatement(type, concurrency, holdability);

Type guard

static boolean isSupportedCursorType(int resultSetType) {
  return resultSetType == ResultSet.TYPE_FORWARD_ONLY;
}

Try / catch

try {
  st = conn.createStatement(reqType, reqConcurrency, reqHoldability);
} catch (SQLException e) {
  if (e.getMessage() != null && e.getMessage().contains("TYPE_FORWARD_ONLY")) {
    st = conn.createStatement(); // driver defaults are always supported
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Calling conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ...) or conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_SENSITIVE, ...) with any type constant other than ResultSet.TYPE_FORWARD_ONLY (1003). The no-arg createStatement()/prepareStatement() never trigger it.

Common situations: Porting JDBC code from MySQL/PostgreSQL drivers that accept scrollable cursors; UI grid or pagination components that call rs.absolute()/rs.previous(); libraries and copy-pasted boilerplate that hardcode TYPE_SCROLL_INSENSITIVE; migration from sqlite-jdbc tests that exercise scroll types.

Related errors


AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16). Data as JSON: /api/errors/c451af4dd2b06f5b. Report an issue: GitHub.