{"record":{"id":"cd1b42eef0ef3cd4","repo":"hibernate/hibernate-orm","slug":"clobs-are-not-cacheable-cd1b42","errorCode":null,"errorMessage":"Clobs are not cacheable","messagePattern":"Clobs are not cacheable","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/type/descriptor/java/NClobJavaType.java","lineNumber":49,"sourceCode":" * @author Steve Ebersole\n * @author Loïc Lefèvre\n */\npublic class NClobJavaType extends AbstractClassJavaType<NClob> {\n\tpublic static final NClobJavaType INSTANCE = new NClobJavaType();\n\n\tpublic static class NClobMutabilityPlan implements MutabilityPlan<NClob> {\n\t\tpublic static final NClobMutabilityPlan INSTANCE = new NClobMutabilityPlan();\n\n\t\tpublic boolean isMutable() {\n\t\t\treturn false;\n\t\t}\n\n\t\tpublic NClob deepCopy(NClob value) {\n\t\t\treturn value;\n\t\t}\n\n\t\tpublic Serializable disassemble(NClob value, SharedSessionContract session) {\n\t\t\tthrow new UnsupportedOperationException( \"Clobs are not cacheable\" );\n\t\t}\n\n\t\tpublic NClob assemble(Serializable cached, SharedSessionContract session) {\n\t\t\tthrow new UnsupportedOperationException( \"Clobs are not cacheable\" );\n\t\t}\n\t}\n\n\tpublic NClobJavaType() {\n\t\tsuper( NClob.class, NClobMutabilityPlan.INSTANCE, IncomparableComparator.INSTANCE );\n\t}\n\n\t@Override\n\tpublic boolean isInstance(Object value) {\n\t\treturn value instanceof NClob;\n\t}\n\n\t@Override\n\tpublic NClob cast(Object value) {","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/NClobJavaType.java#L31-L67","documentation":"NClobMutabilityPlan implements disassemble() by throwing UnsupportedOperationException because a live java.sql.NClob is a handle tied to a JDBC connection/locator and cannot be serialized into the second-level cache. disassemble runs when Hibernate builds a cache entry, so the exception appears at write time (insert/update of a cached entity, or putting a query result into the cache).","triggerScenarios":"An entity with a java.sql.NClob (or @Nationalized LOB) attribute is annotated @Cacheable or covered by a @Cache(...) region; a collection or query cache entry includes such a value; hibernate.cache enabled globally and the entity gets cached on flush","commonSituations":"Adding caching annotations to legacy entities that carry LOB fields; moving LOB content from String columns to NClob columns on already-cached entities; enabling query caching on queries selecting LOB entities","solutions":["Model the LOB in a separate, non-cached entity and reference it @OneToOne(fetch=LAZY); cache only the LOB-free aggregate","Map the column as materialized text: @Lob @Nationalized String (String is cacheable) instead of java.sql.NClob","Remove the entity/property from second-level caching entirely","Verify no query cache stores entities with NClob attributes"],"exampleFix":"// before\n@Entity @Cacheable\nclass Article {\n    @Id Long id;\n    @Nationalized java.sql.NClob body; // disassemble -> UnsupportedOperationException\n}\n\n// after\n@Entity @Cacheable\nclass Article {\n    @Id Long id;\n    @OneToOne(fetch = FetchType.LAZY) ArticleBody body; // separate table\n}\n@Entity\nclass ArticleBody {\n    @Id Long id;\n    @Nationalized @Lob String body; // materialized, cacheable sibling","handlingStrategy":"validation","validationCode":"// startup check: refuse caching entities with live LOB fields\nfor (var binding : sessionFactory.getMetamodel().getEntities()) {\n    for (var attr : binding.getAttributes()) {\n        if (java.sql.NClob.class.isAssignableFrom(attr.getJavaType())\n                && binding.getJpaMetamodel()... /* entity is @Cacheable */) {\n            throw new IllegalStateException(\"Cached entity \" + binding.getName() + \" has NClob attribute \" + attr.getName());\n        }\n    }\n}","typeGuard":"static boolean isCacheableJavaType(Class<?> t) {\n    return !(java.sql.NClob.class.isAssignableFrom(t) || java.sql.Clob.class.isAssignableFrom(t) || java.sql.Blob.class.isAssignableFrom(t));\n}","tryCatchPattern":"catch (UnsupportedOperationException e) {\n    if (\"Clobs are not cacheable\".equals(e.getMessage()))\n        throw new IllegalStateException(\"Remove NClob attributes from cached entities or map them as String\", e);\n    throw e;\n}","preventionTips":["Never annotate LOB-bearing entities with @Cacheable","Model LOBs in separate non-cached entities referenced @OneToOne(lazy)","Map LOB columns as materialized String/byte[] when caching is required"],"tags":["hibernate","nclob","second-level-cache","mutability-plan","unsupportedoperation"],"backgroundTag":"lob-not-cacheable","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}