tursodatabase/turso · error · SQLException

Error reading character stream

Error message

Error reading character stream

What it means

setCharacterStream(int, Reader, int) copies up to length chars from the Reader into a String; any IOException from reader.read during the copy is wrapped in this SQLException. The SQL never runs — the failure is in the caller's Reader (closed, truncated, or broken source).

Source

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

    }
    if (length < 0) {
      throw new SQLException("setCharacterStream length must be non-negative");
    }
    if (length == 0) {
      setParam(parameterIndex, "");
      return;
    }
    try {
      char[] buffer = new char[length];
      int offset = 0;
      int read;
      while (offset < length && (read = reader.read(buffer, offset, length - offset)) > 0) {
        offset += read;
      }
      String value = new String(buffer, 0, offset);
      setParam(parameterIndex, value);
    } catch (IOException e) {
      throw new SQLException("Error reading character stream", e);
    }
  }

  @Override
  public void setRef(int parameterIndex, Ref x) throws SQLException {
    // TODO
  }

  @Override
  public void setBlob(int parameterIndex, Blob x) throws SQLException {
    // TODO
  }

  @Override
  public void setClob(int parameterIndex, Clob x) throws SQLException {
    // TODO
  }

View on GitHub (pinned to bad083fafb)

Solutions

  1. Unwrap SQLException.getCause() to find the real IOException and fix the source Reader's lifecycle.
  2. Open the Reader and bind it in one try-with-resources scope.
  3. Buffer the content first (read to String) and call setString for unreliable sources.

Example fix

// before
ps.setCharacterStream(1, reader, len); // reader closed elsewhere

// after
try (Reader r = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
    ps.setCharacterStream(1, r, Files.size(path));
}
Defensive patterns

Strategy: try-catch

Validate before calling

// fail fast if the reader is already closed
try {
    reader.read();
    // note: this consumes a char — prefer marking, or just rely on the catch below
} catch (IOException closed) {
    throw new IllegalStateException("reader closed before binding", closed);
}

Try / catch

try {
    ps.setCharacterStream(1, reader, len);
} catch (SQLException e) {
    if (e.getCause() instanceof IOException ioe) {
        // Reader failure — reopen the source or fail with context
    }
    throw e;
}

Prevention

When it happens

Trigger: Passing a closed FileReader/StringReader; the underlying file deleted or rotated mid-copy; a network-backed Reader resetting; a parser-backed Reader throwing on malformed input.

Common situations: File rotation racing the bind; Readers over sockets or remote storage; a Reader already drained by earlier code (that yields a short silent bind, not an error, unless the source itself throws).

Related errors


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