{"record":{"id":"2048dee633be8150","repo":"hibernate/hibernate-orm","slug":"clobs-may-not-be-accessed-after-serialization","errorCode":null,"errorMessage":"Clobs may not be accessed after serialization","messagePattern":"Clobs 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/SerializableClobProxy.java","lineNumber":47,"sourceCode":"\n\t/**\n\t * Builds a serializable {@link Clob} wrapper around the given {@link Clob}.\n\t *\n\t * @param clob The {@link Clob} to be wrapped.\n\t * @see #generateProxy(Clob)\n\t */\n\tprotected SerializableClobProxy(Clob clob) {\n\t\tthis.clob = clob;\n\t}\n\n\t/**\n\t * Access to the wrapped Clob reference\n\t *\n\t * @return The wrapped Clob reference\n\t */\n\tpublic Clob getWrappedClob() {\n\t\tif ( clob == null ) {\n\t\t\tthrow new IllegalStateException( \"Clobs may not be accessed after serialization\" );\n\t\t}\n\t\telse {\n\t\t\treturn clob;\n\t\t}\n\t}\n\n\t@Override\n\tpublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n\t\tif ( \"getWrappedClob\".equals( method.getName() ) ) {\n\t\t\treturn getWrappedClob();\n\t\t}\n\t\ttry {\n\t\t\treturn method.invoke( getWrappedClob(), 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/SerializableClobProxy.java#L29-L65","documentation":"SerializableClobProxy mirrors the Blob variant: it makes a Clob serializable through a JDK dynamic proxy, but the wrapped Clob field is transient, so a Java serialization round trip nulls it. Afterward getWrappedClob() - and every Clob method routed through invoke() - throws IllegalStateException(\"Clobs may not be accessed after serialization\"). The character data was never written to the serial form.","triggerScenarios":"Putting a Hibernate-proxied Clob into a replicated HttpSession (Spring Session, cluster failover); a detached entity with a Clob attribute stored in a store-by-value cache or shipped over RMI/Java serialization; calling ((WrappedClob) proxy).getWrappedClob() after deserialization.","commonSituations":"Clustered web apps keeping text-heavy entities in session; serializing detached entities to message queues; JSF view state or conversational state holding entities with Clob fields.","solutions":["Map the attribute as String instead of Clob so the text itself is serialized","Reload the entity by id in the new session instead of reusing the serialized instance","If serialization is unavoidable, extract first (clob.getSubString(1, (int) clob.length())) and rebuild with Hibernate.getLobHelper().createClob(text)","Keep LOB-bearing entities within a single session/transaction boundary"],"exampleFix":"// before\n@Entity class Article { @Lob Clob body; }\n session.setAttribute(\"article\", article); // after replication: IllegalStateException\n\n// after\n@Entity class Article {\n    @Lob String body;  // plain serializable text; set via clob.getSubString(1, (int) clob.length())\n}","handlingStrategy":"validation","validationCode":"// Run BEFORE serializing anything that might hold a Hibernate Clob proxy\nstatic String detachClob(java.sql.Clob clob) throws SQLException {\n    try {\n        return clob.getSubString(1, (int) clob.length()); // works on the live proxy\n    } catch (IllegalStateException e) {\n        throw new IllegalStateException(\n            \"Clob already deserialized/empty - reload the entity in this session\", e);\n    }\n}\n// store detachClob(clob) instead of the proxy","typeGuard":null,"tryCatchPattern":"try {\n    text = clob.getSubString(1, (int) clob.length());\n} catch (IllegalStateException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"after serialization\")) {\n        entity = session.find(Entity.class, id);      // only recovery: re-fetch the row\n        text = entity.getBody();\n    } else {\n        throw e;\n    }\n}","preventionTips":["Map long text columns as String in entities destined for sessions/caches/queues","Never store Hibernate Clob proxies across serialization boundaries","Reload entities by id in each session instead of carrying detached LOBs","Materialize clob text before writing to any store-by-value cache"],"tags":["hibernate","jdbc","clob","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"}