{"record":{"id":"a84cabfd08a44a4c","repo":"hibernate/hibernate-orm","slug":"blobs-may-not-be-accessed-after-serialization","errorCode":null,"errorMessage":"Blobs may not be accessed after serialization","messagePattern":"Blobs may not be accessed after serialization","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/engine/jdbc/proxy/SerializableBlobProxy.java","lineNumber":47,"sourceCode":"\n\t/**\n\t * Builds a serializable {@link Blob} wrapper around the given {@link Blob}.\n\t *\n\t * @param blob The {@link Blob} to be wrapped.\n\t * @see #generateProxy(Blob)\n\t */\n\tprivate SerializableBlobProxy(Blob blob) {\n\t\tthis.blob = blob;\n\t}\n\n\t/**\n\t * Access to the wrapped Blob reference\n\t *\n\t * @return The wrapped Blob reference\n\t */\n\tpublic Blob getWrappedBlob() {\n\t\tif ( blob == null ) {\n\t\t\tthrow new IllegalStateException( \"Blobs may not be accessed after serialization\" );\n\t\t}\n\t\telse {\n\t\t\treturn blob;\n\t\t}\n\t}\n\n\t@Override\n\tpublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n\t\tif ( \"getWrappedBlob\".equals( method.getName() ) ) {\n\t\t\treturn getWrappedBlob();\n\t\t}\n\t\ttry {\n\t\t\treturn method.invoke( getWrappedBlob(), args );\n\t\t}\n\t\tcatch ( AbstractMethodError e ) {\n\t\t\tthrow new HibernateException( \"The JDBC driver does not implement the method: \" + method, e );\n\t\t}\n\t\tcatch ( InvocationTargetException e ) {","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/engine/jdbc/proxy/SerializableBlobProxy.java#L29-L65","documentation":"SerializableBlobProxy adds java.io.Serializable to a Blob via a JDK dynamic proxy, but the wrapped Blob field is declared transient: only the proxy shell survives Java serialization. After a serialization round trip the field is null, so getWrappedBlob() - and therefore every Blob method routed through invoke() - throws IllegalStateException(\"Blobs may not be accessed after serialization\"). The underlying bytes were never part of the serial form.","triggerScenarios":"Storing a Hibernate-proxied Blob in an HttpSession that gets passivated/replicated (Spring Session, cluster failover); putting a detached entity containing the proxy into a store-by-value cache (ehcache/Infinispan in that mode); RMI or Java-serialization of the entity; calling ((WrappedBlob) proxy).getWrappedBlob() after deserialization.","commonSituations":"Web apps saving Hibernate entities with Blob fields in the HTTP session across clustered nodes; serializing detached entities into queues or distributed caches; JSF view state holding entities; any architecture that ships entities between JVMs via Java serialization.","solutions":["Map the attribute as byte[] instead of Blob so the raw data itself is serialized","Do not carry the entity across serialization - reload it by id in the new session/transaction","If you must serialize, extract first (blob.getBytes(1, (int) blob.length())) and rebuild with Hibernate.getLobHelper().createBlob(bytes)","Keep Hibernate-managed LOBs inside the owning session's lifetime only"],"exampleFix":"// before\n@Entity class Doc { @Lob Blob content; }   // proxied Blob stored in HttpSession ->\n session.setAttribute(\"doc\", doc);            // after failover: IllegalStateException\n\n// after\n@Entity class Doc {\n    @Lob byte[] content;   // plain serializable data; set via blob.getBytes(1, (int) blob.length())\n}","handlingStrategy":"validation","validationCode":"// Run BEFORE serializing anything that might hold a Hibernate Blob proxy\nstatic byte[] detachBlob(java.sql.Blob blob) throws SQLException {\n    try {\n        return blob.getBytes(1, (int) blob.length()); // works on the live proxy\n    } catch (IllegalStateException e) {\n        throw new IllegalStateException(\n            \"Blob already deserialized/empty - reload the entity in this session\", e);\n    }\n}\n// store detachBlob(blob) in the session/cache instead of the proxy","typeGuard":null,"tryCatchPattern":"try {\n    byte[] data = blob.getBytes(1, (int) blob.length());\n} catch (IllegalStateException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"after serialization\")) {\n        // the lob is gone: the only correct recovery is re-fetching the row\n        entity = session.find(Entity.class, id);\n        data = entity.getContent();\n    } else {\n        throw e;\n    }\n}","preventionTips":["Map LOB columns as byte[]/String in entities that will be serialized (HTTP session, caches, queues)","Never place Hibernate-managed LOB proxies in replicated sessions or store-by-value caches","Keep LOB-bearing entities within one session; reload by id in the next one","Materialize lob content to byte[] before any Java-serialization boundary"],"tags":["hibernate","jdbc","blob","lob","serialization","transient","distributed-cache","http-session","illegal-state"],"backgroundTag":"transient-field-null-after-deserialization","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}