hibernate/hibernate-orm · error · AnnotationException

'@GeneratedColumn' may only be applied to single-column mapp

Error message

'@GeneratedColumn' may only be applied to single-column mappings but '${name}' maps to ${length} columns

What it means

AnnotatedColumn.applyGeneratedAs reads @GeneratedColumn (Hibernate annotation that renders a GENERATED ALWAYS AS ... DDL clause) when binding a column. A generated-column clause targets exactly one physical column, so applying it to a property that maps a number of columns other than 1 throws this AnnotationException with the member name and column count.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedColumn.java:948

				setDefaultValue( columnDefault.value() );
			}
		}
		else {
			BOOT_LOGGER.couldNotPerformColumnDefaultLookup();
		}
	}

	void applyGeneratedAs(PropertyData inferredData, int length) {
		final var memberDetails = inferredData.getAttributeMember();
		if ( memberDetails != null ) {
			final var generatedColumn = getOverridableAnnotation(
					memberDetails,
					GeneratedColumn.class,
					getBuildingContext()
			);
			if ( generatedColumn != null ) {
				if (length!=1) {
					throw new AnnotationException("'@GeneratedColumn' may only be applied to single-column mappings but '"
							+ memberDetails.getName() + "' maps to " + length + " columns" );
				}
				setGeneratedAs( generatedColumn.value() );
			}
		}
		else {
			BOOT_LOGGER.couldNotPerformGeneratedColumnLookup();
		}
}

	private void applyColumnCheckConstraint(jakarta.persistence.Column column) {
		applyCheckConstraints( column.check() );
	}

	void applyCheckConstraints(jakarta.persistence.CheckConstraint[] checkConstraintAnnotationUsages) {
		if ( isNotEmpty( checkConstraintAnnotationUsages ) ) {
			for ( var checkConstraintAnnotationUsage : checkConstraintAnnotationUsages ) {
				addCheckConstraint(

View on GitHub (pinned to fad1729dce)

Solutions

  1. Remove @GeneratedColumn from the multi-column property
  2. Apply it to the single mapped column that is actually generated (the specific field inside the embeddable or the one-column basic)
  3. If generation must involve several columns, express it in the column definition or a migration script instead

Example fix

// before: generated column on an embedded (multi-column) attribute
@Entity
public class Invoice {
    @GeneratedColumn("concat(no, '-', year)")   // spans 2 columns -> error
    @Embedded
    private InvoiceNo no;
}

// after: annotate only the single-column field that is generated
@Embeddable
public class InvoiceNo {
    @GeneratedColumn("concat(prefix, seq)")
    private String display;     // maps exactly one column
    private String prefix;
    private long seq;
}
Defensive patterns

Strategy: validation

Validate before calling

// Before boot: @GeneratedColumn must not sit on embedded/multi-column attributes
static boolean generatedColumnsAreSingleColumn(Class<?> entity) {
    for (Field f : entity.getDeclaredFields()) {
        if (f.isAnnotationPresent(GeneratedColumn.class)
                && (f.isAnnotationPresent(Embedded.class) || f.isAnnotationPresent(Columns.class))) {
            return false;
        }
    }
    return true;
}

Try / catch

try {
    final SessionFactory sf = new MetadataSources(standardServiceRegistry)
            .addAnnotatedClass(MyEntity.class)
            .buildMetadata()
            .buildSessionFactory();
} catch (AnnotationException | MappingException e) {
    throw new IllegalStateException("Invalid ORM mapping, aborting startup: " + e.getMessage(), e);
}

Prevention

When it happens

Trigger: Placing @GeneratedColumn on an @Embedded attribute, a composite user type, or any multi-column mapping during metadata binding.

Common situations: Adding @GeneratedColumn to embedded/aggregate attributes; migrating single-column types to composite ones (e.g. money split into amount+currency) while keeping the annotation; copy-paste of generated-column setup onto the wrong field.

Related errors


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