hibernate/hibernate-orm · error · SQLException

Length must be greater than or equal to zero

Error message

Length must be greater than or equal to zero

What it means

In ClobProxy.getCharacterStream(long start, long length) a negative length is rejected with this SQLException (message variant without the period, distinct from the getSubString message). The source comment notes the JDBC javadoc requires start+length to stay within the Clob, so a negative length is a caller bug caught before the sub-stream is created. The check is unreachable for values above Integer.MAX_VALUE, which the preceding check already rejected.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/engine/jdbc/proxy/ClobProxy.java:153

		final long endIndex = Math.min( start + length - 1, string.length() );
		return string.substring( (int) start - 1, (int) endIndex );
	}

	@Override
	public Reader getCharacterStream(long start, long length) throws SQLException {
		if ( start < 1 ) {
			throw new SQLException( "Start position 1-based; must be 1 or more." );
		}
		if ( start > length() + 1 ) {
			throw new SQLException( "Start position [" + start + "] cannot exceed overall CLOB length [" + length() + "]" );
		}
		if ( length > Integer.MAX_VALUE ) {
			throw new SQLException( "Can't deal with Clobs larger than 'Integer.MAX_VALUE'" );
		}
		if ( length < 0 ) {
			// javadoc for getCharacterStream(long,int) specifies that the start+length must not exceed the
			// total length (this is at odds with the behavior of getSubString(long,int))
			throw new SQLException( "Length must be greater than or equal to zero" );
		}
		return DataHelper.subStream( getCharacterStream(), start-1, (int) length );
	}

	@Override
	public void free() throws SQLException {
		characterStream.release();
	}

	protected void resetIfNeeded() {
		try {
			if ( needsReset ) {
				characterStream.asReader().reset();
			}
		}
		catch ( IOException ioe ) {
			throw new HibernateException( "could not reset reader", ioe );
		}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Pass length >= 0 (0 is legal, yielding an empty reader)
  2. Clamp remaining counts: long len = Math.max(0L, remaining)
  3. Translate 'unlimited' sentinels to the true remaining length before the call

Example fix

// before
Reader r = clob.getCharacterStream(1, remaining); // remaining < 0

// after
long len = Math.max(0L, remaining);
if (len > 0) { Reader r = clob.getCharacterStream(1, len); }
Defensive patterns

Strategy: validation

Validate before calling

static Reader safeReader(java.sql.Clob clob, long start, long length) throws SQLException {
    if (start < 1) throw new IllegalArgumentException("start must be >= 1");
    long len = Math.max(0L, length);                // negative length means 'nothing'
    return clob.getCharacterStream(start, len);
}

Try / catch

try {
    r = clob.getCharacterStream(start, length);
} catch (SQLException e) {
    if (e.getMessage() != null && e.getMessage().contains("greater than or equal to zero")) {
        r = clob.getCharacterStream(start, 0);      // degrade to empty reader
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Calling clob.getCharacterStream(1, -1); a remaining-chars computation (total - consumed) going negative after the content is exhausted; forwarding a -1 'unlimited' sentinel from configuration.

Common situations: Windowed streaming loops that underflow at the end of the content; optional size parameters defaulting to -1; tests probing invalid arguments.

Related errors


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/7c2a7303f78368a0. Report an issue: GitHub.