hibernate/hibernate-orm · error · AnnotationException

Collection '{}' is annotated both '@MapKey' and '@MapKeyColu

Error message

Collection '{}' is annotated both '@MapKey' and '@MapKeyColumn'

What it means

A Map-valued collection must define exactly one key-mapping strategy: @MapKey (key is a property of the target entity or the target's primary key) or @MapKeyColumn (key is an explicit column of the collection table). When hasMapKeyProperty is already true and @MapKeyColumn is also present, checkMapKeyColumn throws this AnnotationException.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java:1144

		collection.setInverse( isUnowned );

		//TODO reduce tableBinder != null and oneToMany
		scheduleSecondPass( isUnowned );
		getMetadataCollector().addCollectionBinding( collection );
		bindProperty();
	}

	private boolean isUnownedCollection() {
		return mappedBy != null;
	}

	private boolean isMutable() {
		return !property.hasDirectAnnotationUsage( Immutable.class );
	}

	private void checkMapKeyColumn() {
		if ( property.hasDirectAnnotationUsage( MapKeyColumn.class ) && hasMapKeyProperty ) {
			throw new AnnotationException( "Collection '" + qualify( propertyHolder.getPath(), propertyName )
					+ "' is annotated both '@MapKey' and '@MapKeyColumn'" );
		}
	}

	private void scheduleSecondPass(boolean isMappedBy) {
		final var metadataCollector = getMetadataCollector();
		//many to many may need some second pass information
		if ( !oneToMany && isMappedBy ) {
			metadataCollector.addMappedBy( getElementType().getName(), mappedBy, propertyName );
		}

		if ( inheritanceStatePerClass == null) {
			throw new AssertionFailure( "inheritanceStatePerClass not set" );
		}
		metadataCollector.addSecondPass( getSecondPass(), !isMappedBy );
	}

	private void bindOptimisticLock(boolean isMappedBy) {

View on GitHub (pinned to fad1729dce)

Solutions

  1. If the key is a column in the collection table, keep only @MapKeyColumn
  2. If the key is a target-entity property, keep only @MapKey(name = "...")
  3. For element-collection maps, also consider @MapKeyJoinColumn for entity-typed keys instead of stacking annotations

Example fix

// before
@ManyToMany
@MapKey(name = "isbn")          // key = property of target
@MapKeyColumn(name = "key_col") // error: conflicts with @MapKey
Map<String, Book> books;

// after
@ManyToMany
@MapKeyColumn(name = "key_col") // choose ONE key mapping strategy
Map<String, Book> books;
Defensive patterns

Strategy: validation

Validate before calling

// Reject @MapKey together with @MapKeyColumn
static void checkMapKeyConflicts(Class<?>... entities) {
    for ( Class<?> c : entities ) {
        for ( Field f : c.getDeclaredFields() ) {
            if ( f.isAnnotationPresent( MapKey.class )
                    && f.isAnnotationPresent( MapKeyColumn.class ) ) {
                throw new IllegalStateException( "Both @MapKey and @MapKeyColumn on "
                    + c.getName() + "." + f.getName() );
            }
        }
    }
}

Prevention

When it happens

Trigger: A map collection property carries both @MapKey (or otherwise gets a map-key property bound) and @MapKeyColumn directly on the property; the check runs during bind() via checkMapKeyColumn().

Common situations: Copy-paste while switching key strategies from property-based to column-based; adding @MapKeyColumn to fix a key-column name without removing an older @MapKey; IDE suggesting both annotations for map mappings.

Related errors


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