hibernate/hibernate-orm · error · AnnotationException

Property '${property}' uses *-to-many mapping with mappedBy

Error message

Property '${property}' uses *-to-many mapping with mappedBy '${mappedByProperty}' in the aggregate component class '${componentClassName}' within an array property, which is not allowed.

What it means

Same validation pass as the one-to-one case: AggregateComponentSecondPass.validateComponent rejects any Collection-typed property inside an array-bound aggregate that declares mappedBy (getMappedByProperty() != null), i.e. a bidirectional @OneToMany/@ManyToMany. Struct elements stored in arrays cannot be inverse sides of a relationship.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentSecondPass.java:203

			final Value value = property.getValue();
			if ( value instanceof Component comp ) {
				validateComponent( comp, qualify( basePath, property.getName() ), inArray );
			}
			else if ( value instanceof ToOne toOne ) {
				if ( inArray && toOne.getReferencedPropertyName() != null ) {
					throw new AnnotationException(
							"Property '" + qualify( basePath, property.getName() )
									+ "' uses one-to-one mapping with mappedBy '"
									+ toOne.getReferencedPropertyName()
									+ "' in the aggregate component class '"
									+ component.getComponentClassName()
									+ "' within an array property, which is not allowed."
					);
				}
			}
			else if ( value instanceof Collection collection ) {
				if ( inArray && collection.getMappedByProperty() != null ) {
					throw new AnnotationException(
							"Property '" + qualify( basePath, property.getName() )
									+ "' uses *-to-many mapping with mappedBy '"
									+ collection.getMappedByProperty()
									+ "' in the aggregate component class '"
									+ component.getComponentClassName()
									+ "' within an array property, which is not allowed."
					);
				}
				if ( inArray && collection.getCollectionTable() != null ) {
					throw new AnnotationException(
							"Property '" + qualify( basePath, property.getName() )
									+ "' defines a collection table '"
									+ collection.getCollectionTable()
									+ "' in the aggregate component class '"
									+ component.getComponentClassName()
									+ "' within an array property, which is not allowed."
					);
				}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Move the @OneToMany/@ManyToMany out of the embeddable onto the owning entity
  2. If the collection is really part of the struct value, model it as a nested aggregate array instead of an association
  3. Remove mappedBy if the collection must stay (unidirectional join from the struct side is usually still wrong - prefer moving it out)
  4. Stop using the embeddable inside arrays and map it as a first-level component

Example fix

// before: bidirectional many-side inside an array aggregate
@Embeddable
public class OrderLine {
    @OneToMany(mappedBy = "orderLine")      // rejected: mappedBy within array aggregate
    private List<Attachment> attachments;
}

@Entity
public class Order {
    @Array
    private List<OrderLine> lines;
}

// after: ownership lives on the entity; the aggregate keeps only value data
@Embeddable
public class OrderLine { /* value fields only */ }

@Entity
public class Attachment {
    @ManyToOne @JoinColumn(name = "order_id")
    private Order order;
}
Defensive patterns

Strategy: validation

Validate before calling

// Before boot: aggregates used in arrays must not contain mappedBy collections
static boolean aggregateHasNoMappedByCollections(Class<?> embeddable) {
    for (Field f : embeddable.getDeclaredFields()) {
        OneToMany otm = f.getAnnotation(OneToMany.class);
        ManyToMany mtm = f.getAnnotation(ManyToMany.class);
        if ((otm != null && !otm.mappedBy().isEmpty())
                || (mtm != null && !mtm.mappedBy().isEmpty())) {
            return false;
        }
    }
    return true;
}

Try / catch

try {
    final SessionFactory sf = new MetadataSources(standardServiceRegistry)
            .addAnnotatedClass(MyEntity.class)
            .buildMetadata()
            .buildSessionFactory();
} catch (AnnotationException | MappingException e) {
    throw new IllegalStateException("Invalid ORM mapping, aborting startup: " + e.getMessage(), e);
}

Prevention

When it happens

Trigger: An embeddable used inside an @Array or aggregate element-collection contains @OneToMany(mappedBy = ...) or @ManyToMany(mappedBy = ...), triggering the inArray validation during the second pass.

Common situations: Copying working entity patterns (bidirectional collections) into a new struct aggregate; embedding an existing @Embeddable with collection associations into an array column; schema-first designs where the struct was modeled like a mini-entity.

Related errors


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