tursodatabase/turso · error · SQLException

setUnicodeStream length must be non-negative

Error message

setUnicodeStream length must be non-negative

What it means

JDBC4PreparedStatement.setUnicodeStream(int, InputStream, int) rejects a negative length with SQLException at JDBC4PreparedStatement.java:208 before touching the stream. This deprecated JDBC 1 method decodes the stream as UTF-8; the driver requires an explicit non-negative byte length and does not accept -1 as an 'unknown length' sentinel. Null streams are accepted and bind NULL.

Solutions

  1. Migrate off the deprecated API: use setCharacterStream or setNString for unicode text, or setString directly.
  2. If you must use it, compute and pass the exact non-negative byte length of the UTF-8 payload.
  3. Buffer the stream into a String first, then call setString - removes length bookkeeping entirely.
  4. Validate length >= 0 at the boundary of your wrapper and fail with your own clear message.

Example fix

// before
ps.setUnicodeStream(1, utf8In, -1); // negative sentinel -> SQLException

// after
String text = new String(utf8In.readAllBytes(), StandardCharsets.UTF_8);
ps.setString(1, text);
Defensive patterns

Strategy: validation

Validate before calling

if (length < 0) {
    String text = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
    ps.setString(parameterIndex, text);
} else {
    ps.setUnicodeStream(parameterIndex, stream, length);
}

Try / catch

try {
    ps.setUnicodeStream(i, in, length);
} catch (SQLException e) {
    if (e.getMessage().contains("non-negative")) { /* recompute length or setString */ } else { throw e; }
}

Prevention

When it happens

Trigger: Passing -1 as a length sentinel (a convention tolerated by some other drivers); length computed from a char count, remaining bytes, or an underflowing calculation; forwarding caller-supplied lengths without validation in a generic data layer; test code using placeholder lengths.

Common situations: Legacy code still on the deprecated setUnicodeStream API being pointed at turso; wrappers/frameworks that mirror the old JDBC 1 trio (setAsciiStream/setUnicodeStream/setBinaryStream); migrations where the original database accepted loose length semantics.

Related errors


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

Appendix: source

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

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

  @Override
  public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
    requireNonNull(this.statement);
    if (x == null) {
      setParam(parameterIndex, null);
      return;
    }
    if (length < 0) {
      throw new SQLException("setUnicodeStream length must be non-negative");
    }
    if (length == 0) {
      setParam(parameterIndex, "");
      return;
    }
    try {
      byte[] buffer = new byte[length];
      int offset = 0;
      int read;
      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);
    }
  }

View on GitHub (pinned to 244cde92a7)