hibernate/hibernate-orm · error · MultipleBagFetchException
cannot simultaneously fetch multiple bags: {}
Error message
cannot simultaneously fetch multiple bags: {} What it means
The classic Hibernate multiple-bag limitation: while resolving fetches, two or more fetched plural attributes with CollectionClassification.BAG (a java.util.List mapping without @OrderColumn) were encountered on one query. Join-fetching multiple bags would require an ambiguous cartesian row product, so Hibernate aborts with MultipleBagFetchException naming the two conflicting roles.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/query/sqm/sql/spi/BaseSqmToSqlAstConverter.java:9091
if ( entityMappingType.getSuperMappingType() != null ) {
// A joined table group was created by an enabled entity graph or fetch profile,
// and it's of an inheritance subtype, so we should apply the discriminator
getCurrentClauseStack().push( Clause.FROM );
registerEntityNameUsage( actualTableGroup, EntityNameUse.TREAT,
entityMappingType.getEntityName() );
getCurrentClauseStack().pop();
}
}
}
if ( fetchable instanceof PluralAttributeMapping pluralAttributeMapping ) {
final var collectionClassification =
pluralAttributeMapping.getMappedType()
.getCollectionSemantics()
.getCollectionClassification();
if ( collectionClassification == CollectionClassification.BAG ) {
final var navigableRole = fetchable.getNavigableRole();
if ( currentBagRole != null ) {
throw new MultipleBagFetchException(
Arrays.asList( currentBagRole,
navigableRole.getNavigableName() )
);
}
currentBagRole = navigableRole.getNavigableName();
}
}
}
}
return fetch;
}
finally {
if ( incrementFetchDepth ) {
fetchDepth--;
}
if ( entityGraphTraversalState != null && traversalResult != null ) {
entityGraphTraversalState.backtrack( traversalResult );
}View on GitHub (pinned to fad1729dce)
Solutions
- Add @OrderColumn to the List mappings - that classifies them as LIST, which can be fetched together
- Change one or both collections to Set (with LinkedHashSet/TreeSet to keep ordering)
- Split into two queries: fetch the parent list first, then load each bag separately (or via batch/subselect fetching)
- Keep the second bag lazy and initialize it on demand, or use @Fetch(FetchMode.SUBSELECT) for the second query
Example fix
// before @OneToMany(mappedBy = "o", cascade = ALL) private List<Line> lines = new ArrayList<>(); // BAG @OneToMany(mappedBy = "o", cascade = ALL) private List<Note> notes = new ArrayList<>(); // BAG -> MultipleBagFetchException // after @OneToMany(mappedBy = "o", cascade = ALL) @OrderColumn(name = "pos") private List<Line> lines = new ArrayList<>(); // LIST - fetchable together
Defensive patterns
Strategy: validation
Validate before calling
// Before executing, verify at most one fetched attribute is a BAG
org.hibernate.metamodel.model.domain.EntityDomainType<E> et =
sessionFactory.getMetamodel().entity(E.class);
long bagFetches = fetchedAttributes.stream()
.filter(a -> et.getAttribute(a) instanceof jakarta.metamodel.PluralAttribute<?, ?, ?> pa
&& pa.getCollectionType() == jakarta.metamodel.PluralAttribute.CollectionType.LIST
&& !hasOrderColumn(a)) // hasOrderColumn: check your mapping metadata
.count();
if (bagFetches > 1) {
throw new IllegalStateException("Query join-fetches multiple bags - split the query or add @OrderColumn");
} Try / catch
try {
return query.getResultList();
} catch (org.hibernate.HibernateException e) {
if (e.getClass().getSimpleName().equals("MultipleBagFetchException")) {
return loadInTwoQueries(id -> session.createQuery(secondaryFetchHql, Line.class)
.setParameter("ids", id).getResultList());
}
throw e;
} Prevention
- Prefer Set or @OrderColumn List for associations you will join-fetch
- Fetch at most one collection per query; use batch or subselect fetching for the rest
- Review entity graphs before adding new collection attributes
When it happens
Trigger: HQL 'from X x join fetch x.list1 join fetch x.list2' where both are List mappings without @OrderColumn; entity graphs that fetch two bag associations; criteria roots with two fetch(..., JOIN) calls on bag attributes; cascading fetch of a bag inside another fetched association.
Common situations: Entities modeled with List everywhere (default OneToMany target); adding a second lazy collection and fetching it for a screen; entity graphs inherited from modules that later add bags; performance tuning that blindly converts lazy loads to join fetch.
Related errors
- Duplicate named entity graph '%s'
- Attribute '{}' is annotated '@Bag' and may not also be annot
- Attribute '{}' is annotated '@Bag' and may not also be annot
- Attribute '%s.%s' of type '%s' is annotated '@Bag' (bags are
- The 'root' parameter of the @NamedEntityGraph should be pass
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/d54ef23624330760.
Report an issue: GitHub.