hibernate/hibernate-orm · error · AnnotationException
Association '%s' of entity '%s' is 'mappedBy' a different en
Error message
Association '%s' of entity '%s' is 'mappedBy' a different entity and may not explicitly specify the '@JoinColumn'
What it means
This association is the INVERSE side of a bidirectional relationship ('mappedBy' points at the owning side), yet it also declares an explicit '@JoinColumn'. In a bidirectional association only the owning side defines the join column; the inverse side mirrors it, so Hibernate forbids the duplicate declaration.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedJoinColumn.java:114
// formulaColumn.setContext( buildingContext );
// formulaColumn.setPropertyHolder( propertyHolder );
// formulaColumn.setPropertyName( getRelativePath( propertyHolder, propertyName ) );
// formulaColumn.setJoins( joins );
formulaColumn.setParent( parent );
formulaColumn.bind();
return formulaColumn;
}
static AnnotatedJoinColumn buildJoinColumn(
JoinColumn joinColumn,
String mappedBy,
AnnotatedJoinColumns parent,
PropertyHolder propertyHolder,
PropertyData inferredData,
String defaultColumnSuffix) {
if ( joinColumn != null ) {
if ( mappedBy != null ) {
throw new AnnotationException(
String.format(
Locale.ROOT,
"Association '%s' of entity '%s' is 'mappedBy' a different entity and may not explicitly specify the '@JoinColumn'",
inferredData.getPropertyName(),
propertyHolder.getEntityName() )
);
}
return explicitJoinColumn( joinColumn, parent, inferredData, defaultColumnSuffix );
}
else {
return implicitJoinColumn( parent, inferredData, defaultColumnSuffix );
}
}
private static AnnotatedJoinColumn explicitJoinColumn(
JoinColumn joinColumn,
AnnotatedJoinColumns parent,
PropertyData inferredData,View on GitHub (pinned to fad1729dce)
Solutions
- Delete the @JoinColumn from the mappedBy (inverse) side named in the message.
- Put the @JoinColumn with the desired FK name on the OWNING side (the @ManyToOne/@OneToOne that does NOT use mappedBy).
- Verify the mappedBy string actually names the owning-side property, so the mapping is a valid bidirectional pair.
Example fix
// before
@Entity
class Order {
@OneToMany(mappedBy = "order")
@JoinColumn(name = "order_id") // illegal on inverse side
List<OrderItem> items;
}
// after
@Entity
class Order {
@OneToMany(mappedBy = "order")
List<OrderItem> items;
}
@Entity
class OrderItem {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "order_id") // owning side owns the column
Order order;
} Defensive patterns
Strategy: validation
Validate before calling
// Guard: inverse (mappedBy) sides must not declare join columns
for (Field f : cls.getDeclaredFields()) {
OneToMany otm = f.getAnnotation(OneToMany.class);
ManyToMany mtm = f.getAnnotation(ManyToMany.class);
boolean inverse = (otm != null && !otm.mappedBy().isEmpty())
|| (mtm != null && !mtm.mappedBy().isEmpty());
if (inverse && (f.isAnnotationPresent(JoinColumn.class) || f.isAnnotationPresent(JoinColumns.class))) {
throw new IllegalStateException(cls.getName() + "." + f.getName()
+ " is mappedBy and must not declare @JoinColumn");
}
} Try / catch
try {
factory = cfg.buildSessionFactory();
} catch (AnnotationException e) {
if (e.getMessage().contains("may not explicitly specify the '@JoinColumn'")) {
// strip the @JoinColumn from the field named in the message
fixInverseSideJoinColumn(e.getMessage());
}
throw e;
} Prevention
- Rule of thumb: @JoinColumn lives on the @ManyToOne/@OneToOne side; mappedBy side stays annotation-free.
- When converting unidirectional to bidirectional, delete the old @JoinColumn in the same commit.
- Let a static-analysis or ArchUnit rule flag mappedBy fields carrying @JoinColumn.
When it happens
Trigger: '@OneToMany(mappedBy = "order") @JoinColumn(name = "oid")' on the collection side while the @ManyToOne side owns the FK; a @ManyToMany(mappedBy=...) with a @JoinTable/@JoinColumn; mappedBy set to a non-null value and joinColumn != null when buildJoinColumn is invoked.
Common situations: Adding @JoinColumn to the 'child list' side believing it fixes a FK name (it does not — name it on the @ManyToOne side); converting unidirectional to bidirectional and forgetting to delete the old @JoinColumn; copy-paste between owning and inverse sides.
Related errors
- Association '%s' is 'mappedBy' a property '%s' of entity '%s
- Association '%s' is 'mappedBy' a property '%s' of entity '%s
- Association '%s' is 'mappedBy' a property '%s' of entity '%s
- Class '<componentClassName>' is an '@Embeddable' type and ma
- Property '<propertyName>' may not be annotated '@BatchSize'
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/f0dce07ee2ceab16.
Report an issue: GitHub.