tursodatabase/turso · error · SQLException

Error reading InputStream

Error message

Error reading InputStream

What it means

The no-length setAsciiStream(int, InputStream) and setBinaryStream(int, InputStream) overloads drain the whole stream eagerly via the readBytes helper: one probe byte, then 8 KiB chunks until EOF. Any IOException during that copy is wrapped in this SQLException. An empty stream is legal (probe byte == -1 binds an empty value); this error means the stream actually failed mid-read, at bind time — not later at execute.

Source

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

   * @return a byte array containing the data
   * @throws SQLException if an I/O error occurs while reading
   */
  private byte[] readBytes(InputStream x) throws SQLException {
    try {
      int firstByte = x.read();
      if (firstByte == -1) {
        return new byte[0];
      }
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      baos.write(firstByte);
      byte[] buffer = new byte[8192];
      int bytesRead;
      while ((bytesRead = x.read(buffer)) > 0) {
        baos.write(buffer, 0, bytesRead);
      }
      return baos.toByteArray();
    } catch (IOException e) {
      throw new SQLException("Error reading InputStream", e);
    }
  }

  @Override
  public void setCharacterStream(int parameterIndex, @Nullable Reader reader) throws SQLException {
    requireNonNull(this.statement);
    if (reader == null) {
      setParam(parameterIndex, null);
      return;
    }
    try {
      StringBuilder sb = new StringBuilder();
      char[] buffer = new char[8192];
      int read;
      while ((read = reader.read(buffer)) != -1) {
        sb.append(buffer, 0, read);
      }
      setParam(parameterIndex, sb.toString());

View on GitHub (pinned to bad083fafb)

Solutions

  1. Unwrap SQLException.getCause() for the original IOException and fix the producer (timeouts, retries, keep-alive).
  2. Stabilize remote streams first: read them fully into a byte[] with your own retry, then bind with setBytes; or download to a temp file.
  3. Open and bind the stream inside one try-with-resources block — the copy happens during the set* call itself.

Example fix

// before
InputStream in = objectStore.open(key); // may reset mid-read
ps.setBinaryStream(1, in);

// after
byte[] data = retryingReadAll(objectStore, key); // buffered + retried
ps.setBytes(1, data);
Defensive patterns

Strategy: try-catch

Validate before calling

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

Try / catch

try {
    ps.setBinaryStream(1, in);
} catch (SQLException e) {
    if (e.getCause() instanceof IOException ioe) {
        // stream failure: reopen/retry the source, or fail with context
    }
    throw e;
}

Prevention

When it happens

Trigger: Already-closed or invalidated stream; HTTP/S3-backed stream resetting mid-copy; GZIP stream hitting corruption while being drained; file deleted after open.

Common situations: Object-storage streams without client-side retry; streams closed by an outer scope before the set* call; two components sharing one stream instance.

Related errors


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