hibernate/hibernate-orm · error · UnsupportedOperationException
Blobs are not cacheable
Error message
Blobs are not cacheable
What it means
BlobJavaType's mutability plan deliberately refuses to disassemble java.sql.Blob values: a Blob is a live handle on the JDBC connection/locator and cannot be serialized into the second-level cache. When an entity holding a Blob attribute is put into the shared cache, disassemble throws this UnsupportedOperationException ('Blobs are not cacheable').
Source
Thrown at hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BlobJavaType.java:55
public class BlobJavaType extends AbstractClassJavaType<Blob> {
public static final BlobJavaType INSTANCE = new BlobJavaType();
public static class BlobMutabilityPlan implements MutabilityPlan<Blob> {
public static final BlobMutabilityPlan INSTANCE = new BlobMutabilityPlan();
@Override
public boolean isMutable() {
return false;
}
@Override
public Blob deepCopy(Blob value) {
return value;
}
@Override
public Serializable disassemble(Blob value, SharedSessionContract session) {
throw new UnsupportedOperationException( "Blobs are not cacheable" );
}
@Override
public Blob assemble(Serializable cached, SharedSessionContract session) {
throw new UnsupportedOperationException( "Blobs are not cacheable" );
}
}
public BlobJavaType() {
super( Blob.class, BlobMutabilityPlan.INSTANCE, IncomparableComparator.INSTANCE );
}
@Override
public boolean isInstance(Object value) {
return value instanceof Blob;
}
@OverrideView on GitHub (pinned to fad1729dce)
Solutions
- Map the attribute as byte[] instead of java.sql.Blob (fully materialized, cacheable)
- Keep Blob but exclude the entity from the second-level cache (remove @Cacheable/@Cache)
- Split the LOB into a separate non-cached entity and cache only the metadata part
- Alternatively use a converter that materializes byte[] for the cached form
Example fix
// before
@Entity
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Attachment {
@Id private Long id;
private Blob content; // disassemble -> 'Blobs are not cacheable'
}
// after
@Entity
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Attachment {
@Id private Long id;
private byte[] content; // materialized, cache-safe
} Defensive patterns
Strategy: fallback
Validate before calling
// startup audit: no cached entity may hold java.sql.Blob/Clob attributes
for (Class<?> entity : annotatedEntities) {
if (entity.isAnnotationPresent(Cacheable.class)
|| entity.isAnnotationPresent(org.hibernate.annotations.Cache.class)) {
for (Field f : entity.getDeclaredFields()) {
if (Blob.class.isAssignableFrom(f.getType()) || Clob.class.isAssignableFrom(f.getType())) {
throw new MappingException("Cached entity " + entity + " has LOB field " + f);
}
}
}
} Type guard
static boolean isCacheSafeLobMapping(Class<?> fieldType) {
return !java.sql.Blob.class.isAssignableFrom(fieldType)
&& !java.sql.Clob.class.isAssignableFrom(fieldType); // byte[]/String are fine
} Try / catch
// no meaningful retry: catch to convert into a clear configuration error
try {
session.persist(attachment);
} catch (UnsupportedOperationException e) {
if (String.valueOf(e.getMessage()).contains("Blobs are not cacheable")) {
throw new MappingException("Remove " + Attachment.class.getSimpleName()
+ " from the 2nd-level cache or map its LOB as byte[]", e);
}
throw e;
} Prevention
- Map LOBs as byte[] when the entity is cached or the payload is small
- Reserve java.sql.Blob for streaming large LOBs on deliberately non-cached entities
- Audit cached entities for Blob/Clob/NClob fields whenever enabling 2LC
- Split large LOBs into child entities and cache the parent only
When it happens
Trigger: An entity with a java.sql.Blob attribute is annotated @Cacheable / included in a @Cache region; the first flush/load that writes the entity into the second-level cache invokes disassemble and fails.
Common situations: Turning on second-level cache (or adding the entity to a region) for legacy entities that map LOBs as java.sql.Blob; enabling query caching which caches entity identifiers/data; moving from byte[] to Blob mapping for streaming while keeping the cache annotation.
Related errors
- Unable to set BLOB bytes after creation
- Could not create JDBC Blob
- Underlying stream does not allow reset
- Start position 1-based; must be 1 or more.
- Length must be great-than-or-equal to zero.
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/9103c550ddad16ba.
Report an issue: GitHub.