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

  1. Add @OrderColumn to the List mappings - that classifies them as LIST, which can be fetched together
  2. Change one or both collections to Set (with LinkedHashSet/TreeSet to keep ordering)
  3. Split into two queries: fetch the parent list first, then load each bag separately (or via batch/subselect fetching)
  4. 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

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


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