hibernate/hibernate-orm · error · SQLException

Start position 1-based; must be 1 or more.

Error message

Start position 1-based; must be 1 or more.

What it means

Hibernate's ClobProxy implements java.sql.Clob for non-contextual creation (e.g. Hibernate.getLobHelper().createClob(String) or createClob(Reader, long)). Its getSubString(long start, int length) validates start against the JDBC contract, which is 1-based: position 1 is the first character. Passing 0 or a negative value throws this SQLException before any substring is built.

Source

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

	@Override
	public OutputStream setAsciiStream(long pos) {
		throw notSupported();
	}

	@Override
	public Writer setCharacterStream(long pos) {
		throw notSupported();
	}

	@Override
	public void truncate(long len) throws SQLException {
		throw notSupported();
	}

	@Override
	public String getSubString(long start, int 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 < 0 ) {
			throw new SQLException( "Length must be great-than-or-equal to zero." );
		}
		final String string = characterStream.asString();
		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 ) {

View on GitHub (pinned to fad1729dce)

Solutions

  1. Pass start >= 1, i.e. use index + 1 for 0-based offsets
  2. When porting s.substring(a, b), call getSubString(a + 1, b - a)
  3. Pin the contract with a test: getSubString(1, 1) must return the first character

Example fix

// before (String.substring semantics)
String head = clob.getSubString(0, 10); // throws

// after (JDBC 1-based)
String head = clob.getSubString(1, 10);
Defensive patterns

Strategy: validation

Validate before calling

static String substring0(java.sql.Clob clob, int beginIndex, int endIndex) throws SQLException {
    long start = beginIndex + 1;                    // String is 0-based, Clob is 1-based
    int length = Math.max(0, endIndex - beginIndex);
    return clob.getSubString(start, length);
}

Try / catch

try {
    s = clob.getSubString(start, length);
} catch (SQLException e) {
    if (e.getMessage() != null && e.getMessage().contains("Start position")) {
        s = clob.getSubString(beginIndex + 1, length); // fix origin, retry
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Calling clob.getSubString(0, n) on a Hibernate-created Clob; translating String.substring(i, j) calls directly into getSubString(i, j - i) without shifting the origin; using a 0-based loop counter as the start.

Common situations: Developers mapping String.substring (0-based) semantics onto Clob.getSubString (1-based); text-windowing code (previews, excerpts) written against arrays; test data helpers assuming array indexing.

Related errors


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