tursodatabase/turso · error · SQLException

Exception while retrieving number of changes

Error message

Exception while retrieving number of changes

What it means

The Java wrapper throws this when the native changes() call returns -1. The native code returns -1 exclusively when the statement pointer fails to resolve to a live statement, meaning the statement was closed, finalized, or its handle is stale. The change count itself is read from the connection and cannot otherwise fail.

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 bad083fafb)

Solutions

  1. Call changes() immediately after executing, before any close().
  2. Store the result locally instead of re-querying after cleanup.
  3. Guard with isClosed() when counters are read in error/recovery paths.
  4. Avoid sharing one statement object across components that each may close it.

Example fix

// before
stmt.execute();
stmt.close();
long affected = stmt.changes(); // throws: statement closed

// after
stmt.execute();
long affected = stmt.changes(); // read before close
stmt.close();
Defensive patterns

Strategy: validation

Validate before calling

long affected = -1;
if (!stmt.isClosed()) {
    affected = stmt.changes();
}

Try / catch

try {
    affected = stmt.changes();
} catch (SQLException e) {
    affected = -1;
    LOG.warn("changes() failed; statement no longer open", e);
}

Prevention

When it happens

Trigger: Calling stmt.changes() after stmt.close(); after connection close; on a statement reclaimed by a pool; in cleanup/finalizer code that runs after the normal close path.

Common situations: Getting 'rows affected' for the last INSERT/UPDATE/DELETE after a try-with-resources block exited; long-lived statement fields that an error handler already closed; double-close patterns where the second consumer of a shared statement hits a finalized handle.

Related errors


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