hibernate/hibernate-orm · error · UnsupportedOperationException

CollectionType not supported as part of cache key!

Error message

CollectionType not supported as part of cache key!

What it means

CollectionType overrides Type.disassemble(Object, SessionFactoryImplementor) (CollectionType.java:261) to throw UnsupportedOperationException ('CollectionType not supported as part of cache key!'), because a collection is a role/fk construct, not a serializable value. The two-argument disassemble is used by DefaultCacheKeysFactory when building entity-id and natural-id cache keys, so the error means a collection-typed value ended up in a position that must be cache-key material (identifier, natural id, or a component used in one).

Source

Thrown at hibernate-core/src/main/java/org/hibernate/type/CollectionType.java:262

	@Override
	public Serializable disassemble(Object value, SharedSessionContractImplementor session, Object owner)
			throws HibernateException {
		//remember the uk value

		//This solution would allow us to eliminate the owner arg to disassemble(), but
		//what if the collection was null, and then later had elements added? seems unsafe
		//session.getPersistenceContext().getCollectionEntry( (PersistentCollection) value ).getKey();

		final Object key = getKeyOfOwner( owner, session );
		return key == null ? null
				: getPersister( session )
						.getKeyType()
						.disassemble( key, session, owner );
	}

	@Override
	public Serializable disassemble(Object value, SessionFactoryImplementor sessionFactory) throws HibernateException {
		throw new UnsupportedOperationException( "CollectionType not supported as part of cache key!" );
	}

	@Override
	public Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner)
			throws HibernateException {
		//we must use the "remembered" uk value, since it is
		//not available from the EntityEntry during assembly
		if ( cached == null ) {
			return null;
		}
		else {
			final Object key =
					getPersister( session )
							.getKeyType()
							.assemble( cached, session, owner);
			return resolveKey( key, session, owner );
		}
	}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Remove collection-valued members from identifiers and natural ids; key entities on scalar/basic values only.
  2. If the collection was meant to be data, move it to a normal association (one-to-many) keyed by the owner's id, not into the key.
  3. Audit generic caching code that calls disassemble(value, sessionFactory) and skip CollectionType/AnyType instances.
  4. Validate mappings at startup (SessionFactory schema validation pass or a custom MappingMetadata check) to catch key-position collections before runtime.

Example fix

// before
@Embeddable
public class NaturalKey implements Serializable {
    String code;
    @OneToMany(mappedBy = "doc")
    Set<Tag> tags; // collection inside a @NaturalId component
}

// after: keep the natural id scalar-only
@Embeddable
public class NaturalKey implements Serializable {
    String code;
}

@Entity
public class Doc {
    @Embedded @NaturalId NaturalKey key;
    @OneToMany(mappedBy = "doc")
    Set<Tag> tags; // ordinary association, not part of the key
}
Defensive patterns

Strategy: validation

Validate before calling

// Startup check: no collection-typed members inside id/natural-id components
for (EntityType<?> et : emf.getMetamodel().getEntities()) {
    et.getSingularAttributes().stream()
      .filter(a -> a.isId() || isNaturalId(et, a))
      .filter(a -> ((SingularAttribute<?, ?>) a).getType().getPersistenceType()
                   == Type.PersistenceType.EMBEDDABLE)
      .forEach(a -> {
          for (Attribute<?, ?> sub : ((EmbeddableType<?>) ((SingularAttribute<?, ?>) a).getType()).getAttributes()) {
              if (sub.isCollection()) {
                  throw new IllegalStateException(
                      et.getName() + "." + a.getName() + "." + sub.getName()
                      + ": collections cannot be part of a cache key");
              }
          }
      });
}

Prevention

When it happens

Trigger: Mapping a collection type where a scalar is required: an @Id/@EmbeddedId component containing a collection-typed member, a @NaturalId containing a collection, or custom code calling Type.disassemble(value, sessionFactory) on a collection role's type; also mismatched generic frameworks that treat any property type as cache-key-able.

Common situations: Hand-written hbm.xml with <composite-id> accidentally including a <set>; JPA entities where a @NaturalId-annotated embeddable gains a collection field during refactoring; generic audit/caching layers that disassemble every property type into cache keys.

Related errors


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/9a5cc3e9f672dabf. Report an issue: GitHub.