tursodatabase/turso · error · java.sql.SQLException
step() returned null, which is only returned when an error…
Error message
step() returned null, which is only returned when an error occurs
What it means
TursoStatement.step() wraps the native step(long) and treats a null return as an error, because the JNI contract returns null only on failure. Unlike the ResultSet 'invalid result' errors, no result object or engine message accompanies it — this is a bare 'native call failed while advancing the statement'. Root causes are engine-level failures or stepping a statement whose pointer is stale (already closed, finalized, or reset underneath you).
Solutions
- Catch SQLException at the iteration boundary and re-prepare the statement once on a fresh handle if the operation is idempotent
- Verify the statement and connection are still open before stepping, and never step concurrently from two threads
- If the failure repeats deterministically, check the database file and disk health with the sqlite3 CLI
Example fix
// before
while (rs.next()) { /* rows */ } // 'step() returned null...' mid-loop
// after
try {
while (rs.next()) { /* rows */ }
} catch (SQLException e) {
stmt.close();
stmt = conn.prepareStatement(sql); // statement unusable: re-prepare, retry once
rs = stmt.executeQuery(sql);
} Defensive patterns
Strategy: try-catch
Try / catch
try {
while (rs.next()) { /* ... */ }
} catch (SQLException e) {
// null native return: the statement is unusable
stmt.close();
stmt = conn.prepareStatement(sql); // re-prepare and re-execute once
} Prevention
- Close statements before their connection, never after
- Never step a statement concurrently from two threads
- Bound retries to one re-prepare; persistent failure means an engine or environment problem
When it happens
Trigger: Stepping after the statement was closed; stepping while another thread closes the connection or finalizes the statement; an engine I/O failure during iteration; native memory exhaustion.
Common situations: Lifecycle races with pooled connections; statements kept alive longer than their database handle; iterating large result sets while a timeout handler tears the connection down.
Related errors
- Exception while retrieving total number of changes
- Unable to load necessary native library
- ARM64 architecture is not supported on Linux yet
- Exception while resetting statement
- Exception while retrieving number of changes
AI-assisted analysis of tursodatabase/turso@244cde92a7 (2026-08-20).
Data as JSON: /api/errors/5beebae9da84e915.
Report an issue: GitHub.
Appendix: source
Thrown at bindings/java/src/main/java/tech/turso/core/TursoStatement.java:53
public TursoResultSet getResultSet() {
return resultSet;
}
/**
* Expects a clean statement created right after prepare method is called.
*
* @return true if the ResultSet has at least one row; false otherwise.
*/
public boolean execute() throws SQLException {
resultSet.next();
return resultSet.hasLastStepReturnedRow();
}
TursoStepResult step() throws SQLException {
final TursoStepResult result = step(this.statementPointer);
if (result == null) {
throw new SQLException("step() returned null, which is only returned when an error occurs");
}
return result;
}
/**
* Because turso supports async I/O, it is possible to return a {@link TursoStepResult} with
* {@link TursoStepResult#STEP_RESULT_ID_ROW}. However, this is handled by the native side, so you
* can expect that this method will not return a {@link TursoStepResult#STEP_RESULT_ID_ROW}.
*/
@Nullable
private native TursoStepResult step(long stmtPointer) throws SQLException;
/**
* Throws formatted SQLException with error code and message.
*
* @param errorCode Error code.
* @param errorMessageBytes Error message.View on GitHub (pinned to 244cde92a7)