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
- Pass ResultSet.TYPE_FORWARD_ONLY as the resultSetType argument
- Remove scrolling calls (previous(), absolute(), relative(), afterLast()) and iterate with next() only
- For pagination, re-execute the query with LIMIT/OFFSET instead of scrolling a cursor
- 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
- Prefer the no-arg createStatement()/prepareStatement(sql) — the driver applies supported defaults and checkCursor never runs
- Audit ported JDBC code for TYPE_SCROLL_* constants before switching drivers
- Do not use scroll APIs (absolute, previous, relative); design iteration as forward-only
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
- SQLite only supports CONCUR_READ_ONLY cursors
- SQLite only supports closing cursors at commit
- The result set is not open
- The resultSet is not open
- step() returned invalid result: " + errorMessage
AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16).
Data as JSON: /api/errors/c451af4dd2b06f5b.
Report an issue: GitHub.