{"record":{"id":"c893f3f8f70f8701","repo":"hibernate/hibernate-orm","slug":"ambiguous-persistent-property-methods-declared-by","errorCode":null,"errorMessage":"Ambiguous persistent property methods declared by '%s': '%s' and '%s' (mark one '@Transient')","messagePattern":"Ambiguous persistent property methods declared by '(.+?)': '(.+?)' and '(.+?)' \\(mark one '@Transient'\\)","errorType":"exception","errorClass":"MappingException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyContainer.java","lineNumber":204,"sourceCode":"\t\t\t\tpersistentAttributesFromGetters.put( name, getterDetails );\n\t\t\t}\n\n\t\t}\n\n\t\t// Check record components...\n\t\tfor ( int i = 0; i < recordComponents.size(); i++ ) {\n\t\t\tfinal var recordComponentDetails = recordComponents.get( i );\n\t\t\tif ( recordComponentDetails.hasDirectAnnotationUsage( Access.class ) ) {\n\t\t\t\tfinal String name = recordComponentDetails.getName();\n\t\t\t\tpersistentAttributeMap.put( name, recordComponentDetails );\n\t\t\t\tpersistentAttributesFromComponents.put( name, recordComponentDetails );\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate static void throwAmbiguousPropertyException(\n\t\t\tClassDetails classDetails, MethodDetails previous, MethodDetails getterDetails) {\n\t\tthrow new MappingException(\n\t\t\t\tString.format(\n\t\t\t\t\t\t\"Ambiguous persistent property methods declared by '%s': '%s' and '%s' (mark one '@Transient')\",\n\t\t\t\t\t\tclassDetails.getName(),\n\t\t\t\t\t\tprevious.getName(),\n\t\t\t\t\t\tgetterDetails.getName()\n\t\t\t\t),\n\t\t\t\tnew Origin( SourceType.ANNOTATION, classDetails.getName() )\n\t\t);\n\t}\n\n\t/**\n\t * Collects members \"backing\" an attribute based on the Class's \"default\" access-type\n\t */\n\tprivate static void collectPersistentAttributesUsingClassLevelAccessType(\n\t\t\tClassDetails classDetails,\n\t\t\tAccessType classLevelAccessType,\n\t\t\tMap<String, MemberDetails> persistentAttributeMap,\n\t\t\tMap<String,MethodDetails> persistentAttributesFromGetters,","sourceCodeStart":186,"sourceCodeEnd":222,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyContainer.java#L186-L222","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\npublic class Order {\n    public boolean isPaid() { return paid; }\n    public Boolean getPaid() { return paid; } // ambiguous\n}\n\n// after\npublic class Order {\n    public boolean isPaid() { return paid; }\n\n    @Transient\n    public Boolean getPaid() { return paid; } // not persistent\n}","handlingStrategy":"validation","validationCode":"// scan mapped classes for duplicate property accessors before bootstrap\nfor (PropertyDescriptor pd : Introspector.getBeanInfo(Order.class).getPropertyDescriptors()) {\n    Method read = pd.getReadMethod();\n    if (read != null && hasConflictingAccessor(Order.class, pd.getName(), read)) {\n        throw new IllegalStateException(\"Ambiguous accessors for property \" + pd.getName());\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    metadata = sources.buildMetadata();\n} catch (MappingException e) { // ambiguous-property MappingException carries Origin(ANNOTATION, class)\n    failBuild(\"Duplicate accessors in \" + e.getOrigin() + \": \" + e.getMessage());\n}","preventionTips":["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."],"tags":["hibernate","orm","mapping","ambiguous-property","accessors","transient"],"backgroundTag":"duplicate-property-accessors","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}