Collections

This section documents Hibernate ORM 7.4.x (User Guide, Introduction, Query Language Guide, Data Repositories Guide), Jakarta Persistence 3.2, Hibernate Search 8.4.x, and the Hibernate Validator / Hibernate Reactive references — which are the references these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against those official docs before being relied on in production.

Three older reference books were consulted as bibliography only while preparing these pages and are not the primary or main source for any page. All three predate Jakarta Persistence 3.2 and Hibernate ORM 6/7 (the javax.persistencejakarta.persistence namespace change, the ORM 6 query-engine rewrite, the Hibernate Search 6+ Elasticsearch backend), so the official documentation above wins on any discrepancy.

This section’s bibliography lists the reference material consulted while preparing these pages.

Hibernate wraps every mapped collection field in its own persistent collection implementation (PersistentBag, PersistentList, PersistentSet, PersistentMap) that tracks additions/removals for dirty checking. This page covers the collection interfaces Hibernate supports, @ElementCollection for collections of non-entity values, and the bag-semantics trade-off that trips up more people than any other collection detail.

List, Set, Map, and sorted collections

Declared type Hibernate behavior

List<T> (no @OrderColumn)

Treated as a bag — see below — unless @OrderColumn is present, in which case insertion order is persisted in a dedicated index column.

Set<T>

No duplicates, no defined order (unless @OrderBy is added); backed by a HashSet (needs correct equals()/hashCode() on the element type) or, with @SortNatural/@SortComparator, a TreeSet.

Map<K, V>

A key/value association; the key column is declared with @MapKeyColumn (a basic key) or @MapKey (using another entity attribute as the key) or @MapKeyJoinColumn (an entity key).

SortedSet<T> / SortedMap<K, V>

Ordered in memory via @SortNatural (the elements'/keys' natural ordering) or @SortComparator(MyComparator.class). This is in-memory Java-side sorting via a TreeSet/TreeMap, distinct from @OrderBy, which asks the database to ORDER BY on select.

@ElementCollection

For a collection of basic values or embeddables that have no identity of their own — no @Entity, just a value stored in its own table keyed by the owner’s foreign key:

@Entity
public class Book {
    @ElementCollection
    @CollectionTable(name = "book_tag", joinColumns = @JoinColumn(name = "book_id"))
    @Column(name = "tag")
    private Set<String> tags = new HashSet<>();

    @ElementCollection
    @CollectionTable(name = "book_translation", joinColumns = @JoinColumn(name = "book_id"))
    @MapKeyColumn(name = "locale")
    private Map<String, String> translatedTitles = new HashMap<>();

    @ElementCollection
    @CollectionTable(name = "book_review", joinColumns = @JoinColumn(name = "book_id"))
    private List<@Embeddable Review> reviews = new ArrayList<>();
}

An @ElementCollection has no cascading options to configure — the contained values are always fully owned by the parent entity (they cannot be shared, referenced from elsewhere, or outlive their owner), so every add/ remove is unconditionally reflected as an INSERT/DELETE on the collection table.

@OrderColumn, @OrderBy, and @MapKey*

  • @OrderColumn — persists a dedicated integer index column and reconstructs a List in exactly that order, supporting index-based operations (list.set(3, …​)) without reordering the rest of the table.

  • @OrderBy("title ASC") — asks the database to sort on SELECT, referencing entity attribute names (not column names); the order is not stored anywhere, only applied at read time. Cheaper to write (no index column to maintain) but list index operations are not meaningful, since re-fetching can reorder elements if the sort key changes.

  • @MapKeyColumn / @MapKey / @MapKeyJoinColumn — the three ways to say what a `Map’s key comes from: a dedicated column value, another attribute of the value entity, or a to-one association used as the key.

Bag vs. list semantics — the performance trade-off

A plain List<T> with no @OrderColumn is a bag: an unordered collection that permits duplicates, represented internally as PersistentBag. Bags are the cause of a specific, easy-to-hit runtime error: a query cannot JOIN FETCH two bags of the same root entity in one query — Hibernate cannot tell which rows of the Cartesian product belong to which parent-collection element once two unordered bags are combined, and throws MultipleBagFetchException. The fixes, in order of preference:

  • Change one of the two collections to a Set (if duplicates genuinely cannot occur) — the most common fix.

  • Add @OrderColumn to make it a true ordered list instead of a bag.

  • Fetch the two collections in separate queries (or via @BatchSize, see Fetching & N+1) instead of both in one JOIN FETCH.

A Set also avoids a related, subtler bag cost: adding one element to a bag-mapped @OneToMany/ @ManyToMany collection sometimes forces Hibernate to delete and re-insert the entire collection’s join rows to preserve correctness, because a bag has no identity per row to update selectively — another reason Set (or an ordered List via @OrderColumn) is generally the safer default over a bare List.

Mapping to SQL arrays

Where the database and dialect support a native array column type (PostgreSQL’s text[], integer[], …​), a basic collection of a simple element type can map directly to it via @JdbcTypeCode(SqlTypes.ARRAY) on a String[]/Integer[] field, avoiding a separate @ElementCollection table entirely for small, denormalized, rarely-queried-by-element lists.