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
- Fully read/materialize the NClob while the session and transaction are open (e.g. in @Transactional service code)
- Map the column as materialized String (@Lob @Nationalized String) or byte[] so no live locator is retained
- Keep the session open until LOB content is consumed, or re-attach and refresh within a new transaction before reading
- 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
- Consume LOB streams inside the owning transaction; never in the view layer
- Map LOBs as materialized String when the content size is manageable
- Copy temp LOB content to a String before commit on drivers that free locators at commit
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
- Unable to access lob stream
- MODE function requires a WITHIN GROUP clause with exactly on
- Temporal unit not supported [%s]
- Insert conflict 'do update' clause with constraint name is n
- Locking with set operators is not supported
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/52ed934c421cbac5.
Report an issue: GitHub.