tursodatabase/turso · error · java.sql.SQLException
Exception while retrieving number of changes
Error message
Exception while retrieving number of changes
What it means
TursoStatement.changes() asks the native layer (JNI, bindings/java/rs_src/turso_statement.rs:313) for the number of rows changed by the most recent statement execution on this connection. The native method returns -1 when the statement pointer cannot be resolved (zeroed pointer after close, or a native failure), and the Java wrapper at TursoStatement.java:268 converts any -1 into this generic SQLException. Seeing it almost always means the statement handle was invalid at call time, typically because the statement or its connection was already closed.
Solutions
- Read changes() immediately after executeUpdate/step, inside the try (or try-with-resources) block while the statement is still open.
- Guard the call: if (!stmt.isClosed()) { long n = stmt.changes(); } and skip or treat as 0 when closed.
- If you need the value later, cache it in a local variable right after execution instead of querying the statement again.
- For a connection-wide count use totalChanges() on a still-open statement, or open a fresh statement.
- If the statement is verifiably open and it still fails, upgrade the turso Java bindings plus native library - a -1 with a live handle indicates a native-layer defect.
Example fix
// before
try (TursoStatement stmt = conn.prepare("update t set x = 1")) {
stmt.step();
}
long n = stmt.changes(); // statement already closed -> SQLException
// after
try (TursoStatement stmt = conn.prepare("update t set x = 1")) {
stmt.step();
long n = stmt.changes(); // read while handle is alive
return n;
} Defensive patterns
Strategy: try-catch
Validate before calling
if (stmt == null || stmt.isClosed()) {
// handle: statement already closed; skip or treat changes as 0
} else {
long changed = stmt.changes();
} Try / catch
try {
long n = stmt.changes();
} catch (SQLException e) {
if (stmt.isClosed()) {
// closed-handle path: log and skip
} else {
throw e; // unexpected native failure
}
} Prevention
- Read changes() inside the try-with-resources block, immediately after execution.
- Cache the returned value in a local variable instead of re-querying later.
- Never hold statement references across pool recycle boundaries.
- Check isClosed() before calling metadata-style accessors in defensive paths.
When it happens
Trigger: Calling changes() on a TursoStatement after statement.close() has run (close() frees the native pointer); calling it after the parent TursoConnection.close(); reusing a statement object that a connection pool recycled and closed; any native error while reading the change counter that makes the JNI function return -1.
Common situations: try-with-resources blocks where changes() is read after the block ends; update-count reads moved into finally blocks that run after cleanup; wrapper/ORM layers (HikariCP proxy statements, MyBatis) that close or reset statements before the application reads the affected-row count; migration from sqlite-jdbc where lifetime rules differed slightly.
Related errors
- Exception while resetting statement
- Exception while retrieving parameter count
- turso does not support stored procedures
- createBlob not supported
- createClob not supported
AI-assisted analysis of tursodatabase/turso@244cde92a7 (2026-08-20).
Data as JSON: /api/errors/3438041c3f4672ba.
Report an issue: GitHub.
Appendix: source
Thrown at bindings/java/src/main/java/tech/turso/core/TursoStatement.java:268
final long result = totalChanges(statementPointer);
if (result == -1) {
throw new SQLException("Exception while retrieving total number of changes");
}
return result;
}
private native long totalChanges(long statementPointer) throws SQLException;
/**
* Returns number of changes.
*
* @throws SQLException If a database access error occurs
*/
public long changes() throws SQLException {
final long result = changes(statementPointer);
if (result == -1) {
throw new SQLException("Exception while retrieving number of changes");
}
return result;
}
private native long changes(long statementPointer) throws SQLException;
/**
* Returns the number of parameters in this statement. Parameters are the `?`'s that get replaced
* by the provided arguments.
*
* @throws SQLException If a database access error occurs
*/
public int parameterCount() throws SQLException {
final int result = parameterCount(statementPointer);
if (result == -1) {
throw new SQLException("Exception while retrieving parameter count");
}View on GitHub (pinned to 244cde92a7)