hibernate/hibernate-orm · error · AnnotationException
Association '${path}' is 'mappedBy' a property named '${mapp
Error message
Association '${path}' is 'mappedBy' a property named '${mappedBy}' of the target entity type '${type}' which is not a '@OneToOne' or '@ManyToOne' association What it means
For the non-owning (mappedBy) side of a @OneToOne, Hibernate inspects the property named by 'mappedBy' on the target entity. That property exists but its value is neither a @OneToOne nor a @ManyToOne, so there is no valid owning side to map back to, and AnnotationException is thrown during second-pass binding.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/boot/model/internal/OneToOneSecondPass.java:100
private void bindUnowned(Map<String, PersistentClass> persistentClasses, OneToOne oneToOne) {
oneToOne.setMappedByProperty( mappedBy );
final String targetEntityName = oneToOne.getReferencedEntityName();
final var targetEntity = persistentClasses.get( targetEntityName );
if ( targetEntity == null ) {
final String problem = annotatedEntity
? " which does not belong to the same persistence unit"
: " which is not an '@Entity' type";
throw new MappingException( "Association '" + getPath( propertyHolder, inferredData )
+ "' targets the type '" + targetEntityName + "'" + problem );
}
final var targetProperty = targetProperty( oneToOne, targetEntity );
final var targetPropertyValue = targetProperty.getValue();
if ( targetPropertyValue instanceof ManyToOne ) {
bindTargetManyToOne( persistentClasses, oneToOne, targetEntity, targetProperty );
}
else if ( !(targetPropertyValue instanceof OneToOne) ) {
throw new AnnotationException( "Association '" + getPath( propertyHolder, inferredData )
+ "' is 'mappedBy' a property named '" + mappedBy
+ "' of the target entity type '" + targetEntityName
+ "' which is not a '@OneToOne' or '@ManyToOne' association" );
}
checkMappedByType(
mappedBy,
targetPropertyValue,
oneToOne.getPropertyName(),
propertyHolder,
persistentClasses
);
}
private void bindTargetManyToOne(
Map<String, PersistentClass> persistentClasses,
OneToOne oneToOne,
PersistentClass targetEntity,
Property targetProperty) {View on GitHub (pinned to fad1729dce)
Solutions
- Annotate the mappedBy-referenced property on the target entity with @OneToOne (or @ManyToOne) so it becomes a valid owning side.
- Double-check which side owns the foreign key: the side WITHOUT mappedBy gets @JoinColumn; the inverse side gets mappedBy.
- If the target property is genuinely not an association, the mappedBy value is wrong — point it at the actual owning property name (see also the 'does not exist' variant of this error).
Example fix
// before: owning side is not an association
public class User {
@OneToOne(mappedBy = "user")
private Profile profile;
}
public class Profile {
private User user; // no annotation => not an owning side
}
// after
public class Profile {
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
} Defensive patterns
Strategy: validation
Validate before calling
// Check the mappedBy target property is an association of allowed kind
for (Class<?> entity : annotatedClasses) {
for (Field f : entity.getDeclaredFields()) {
OneToOne o2o = f.getAnnotation(OneToOne.class);
if (o2o == null || o2o.mappedBy().isEmpty()) continue;
Field inverse = f.getType().getDeclaredField(o2o.mappedBy());
if (inverse.getAnnotation(OneToOne.class) == null && inverse.getAnnotation(ManyToOne.class) == null) {
throw new IllegalStateException("mappedBy '" + o2o.mappedBy() + "' on " + f + " is not @OneToOne/@ManyToOne");
}
}
} Type guard
static boolean isValidOwningSide(Field f) {
return f.isAnnotationPresent(OneToOne.class) || f.isAnnotationPresent(ManyToOne.class);
} Try / catch
try {
SessionFactory sf = cfg.buildSessionFactory();
} catch (AnnotationException e) {
throw new IllegalStateException("Bidirectional mapping broken: " + e.getMessage(), e);
} Prevention
- Put @JoinColumn on the owning side and mappedBy on the inverse — never both
- Write one integration test per bidirectional relationship that boots the factory
When it happens
Trigger: @OneToOne(mappedBy = "profile") where Profile.profile exists but is a plain attribute, a @OneToMany, or an @Embedded; also when the owning side was annotated with the wrong cardinality (e.g. @OneToMany instead of @ManyToOne) so the inverse lookup finds a non-conforming value.
Common situations: Building a bidirectional one-to-one and annotating the wrong side as owner; copy-pasting a one-to-many pattern into a one-to-one; renaming/re-typing the owning property during refactoring while leaving mappedBy pointing at a now-basic field.
Related errors
- Association '${path}' is 'mappedBy' a property named '${mapp
- Association '${path}' targets the type '${type}' which does
- AttributeConverter class [%s] registered multiple times
- Duplicate named query '%s'
- Duplicate named stored procedure '{}'
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/1eaba6b136a8aa16.
Report an issue: GitHub.