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

  1. Read changes() immediately after executeUpdate/step, inside the try (or try-with-resources) block while the statement is still open.
  2. Guard the call: if (!stmt.isClosed()) { long n = stmt.changes(); } and skip or treat as 0 when closed.
  3. If you need the value later, cache it in a local variable right after execution instead of querying the statement again.
  4. For a connection-wide count use totalChanges() on a still-open statement, or open a fresh statement.
  5. 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

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


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)