hibernate/hibernate-orm · error · IllegalArgumentException
Expected table group with table joins to have an entity type
Error message
Expected table group with table joins to have an entity typed model part but got:
What it means
To emit a locking clause for a table group with joins, the strategy must resolve an EntityPersister from the group's model part. Only three shapes are supported: an EntityPersister itself, a PluralAttributeMapping (via its element persister), or an EntityAssociationMapping (via the associated entity). Any other model part — e.g. a table group backed by a values clause, CTE, or function source — hits the else branch and throws IllegalArgumentException naming what was found. This is an internal capability gap of the locking translator.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/sql/ast/internal/StandardLockingClauseStrategy.java:288
return tableMapping;
}
}
}
throw new IllegalArgumentException( "Couldn't find subclass index for joined table reference " + joinedTableReference );
}
private EntityPersister determineEntityPersister(ModelPartContainer modelPart) {
if ( modelPart instanceof EntityPersister entityPersister ) {
return entityPersister;
}
else if ( modelPart instanceof PluralAttributeMapping pluralAttributeMapping ) {
return pluralAttributeMapping.getCollectionDescriptor().getElementPersister();
}
else if ( modelPart instanceof EntityAssociationMapping entityAssociationMapping ) {
return entityAssociationMapping.getAssociatedEntityMappingType().getEntityPersister();
}
else {
throw new IllegalArgumentException( "Expected table group with table joins to have an entity typed model part but got: " + modelPart );
}
}
private String[] determineKeyColumnNames(TableGroup tableGroup) {
if ( tableGroup instanceof LockingTableGroup lockingTableGroup ) {
return extractColumnNames( lockingTableGroup.getKeyColumnMappings() );
}
else if ( tableGroup.getModelPart() instanceof EntityPersister entityPersister ) {
return entityPersister.getIdentifierColumnNames();
}
else if ( tableGroup.getModelPart() instanceof PluralAttributeMapping pluralAttributeMapping ) {
return extractColumnNames( pluralAttributeMapping.getKeyDescriptor() );
}
else if ( tableGroup.getModelPart() instanceof EntityAssociationMapping entityAssociationMapping ) {
return extractColumnNames( entityAssociationMapping.getAssociatedEntityMappingType().getIdentifierMapping() );
}
else {
throw new AssertionFailure( "Unable to determine columns for locking" );View on GitHub (pinned to fad1729dce)
Solutions
- Remove the pessimistic lock from that part of the query — lock only entity roots
- Restructure the query so the locked table group corresponds to a real entity mapping
- For read-only derived tables, mark them read-only and keep locking on the entity side
- Upgrade Hibernate and check the release notes — unsupported-model-part cases in locking have been progressively handled
Example fix
// before
List<Order> rows = em.createQuery(
"select o from Order o join OrderValues v on v.orderId = o.id where v.amount > :a",
Order.class)
.setLockMode(LockModeType.PESSIMISTIC_WRITE) // v has non-entity model part
.getResultList();
// after
List<Order> rows = em.createQuery(
"select o from Order o where o.total > :a", Order.class)
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.getResultList(); Defensive patterns
Strategy: validation
Validate before calling
// apply pessimistic locks only to entity roots
for (Map.Entry<String, LockModeType> e : requestedLocks.entrySet()) {
SqmRoot<?> root = (SqmRoot<?>) query.getSqmQuery().getRoot(e.getKey());
if (root == null) {
throw new IllegalArgumentException("lock alias " + e.getKey() + " is not an entity root");
}
} Try / catch
try {
return query.getResultList();
}
catch (IllegalArgumentException e) {
if (e.getMessage() != null && e.getMessage().contains("entity typed model part")) {
// unsupported construct under locking: drop the lock or restructure the query
return unlockedQuery().getResultList();
}
throw e;
} Prevention
- Do not combine CTE/values/subselect table groups with pessimistic locking
- Lock only entity roots; mark derived/read-only joins as such
- Re-run exotic reporting queries without locks after Hibernate upgrades
When it happens
Trigger: A query with pessimistic locking (especially FOR UPDATE OF on dialects that render table-specific clauses) whose table group is derived from a non-entity source: join to a VALUES/CTE construct, a @Subselect/mapped subselect entity usage, or a generateSeries/values-valued path.
Common situations: Exotic reporting or batch queries that mix CTE/values joins with LockModeType.PESSIMISTIC_WRITE; frameworks generating synthetic table groups; mapped views (@Subselect) combined with lock hints.
Related errors
- Can't emulate offset clause in subquery
- not null makes no sense for in expression
- Couldn't find subclass index for joined table reference
- Can't interpret expression because no parameter bindings are
- Unsupported Expression type (expected ColumnReference) : %s
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/98af9f0ff26dede0.
Report an issue: GitHub.