{"record":{"id":"8e5fe8d52f487d52","repo":"hibernate/hibernate-orm","slug":"unable-to-access-lob-stream","errorCode":null,"errorMessage":"Unable to access lob stream","messagePattern":"Unable to access lob stream","errorType":"exception","errorClass":"HibernateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ByteArrayJavaType.java","lineNumber":148,"sourceCode":"\tpublic <X> Byte[] wrap(X value, WrapperOptions options) {\n\t\tif ( value == null ) {\n\t\t\treturn null;\n\t\t}\n\t\tif (value instanceof Byte[] bytes) {\n\t\t\treturn bytes;\n\t\t}\n\t\tif (value instanceof byte[] bytes) {\n\t\t\treturn wrapBytes( bytes );\n\t\t}\n\t\tif (value instanceof InputStream inputStream) {\n\t\t\treturn wrapBytes( DataHelper.extractBytes( inputStream ) );\n\t\t}\n\t\tif ( value instanceof Blob blob ) {\n\t\t\ttry {\n\t\t\t\treturn wrapBytes( DataHelper.extractBytes( blob.getBinaryStream() ) );\n\t\t\t}\n\t\t\tcatch ( SQLException e ) {\n\t\t\t\tthrow new HibernateException( \"Unable to access lob stream\", e );\n\t\t\t}\n\t\t}\n\n\t\tthrow unknownWrap( value.getClass() );\n\t}\n\n\tprivate Byte[] wrapBytes(byte[] bytes) {\n\t\tif ( bytes == null ) {\n\t\t\treturn null;\n\t\t}\n\t\tfinal var result = new Byte[bytes.length];\n\t\tfor ( int i = 0; i < bytes.length; i++ ) {\n\t\t\tresult[i] = bytes[i];\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate byte[] unwrapBytes(Byte[] bytes) {","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ByteArrayJavaType.java#L130-L166","documentation":"ByteArrayJavaType.wrap converts an incoming java.sql.Blob to Byte[] by calling DataHelper.extractBytes(blob.getBinaryStream()); a SQLException during extraction is wrapped as HibernateException('Unable to access lob stream'). Same locator-lifetime failure as the other Blob errors, but on the wrap (read side) path.","triggerScenarios":"wrap() receiving a raw JDBC Blob whose locator is dead: setting a Byte[] attribute from a stored-function result or native query returning Blob; converting after the owning transaction ended.","commonSituations":"Native queries and StoredProcedureQuery returning Blob into Byte[] fields; moving data across sessions; Oracle temporary LOB lifetime ending at commit.","solutions":["Convert the Blob to bytes inside the owning transaction.","Have the query return byte[] directly, or map the attribute as byte[] with @Lob.","Copy the bytes immediately after retrieval via session.doReturningWork if you must cross a transaction boundary."],"exampleFix":"// before\nByte[] data = session.doWork(...); // Blob fetched in one tx, wrapped in another\n\n// after\nbyte[] bytes = session.doReturningWork((conn, rs) -> /* read bytes now, inside live tx */);\nentity.setData(bytes);","handlingStrategy":"try-catch","validationCode":"try {\n    blob.length(); // probe the locator before wrap()\n} catch (SQLException e) {\n    throw new IllegalStateException(\"Cannot wrap a dead Blob locator\", e);\n}","typeGuard":null,"tryCatchPattern":"try {\n    return byteArrayJavaType.wrap(blob, options);\n} catch (HibernateException e) {\n    if (e.getCause() instanceof SQLException) {\n        return wrapBytesFromFreshLoad(id); // re-read inside a live tx\n    }\n    throw e;\n}","preventionTips":["Copy Blob bytes out inside the transaction that produced the Blob.","Avoid passing raw JDBC Blobs between layers; materialize early.","Map byte[] with @Lob when streaming is unnecessary."],"tags":["hibernate","blob","lob","jdbc","wrap"],"backgroundTag":"blob-stream-access","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}