hibernate/hibernate-orm · error · AnnotationException
Association '{}' in entity '{}' is annotated '@MapsId' but r
Error message
Association '{}' in entity '{}' is annotated '@MapsId' but refers to a property '{}' which has an explicit column mapping What it means
'@MapsId' tells Hibernate to derive the association's join column from the mapped identifier property's column. That derivation only works when the id property uses the default/implicit column name; if the named id property has an explicit @Column mapping, the derived join-column name would silently disagree with it, so Hibernate (as a non-spec convenience it controls strictly) throws instead.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedJoinColumn.java:394
mappingColumn != null && mappingColumn.isUnique(),
false
);
}
private String defaultColumnName(int columnIndex, PersistentClass referencedEntity, String logicalReferencedColumn) {
final var parent = getParent();
if ( parent.hasMapsId() ) {
// infer the join column of the association
// from the name of the mapped primary key
// column (this is not required by the JPA
// spec) and is arguably backwards, given
// the name of the @MapsId annotation, but
// it's better than just having two different
// column names which disagree
final var column = parent.resolveMapsId().getValue().getColumns().get( columnIndex );
// return column.getQuotedName();
if ( column.isExplicit() ) {
throw new AnnotationException( "Association '" + parent.getPropertyName()
+ "' in entity '" + parent.getPropertyHolder().getEntityName()
+ "' is annotated '@MapsId' but refers to a property '"
+ parent.getMapsId() + "' which has an explicit column mapping" );
}
}
// else {
return parent.buildDefaultColumnName( referencedEntity, logicalReferencedColumn );
// }
}
private String defaultAnyKeyColumnName() {
final var context = getBuildingContext();
final var buildingOptions = context.getBuildingOptions();
final Identifier logicalColumnName =
buildingOptions.getImplicitNamingStrategy()
.determineAnyKeyColumnName( new ImplicitAnyKeyColumnNameSource() {
private final AttributePath attributePath =
AttributePath.parse( getParent().getPropertyName() );View on GitHub (pinned to fad1729dce)
Solutions
- Remove the explicit @Column from the identifier property named in the message and let the association's @JoinColumn define the shared column name.
- Or keep the explicit @Column and drop @MapsId, mapping the FK manually: '@Column(name="cust_id", insertable=false, updatable=false)' on the id plus a plain @OneToOne with @JoinColumn.
- Make sure only ONE side names the column — either the id property or the association's @JoinColumn, never both.
Example fix
// before
@Entity
class Person {
@Id
@Column(name = "cust_id") // explicit -> conflicts with @MapsId derivation
Long id;
@OneToOne(fetch = FetchType.LAZY)
@MapsId
Customer customer;
}
// after
@Entity
class Person {
@Id
Long id; // implicit name; derived from the join column
@OneToOne(fetch = FetchType.LAZY)
@MapsId
@JoinColumn(name = "cust_id")
Customer customer;
} Defensive patterns
Strategy: validation
Validate before calling
// Guard: an explicitly mapped id property must not be @MapsId-derived
for (Field f : cls.getDeclaredFields()) {
if (f.isAnnotationPresent(ManyToOne.class) || f.isAnnotationPresent(OneToOne.class)) {
MapsId mapsId = f.getAnnotation(MapsId.class);
if (mapsId != null && !mapsId.value().isEmpty()) {
Field idField = cls.getDeclaredField(mapsId.value());
if (idField != null && idField.getAnnotation(Column.class) != null
&& idField.getAnnotation(Column.class).name() != null
&& !idField.getAnnotation(Column.class).name().isEmpty()) {
throw new IllegalStateException("Explicit @Column on id conflicts with @MapsId on " + f.getName());
}
}
}
} Try / catch
try {
Metadata md = sources.buildMetadata();
} catch (AnnotationException e) {
// 'explicit column mapping' -> remove @Column from the id property or drop @MapsId
throw newConfigurationException("Derived-id column conflict", e);
} Prevention
- With @MapsId, name the shared column in exactly one place: the association's @JoinColumn.
- Never copy a tutorial that shows @Column on the id AND @MapsId on the association.
- Cover every derived-id entity with a schema-generation test (drop/create) in CI.
When it happens
Trigger: '@Id @Column(name = "cust_id") Long id;' together with '@OneToOne @MapsId Customer customer;' on the same entity; @MapsId pointing at an @EmbeddedId attribute that declares an explicit @Column. Detected in AnnotatedJoinColumn when parent.hasMapsId() and the resolved mapped column isExplicit().
Common situations: Shared-primary-key (@OneToOne derived-id) patterns where the id column was renamed explicitly; retrofitting @MapsId onto an existing entity that already had a custom id column name; following a tutorial that adds both @Column on the id and @MapsId on the association.
Related errors
- Identifier field '{}' named in '@MapsId' does not exist in e
- Class '<componentClassName>' is an '@Embeddable' type and ma
- Property '<propertyName>' may not be annotated '@BatchSize'
- One to many association '<propertyName>' was annotated '@Col
- Collection '<propertyName>' was annotated '@Collate'
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/cb62222724c44168.
Report an issue: GitHub.