tursodatabase/turso · error · SQLException

Error reading binary stream

Error message

Error reading binary stream

What it means

setBinaryStream(int, InputStream, int) copies the stream through an 8 KiB buffer into a ByteArrayOutputStream; an IOException from x.read — the only call that can throw here, since ByteArrayOutputStream.write is unchecked — is wrapped in this SQLException. Note that a short stream does NOT throw: if EOF arrives before length bytes, the driver silently binds only the bytes actually read, so verify sizes separately if truncation matters.

Source

Thrown at bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java:254

      throw new SQLException("setBinaryStream length must be non-negative");
    }
    if (length == 0) {
      setParam(parameterIndex, new byte[0]);
      return;
    }
    try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
      byte[] buffer = new byte[8192];
      int bytesRead;
      int totalRead = 0;
      while (totalRead < length
          && (bytesRead = x.read(buffer, 0, Math.min(buffer.length, length - totalRead))) > 0) {
        baos.write(buffer, 0, bytesRead);
        totalRead += bytesRead;
      }
      byte[] data = baos.toByteArray();
      setParam(parameterIndex, data);
    } catch (IOException e) {
      throw new SQLException("Error reading binary stream", e);
    }
  }

  @Override
  public void clearParameters() {
    this.currentBatchParams = new Object[paramCount];
  }

  @Override
  public void clearBatch() throws SQLException {
    this.batchQueryParams.clear();
    this.currentBatchParams = new Object[paramCount];
  }

  @Override
  public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
    // TODO
  }

View on GitHub (pinned to bad083fafb)

Solutions

  1. Inspect getCause() for the original IOException and fix the source stream lifecycle.
  2. Keep the source alive until the bind returns; open it in the same scope (try-with-resources).
  3. Buffer remote streams first (readAllBytes with your own retry) and bind with setBytes; also verify the byte count matches expectations to catch silent truncation.

Example fix

// before
ps.setBinaryStream(1, in, len); // in already closed elsewhere

// after
try (InputStream in = Files.newInputStream(path)) {
    ps.setBinaryStream(1, in, (int) Files.size(path));
}
Defensive patterns

Strategy: try-catch

Validate before calling

// fail fast if the stream is already closed
try {
    in.available();
} catch (IOException closed) {
    throw new IllegalStateException("stream closed before binding", closed);
}

Try / catch

try {
    ps.setBinaryStream(1, in, len);
} catch (SQLException e) {
    if (e.getCause() instanceof IOException ioe) {
        // stream failure — reopen/retry the source, or buffer first and use setBytes
    }
    throw e;
}

Prevention

When it happens

Trigger: Already-closed stream; file deleted mid-read; network stream reset while the copy loop is running; truncated upload being bound with its advertised length.

Common situations: Streams from HTTP/S3 dropping mid-transfer; files removed by retention jobs between open and bind; silent truncation going unnoticed because short reads do not error.

Related errors


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