hibernate/hibernate-orm · critical · MappingException
one-to-many collections with identifiers are not supported
Error message
one-to-many collections with identifiers are not supported
What it means
In the AbstractCollectionPersister constructor, an identified collection (idbag semantics, @CollectionId / <idbag>) that is also a one-to-many is rejected with MappingException. Collection-table identifiers exist only for collections of values (element/idbag); a one-to-many stores rows in the target entity's table via FK and has no join-row identity to assign, so the combination is invalid.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java:447
i++;
}
indexContainsFormula = hasFormula;
}
else {
indexContainsFormula = false;
indexColumnIsGettable = null;
indexColumnIsSettable = null;
indexFormulaTemplates = null;
indexFormulas = null;
indexType = null;
indexColumnNames = null;
indexColumnAliases = null;
}
final boolean hasIdentifier = collectionBootDescriptor.isIdentified();
if ( hasIdentifier ) {
if ( collectionBootDescriptor.isOneToMany() ) {
throw new MappingException( "one-to-many collections with identifiers are not supported" );
}
//noinspection ConstantConditions
final var idCollection = (IdentifierCollection) collectionBootDescriptor;
identifierType = idCollection.getIdentifier().getType();
final var idColumn = idCollection.getIdentifier().getColumns().get(0);
identifierColumnName = idColumn.getQuotedName( dialect );
identifierColumnAlias = idColumn.getAlias( dialect );
identifierGenerator = createGenerator( creationContext, idCollection );
}
else {
identifierType = null;
identifierColumnName = null;
identifierColumnAlias = null;
identifierGenerator = null;
}
isLazy = collectionBootDescriptor.isLazy();
isExtraLazy = collectionBootDescriptor.isExtraLazy();View on GitHub (pinned to fad1729dce)
Solutions
- Remove @CollectionId from the @OneToMany — inverse one-to-many rows live in the target table and need no collection-row identifier
- If you need an independent join table with its own surrogate id and entity rows, model it as @ManyToMany or as an explicit association entity (@Entity join table with its own id and two @ManyToOne)
- If you need identified rows of values (not entities), use @ElementCollection + @CollectionId (idbag) instead of @OneToMany
Example fix
// before
@OneToMany(mappedBy = "order")
@CollectionId(columns = @Column(name = "line_id"), generator = "seq", type = Long.class)
private List<OrderLine> lines; // MappingException: one-to-many collections with identifiers are not supported
// after
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderLine> lines; // plain inverse collection
// or explicit association entity if you need join-row identity:
// @Entity class OrderLineLink { @Id Long id; @ManyToOne Order order; @ManyToOne OrderLine line; } Defensive patterns
Strategy: validation
Validate before calling
// Startup scan: @OneToMany + @CollectionId is an invalid combination per AbstractCollectionPersister
for (Class<?> c : scannedEntityClasses) {
for (java.lang.reflect.Field f : c.getDeclaredFields()) {
if (f.isAnnotationPresent(OneToMany.class)
&& (f.isAnnotationPresent(org.hibernate.annotations.CollectionId.class)
|| f.isAnnotationPresent(org.hibernate.annotations.CollectionIdJdbcType.class))) {
throw new IllegalStateException("Field " + f + " combines @OneToMany with a collection id, which is unsupported");
}
}
} Try / catch
try {
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (MappingException e) {
if (e.getMessage() != null && e.getMessage().contains("one-to-many collections with identifiers")) {
throw new IllegalStateException("Remove @CollectionId from the @OneToMany or remodel as @ManyToMany/association entity", e);
}
throw e;
} Prevention
- Reserve @CollectionId for @ElementCollection-style idbags only, never associations
- Audit legacy HBM <idbag> mappings for embedded <one-to-many> elements before migrating
- Use an explicit association @Entity with its own id when join-row identity is genuinely required
When it happens
Trigger: A @OneToMany field also carrying @CollectionId/@CollectionIdJdbcType (or hbm.xml <idbag> containing <one-to-many>); attempting to give the join row of an inverse one-to-many its own surrogate id.
Common situations: Migrating legacy HBM <idbag> mappings forward where they contained <one-to-many>; developers adding @CollectionId to a @OneToMany to get stable row ids for the collection; mixing idbag examples (written for element collections) into associations.
Related errors
- must be an BeforeExecutionGenerator
- Unknown collection: {}
- Unable to resolve mapped-by path : (%s) %s
- Could not resolve named query '{}' for loading collection '{
- The {storageEngine} storage engine is not supported
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/4e37d615d8fb4cb4.
Report an issue: GitHub.