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

  1. Annotate the mappedBy-referenced property on the target entity with @OneToOne (or @ManyToOne) so it becomes a valid owning side.
  2. Double-check which side owns the foreign key: the side WITHOUT mappedBy gets @JoinColumn; the inverse side gets mappedBy.
  3. 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

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


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/1eaba6b136a8aa16. Report an issue: GitHub.