tursodatabase/turso · error · SQLException

Exception while binding blob value at position " + position

Error message

Exception while binding blob value at position " + position

What it means

Thrown when the JNI-native bindBlob call returns a non-zero result. The native code first resolves the statement pointer (failing with SQLITE_ERROR if the statement is closed or the handle is stale), converts the byte[] via JNI (conversion failure also returns SQLITE_ERROR), then calls bind_at which rejects out-of-range 1-based positions. Blob size is not validated at this layer.

Source

Thrown at bindings/java/src/main/java/tech/turso/core/TursoStatement.java:210

    }
    return result;
  }

  private native int bindText(long statementPointer, int position, String value)
      throws SQLException;

  /**
   * Binds a blob value to the prepared statement at the specified position.
   *
   * @param position The index of the SQL parameter to be set.
   * @param value The value to bind to the parameter.
   * @return <a href="https://www.sqlite.org/c3ref/c_abort.html">Result Codes</a>
   * @throws SQLException If a database access error occurs.
   */
  public int bindBlob(int position, byte[] value) throws SQLException {
    final int result = bindBlob(statementPointer, position, value);
    if (result != 0) {
      throw new SQLException("Exception while binding blob value at position " + position);
    }
    return result;
  }

  private native int bindBlob(long statementPointer, int position, byte[] value)
      throws SQLException;

  public void bindObject(int parameterIndex, Object x) throws SQLException {
    if (x == null) {
      this.bindNull(parameterIndex);
      return;
    }
    if (x instanceof Byte) {
      this.bindInt(parameterIndex, (Byte) x);
    } else if (x instanceof Short) {
      this.bindInt(parameterIndex, (Short) x);
    } else if (x instanceof Integer) {
      this.bindInt(parameterIndex, (Integer) x);

View on GitHub (pinned to bad083fafb)

Solutions

  1. Use 1-based parameter positions and check them against parameterCount().
  2. Guard with isClosed() before binding blobs on long-lived statements.
  3. Rebuild prepared statements after any error path that may have closed them rather than blindly reusing.
  4. Add an assertion layer in test builds that binds match the SQL placeholder count.

Example fix

// before
byte[] avatar = Files.readAllBytes(path);
ps.bindBlob(0, avatar); // wrong: 0-based position

// after
byte[] avatar = Files.readAllBytes(path);
if (avatarIndex < 1 || avatarIndex > ps.parameterCount()) {
    throw new IllegalArgumentException("bad bind position");
}
ps.bindBlob(avatarIndex, avatar);
Defensive patterns

Strategy: validation

Validate before calling

if (stmt.isClosed()) throw new IllegalStateException("statement closed");
if (position < 1 || position > stmt.parameterCount()) {
    throw new IllegalArgumentException("blob bind position out of range: " + position);
}
stmt.bindBlob(position, bytes);

Try / catch

try {
    stmt.bindBlob(position, bytes);
} catch (SQLException e) {
    throw new IllegalStateException("bindBlob failed at " + position, e);
}

Prevention

When it happens

Trigger: stmt.bindBlob(position, bytes) with position 0 or beyond parameterCount(); binding a blob after statement close(); passing a byte[] on a statement whose connection is closed.

Common situations: File/image upload code binding binary payloads; 0-based array-index habits carried over into parameter positions; statements kept as long-lived fields and reused after an error path already closed them.

Related errors


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