hibernate/hibernate-orm · error · MappingException
Ambiguous persistent property methods declared by '%s': '%s'
Error message
Ambiguous persistent property methods declared by '%s': '%s' and '%s' (mark one '@Transient')
What it means
During attribute discovery PropertyContainer collects property accessors; when two method-backed candidates resolve to the same persistent property name, throwAmbiguousPropertyException throws a MappingException naming both methods, with Origin(ANNOTATION, className). Hibernate cannot pick which accessor defines the property, so it demands you disambiguate with @Transient.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyContainer.java:204
persistentAttributesFromGetters.put( name, getterDetails );
}
}
// Check record components...
for ( int i = 0; i < recordComponents.size(); i++ ) {
final var recordComponentDetails = recordComponents.get( i );
if ( recordComponentDetails.hasDirectAnnotationUsage( Access.class ) ) {
final String name = recordComponentDetails.getName();
persistentAttributeMap.put( name, recordComponentDetails );
persistentAttributesFromComponents.put( name, recordComponentDetails );
}
}
}
private static void throwAmbiguousPropertyException(
ClassDetails classDetails, MethodDetails previous, MethodDetails getterDetails) {
throw new MappingException(
String.format(
"Ambiguous persistent property methods declared by '%s': '%s' and '%s' (mark one '@Transient')",
classDetails.getName(),
previous.getName(),
getterDetails.getName()
),
new Origin( SourceType.ANNOTATION, classDetails.getName() )
);
}
/**
* Collects members "backing" an attribute based on the Class's "default" access-type
*/
private static void collectPersistentAttributesUsingClassLevelAccessType(
ClassDetails classDetails,
AccessType classLevelAccessType,
Map<String, MemberDetails> persistentAttributeMap,
Map<String,MethodDetails> persistentAttributesFromGetters,View on GitHub (pinned to fad1729dce)
Solutions
- Mark one of the two methods @Transient so only the other is persistent.
- Delete the duplicate accessor and keep a single canonical form (isX for primitive boolean, getX otherwise).
- Rename one method if both must stay on the API but only one is persistent.
- Check for generated code (Lombok/IDE) re-adding the removed accessor and exclude it.
Example fix
// before
public class Order {
public boolean isPaid() { return paid; }
public Boolean getPaid() { return paid; } // ambiguous
}
// after
public class Order {
public boolean isPaid() { return paid; }
@Transient
public Boolean getPaid() { return paid; } // not persistent
} Defensive patterns
Strategy: validation
Validate before calling
// scan mapped classes for duplicate property accessors before bootstrap
for (PropertyDescriptor pd : Introspector.getBeanInfo(Order.class).getPropertyDescriptors()) {
Method read = pd.getReadMethod();
if (read != null && hasConflictingAccessor(Order.class, pd.getName(), read)) {
throw new IllegalStateException("Ambiguous accessors for property " + pd.getName());
}
} Try / catch
try {
metadata = sources.buildMetadata();
} catch (MappingException e) { // ambiguous-property MappingException carries Origin(ANNOTATION, class)
failBuild("Duplicate accessors in " + e.getOrigin() + ": " + e.getMessage());
} Prevention
- Pick one boolean accessor form per property (isX for primitive boolean).
- Delete IDE/Lombok-generated duplicates rather than stacking them.
- Add @Transient the moment you keep a convenience getter that overlaps a mapped one.
When it happens
Trigger: A class exposes two getter forms for one property, e.g. boolean isFlag() plus Boolean getFlag(); or an overloaded getter pair; or a getter contributed twice via class + implemented interface so both MethodDetails survive collection. Raised while the PropertyContainer is built for the annotated class.
Common situations: Lombok @Getter on a boolean generating isX() while a hand-written getX() also exists (or vice versa); IDE auto-generating the second accessor form; interfaces declaring getters that classes redeclare; refactoring boolean wrappers without deleting the old accessor.
Related errors
- Identifier property '" + getPath( holder, data ) + "' cannot
- Attribute '%s' of entity '%s' is mapped by association '%s'
- Identifier attribute '%s' of entity '%s' has type '%s' but i
- Attribute '%s' of entity '%s' is mapped by association '%s'
- Property '" + qualify( classDetails.getName(), attributeMemb
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/c893f3f8f70f8701.
Report an issue: GitHub.