{"record":{"id":"36b8b6e0856a0149","repo":"tursodatabase/turso","slug":"exception-while-retrieving-total-number-of-changes","errorCode":null,"errorMessage":"Exception while retrieving total number of changes","messagePattern":"Exception while retrieving total number of changes","errorType":"exception","errorClass":"SQLException","httpStatus":null,"severity":"error","filePath":"bindings/java/src/main/java/tech/turso/core/TursoStatement.java","lineNumber":252,"sourceCode":"      bindDouble(parameterIndex, (Float) x);\n    } else if (x instanceof Double) {\n      bindDouble(parameterIndex, (Double) x);\n    } else if (x instanceof byte[]) {\n      bindBlob(parameterIndex, (byte[]) x);\n    } else {\n      throw new SQLException(\"Unsupported object type in bindObject: \" + x.getClass().getName());\n    }\n  }\n\n  /**\n   * Returns total number of changes.\n   *\n   * @throws SQLException If a database access error occurs\n   */\n  public long totalChanges() throws SQLException {\n    final long result = totalChanges(statementPointer);\n    if (result == -1) {\n      throw new SQLException(\"Exception while retrieving total number of changes\");\n    }\n\n    return result;\n  }\n\n  private native long totalChanges(long statementPointer) throws SQLException;\n\n  /**\n   * Returns number of changes.\n   *\n   * @throws SQLException If a database access error occurs\n   */\n  public long changes() throws SQLException {\n    final long result = changes(statementPointer);\n    if (result == -1) {\n      throw new SQLException(\"Exception while retrieving number of changes\");\n    }\n","sourceCodeStart":234,"sourceCodeEnd":270,"githubUrl":"https://github.com/tursodatabase/turso/blob/bad083fafbefdeae9a42ec19bdaaad8918dcf411/bindings/java/src/main/java/tech/turso/core/TursoStatement.java#L234-L270","documentation":"The Java wrapper calls the native totalChanges and throws when it returns -1. In the native implementation, -1 is returned only when the statement pointer cannot be resolved to a live statement (closed, finalized, or stale handle); a valid statement always returns the connection's change count. So this exception almost always means the statement was used after close.","triggerScenarios":"Calling stmt.totalChanges() after stmt.close(); after the owning connection was closed; after a pool reclaimed the statement; from a finalizer or cleanup thread racing with close().","commonSituations":"Reporting rows-affected after a try-with-resources block has already auto-closed the statement; utility code that closes statements early for resource hygiene but still wants change totals; races between request timeouts that abort/close statements and code that then reads counters.","solutions":["Read totalChanges() before closing the statement (and inside the same try block as execution).","Check stmt.isClosed() before querying counters in defensive code paths.","Capture the value into a local variable immediately after execute/update, then close.","Ensure only the owning thread closes/uses the statement."],"exampleFix":"// before\ntry (TursoStatement stmt = conn.prepare(sql)) {\n    stmt.step();\n}\nlong total = stmt.totalChanges(); // throws: statement already closed\n\n// after\nlong total;\ntry (TursoStatement stmt = conn.prepare(sql)) {\n    stmt.step();\n    total = stmt.totalChanges(); // read while still open\n}","handlingStrategy":"validation","validationCode":"if (!stmt.isClosed()) {\n    long total = stmt.totalChanges();\n} else {\n    // statement already closed: re-run via a fresh statement if the count is needed\n}","typeGuard":null,"tryCatchPattern":"try {\n    total = stmt.totalChanges();\n} catch (SQLException e) {\n    // native handle gone (statement closed) - treat as unknown rather than crash reporting\n    total = -1;\n    LOG.warn(\"totalChanges unavailable; statement closed\", e);\n}","preventionTips":["Read change counters immediately after execute, inside the statement's try block.","Store counters in local variables instead of re-querying after cleanup.","Never let a finally block close the statement before counters are captured.","Keep statement ownership single-threaded."],"tags":["java","jdbc","changes","statement-lifecycle","use-after-close"],"backgroundTag":"closed-statement-access","analyzedSha":"bad083fafbefdeae9a42ec19bdaaad8918dcf411","analyzedAt":"2026-08-16T23:12:11.798Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}