tursodatabase/turso · error · SQLException

setBinaryStream length must be non-negative

Error message

setBinaryStream length must be non-negative

What it means

JDBC4PreparedStatement.setBinaryStream(int, InputStream, int) validates that length is >= 0 and throws SQLException at JDBC4PreparedStatement.java:236 otherwise. The driver copies exactly 'length' bytes from the stream into a BLOB parameter; a -1 'unknown length' sentinel, accepted by some other drivers, is rejected here. Use the two-argument setBinaryStream(int, InputStream) overload when the size is unknown.

Solutions

  1. Switch to setBinaryStream(parameterIndex, stream) - it reads to EOF and needs no length.
  2. Or buffer the payload (Files.readAllBytes / readAllBytes) and use setBytes(index, bytes).
  3. Compute the length from a reliable source (file length, array length) and fail fast yourself if it cannot be determined.
  4. Audit generic wrapper code for forwarded lengths and validate length >= 0 at the boundary.

Example fix

// before
ps.setBinaryStream(1, fileIn, -1); // -1 sentinel -> SQLException

// after
ps.setBinaryStream(1, fileIn); // reads to EOF
// or: ps.setBytes(1, Files.readAllBytes(path));
Defensive patterns

Strategy: validation

Validate before calling

if (length < 0) {
    ps.setBinaryStream(parameterIndex, stream); // unknown length: read to EOF
} else {
    ps.setBinaryStream(parameterIndex, stream, length);
}

Try / catch

try {
    ps.setBinaryStream(i, in, length);
} catch (SQLException e) {
    if (e.getMessage().contains("non-negative")) { ps.setBinaryStream(i, in); } else { throw e; }
}

Prevention

When it happens

Trigger: Passing -1 (or any negative int) as length, commonly the 'read to end' convention from other drivers; lengths derived from File.length() cast incorrectly, subtraction underflow, or -1 returned by a size-lookup helper; wrapper APIs forwarding user-supplied lengths unchecked; null stream is allowed (binds NULL), only negative length throws.

Common situations: File-upload code where the size probe failed and -1 leaked into the bind call; generic JDBC layers ported from MySQL Connector/J or the PostgreSQL driver; big-binary insert code written against tutorials using the 3-arg API; tests with placeholder lengths.

Related errors


AI-assisted analysis of tursodatabase/turso@244cde92a7 (2026-08-20). Data as JSON: /api/errors/ec4c7e9750944ff4. Report an issue: GitHub.

Appendix: source

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

      while (offset < length && (read = x.read(buffer, offset, length - offset)) > 0) {
        offset += read;
      }
      String text = new String(buffer, 0, offset, StandardCharsets.UTF_8);
      setParam(parameterIndex, text);
    } catch (IOException e) {
      throw new SQLException("Error reading Unicode stream", e);
    }
  }

  @Override
  public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {
    requireNonNull(this.statement);
    if (x == null) {
      setParam(parameterIndex, null);
      return;
    }
    if (length < 0) {
      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);

View on GitHub (pinned to 244cde92a7)