hibernate/hibernate-orm · error · IllegalStateException

Bag is not a list:

Error message

Bag is not a list: 

What it means

PersistentBag can wrap any java.util.Collection; list-position operations go through bagAsList(), which requires the wrapped collection to actually be a List. If the bag was built around a non-List collection (for example a HashSet the application placed in the field), any list-style operation such as add(index, e) or get(index) throws IllegalStateException naming the offending collection class. The mapping says bag, the runtime wrapper is not list-backed, and list operations are therefore impossible.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentBag.java:94

	 *
	 * @param session The session
	 * @param coll The base elements.
	 */
	public PersistentBag(SharedSessionContractImplementor session, Collection<E> coll) {
		super( session );
		setCollection( coll );
		setInitialized();
		setDirectlyAccessible( true );
	}

	private void setCollection(Collection<E> bag) {
		this.collection = bag;
		this.bag = bag instanceof List<E> list ? list : null;
	}

	protected List<E> bagAsList() {
		if ( bag == null ) {
			throw new IllegalStateException( "Bag is not a list: " + collection.getClass().getName() );
		}
		return bag;
	}

	@Override
	public boolean isWrapper(Object collection) {
		return this.collection == collection;
	}

	@Override
	public boolean empty() {
		return collection.isEmpty();
	}

	@Override
	public Iterator<E> entries(CollectionPersister persister) {
		return collection.iterator();
	}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Declare bag-mapped fields as List and initialize them with ArrayList so list operations stay valid
  2. If set semantics are wanted, map the association as a Set (PersistentSet) instead of a bag
  3. Never call indexed operations (get/add at index) on bag-mapped collections
  4. Match field initialization to mapping semantics: List/ArrayList for bags and identifier bags

Example fix

// before
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private Collection<OrderLine> lines = new HashSet<>(); // bag wrapped around a Set -> bagAsList() throws

// after
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderLine> lines = new ArrayList<>();
Defensive patterns

Strategy: type-guard

Validate before calling

// before any list-style call on a bag-mapped collection
if (!(order.getLines() instanceof List)) {
    throw new IllegalStateException(
            "lines is bag-mapped around a non-List collection; indexed access unsupported");
}

Type guard

static boolean supportsIndexAccess(Collection<?> collection) {
    return collection instanceof List;
}

Prevention

When it happens

Trigger: A bag-mapped @OneToMany field declared as Collection but initialized with a non-List implementation (HashSet, ConcurrentLinkedQueue); Hibernate reuses the application-supplied collection when loading; later code casts or calls list operations (indexed add/get) on it.

Common situations: Bag-mapped collections initialized as new HashSet<>() by habit; switching collection implementations during refactoring; legacy <bag> mappings over set-like field types.

Related errors


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