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

  1. Remove @CollectionId from the @OneToMany — inverse one-to-many rows live in the target table and need no collection-row identifier
  2. 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)
  3. 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

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


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