hibernate/hibernate-orm · error · IllegalArgumentException

Can't emulate order preserving row constructor through…

Error message

Can't emulate order preserving row constructor through string concatenation for expression [%s] which is of type [%s]

What it means

Hibernate emulates row value constructors (tuples like (a,b)) on databases lacking native row-constructor syntax by concatenating the tuple components into a single sortable string. Each component must be wrapped so that string order matches the original value order; the wrapper switch only knows how to handle STRING cast types and numeric types with known precision/scale. This IllegalArgumentException is thrown from the default branch when a tuple component's JDBC cast type (e.g. BINARY, OTHER, temporal, JSON, UUID) has no order-preserving string encoding.

Solutions

  1. Rewrite the query to order by the individual columns instead of the tuple (order by e.a, e.b)
  2. Cast the offending component to a string in the query (e.g. cast(e.x as string)) so the STRING branch applies
  3. Use a database/dialect with native row value constructor support (PostgreSQL, MySQL >= 8.0.19) so emulation is not needed
  4. Ensure numeric attributes map with explicit precision/scale (@Column(precision=..., scale=...)) so the numeric branch applies
  5. Upgrade Hibernate - cast-type coverage of the emulation improves across versions

Example fix

// before (SQL Server dialect)
List<Order> orders = session.createQuery("from Order o order by (o.id.customerId, o.id.seqNo)", Order.class).list();

// after
List<Order> orders = session.createQuery("from Order o order by o.id.customerId, o.id.seqNo", Order.class).list();
Defensive patterns

Strategy: fallback

Validate before calling

// Before running tuple ordering, check the dialect and component cast types
boolean nativeRowCtor = dialect.supportsRowValueConstructorSyntax();
boolean emulable = orderComponents.stream().allMatch(e -> {
    CastType t = e.getExpressionType().getSingleJdbcMapping().getCastType();
    return t == CastType.STRING || t == CastType.NUMERIC || t == CastType.EXACT_NUMERIC;
});
if (!nativeRowCtor && !emulable) { /* use per-column ordering instead */ }

Try / catch

try {
    query.list();
} catch (IllegalArgumentException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("Can't emulate order preserving row constructor")) {
        // fall back to ordering by individual columns
    } else throw e;
}

Prevention

When it happens

Trigger: An HQL/Criteria query orders by a tuple or row constructor (e.g. order by (e.a, e.b), ordering by an embeddable/id-class, key(e) ordering, tuple-based pagination) on a dialect where supportsRowValueConstructorSyntax() is false (e.g. SQL Server, MySQL before 8.0.19, older DB2), and at least one tuple component's jdbcMapping.getCastType() is neither STRING nor a numeric type with precision and scale.

Common situations: Sorting or paging by composite keys/embeddables on SQL Server; entity id-class tuple comparisons; @OrderBy on composite/id attributes; upgrading Hibernate where tuple emulation paths became stricter for types without precision/scale (e.g. numeric mappings missing precision metadata); custom BasicType registrations returning an unusual CastType.

Related errors


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

Appendix: source

Thrown at hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java:3325

			case INTEGER, LONG -> castNumberToString( expression, 19, 0 );
			case FIXED -> {
				if ( expression.getExpressionType() instanceof SqlTypedMapping sqlTypedMapping ) {
					if ( sqlTypedMapping.getPrecision() != null && sqlTypedMapping.getScale() != null ) {
						yield castNumberToString(
								expression,
								sqlTypedMapping.getPrecision(),
								sqlTypedMapping.getScale()
						);
					}
				}
				throw new IllegalArgumentException(
						String.format(
								"Can't emulate order preserving row constructor through string concatenation for numeric expression [%s] without precision or scale",
								expression
						)
				);
			}
			default -> throw new IllegalArgumentException(
					String.format(
							"Can't emulate order preserving row constructor through string concatenation for expression [%s] which is of type [%s]",
							expression,
							jdbcMapping.getCastType()
					)
			);
		};
	}

	private int wrapRowComponentAsOrderPreservingConcatArgumentSizeEstimate(Expression expression) {
		final JdbcMapping jdbcMapping = expression.getExpressionType().getSingleJdbcMapping();
		switch ( jdbcMapping.getCastType() ) {
			case STRING:
				if ( expression.getExpressionType() instanceof SqlTypedMapping sqlTypedMapping ) {
					final Long length = sqlTypedMapping.getLength();
					if ( length != null ) {
						return length.intValue();
					}

View on GitHub (pinned to fad1729dce)