hibernate/hibernate-orm · error · HibernateException

Unable to access nclob stream

Error message

Unable to access nclob stream

What it means

In NClobJavaType.unwrap(), converting an NClob to a Reader/CharacterStream/String calls getCharacterStream()/length() on the live LOB. Any SQLException from the driver is wrapped as HibernateException("Unable to access nclob stream"). The usual root cause is an invalidated LOB locator: the stream was already consumed, free() was called, or the owning transaction/connection has closed.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/type/descriptor/java/NClobJavaType.java:150

				}
				else {
					// otherwise we need to build a Reader...
					return type.cast( value.getCharacterStream() );
				}
			}
			else if ( CharacterStream.class.isAssignableFrom( type ) ) {
				if (value instanceof NClobImplementer clobImplementer) {
					// if the incoming NClob is a wrapper, just pass along its CharacterStream
					return type.cast( clobImplementer.getUnderlyingStream() );
				}
				else {
					// otherwise we need to build a CharacterStream...
					return type.cast( new CharacterStreamImpl( value.getCharacterStream(), value.length() ) );
				}
			}
		}
		catch ( SQLException e ) {
			throw new HibernateException( "Unable to access nclob stream", e );
		}

		throw unknownUnwrap( type );
	}

	public <X> NClob wrap(X value, WrapperOptions options) {
		if ( value == null ) {
			return null;
		}
		else {
			final LobCreator lobCreator = options.getLobCreator();
			if ( value instanceof NClob clob ) {
				return lobCreator.wrap( clob );
			}
			else if ( value instanceof Clob clob ) {
				try {
					return lobCreator.createNClob( clob.getCharacterStream(), clob.length() );
				}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Fully read/materialize the NClob while the session and transaction are open (e.g. in @Transactional service code)
  2. Map the column as materialized String (@Lob @Nationalized String) or byte[] so no live locator is retained
  3. Keep the session open until LOB content is consumed, or re-attach and refresh within a new transaction before reading
  4. For drivers that free temp LOBs at commit, copy the LOB content before committing (stream it into a String)

Example fix

// before
@Transactional(readOnly = true)
public Article load(Long id) { return repo.findById(id).orElseThrow(); }
// caller later reads article.getBody().getCharacterStream() -> HibernateException

// after
@Transactional(readOnly = true)
public String loadBody(Long id) {
    return repo.findById(id).map(a -> a.getBodyText()).orElseThrow(); // materialized String
}
// entity uses: @Nationalized @Lob String bodyText;
Defensive patterns

Strategy: try-catch

Validate before calling

static String readWithinTx(jakarta.persistence.EntityManager em, Long id) {
    return em.executeInTransaction(() -> {
        var a = em.find(Article.class, id);
        return a == null ? null : readFully(a.getBody()); // consume NClob now
    });
}

static String readFully(java.sql.NClob clob) throws java.sql.SQLException {
    try (var r = clob.getCharacterStream()) { return r.lines().collect(java.util.stream.Collectors.joining("\n")); }
}

Type guard

static boolean isUsable(java.sql.NClob c) {
    try { c.length(); return true; } catch (java.sql.SQLException e) { return false; }
}

Try / catch

try {
    return readFully(article.getBody());
} catch (HibernateException e) {
    if ("Unable to access nclob stream".equals(e.getMessage()) && e.getCause() instanceof java.sql.SQLException) {
        // locator invalid: re-read inside a fresh transaction instead of retrying the dead handle
        return retryInNewTransaction(article.getId());
    }
    throw e;
}

Prevention

When it happens

Trigger: Accessing a lazy NClob attribute after the session was closed or the transaction committed; reading the character stream twice from a forward-only locator; Oracle/DB2-style drivers that free temporary LOBs at commit while the entity is still referenced

Common situations: Detached entities whose NClob fields are read in the view layer after the transaction ended; long request processing holding entities across commits; async processing or thread handoff after the session-bound work finished

Related errors


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