hibernate/hibernate-orm · error · IllegalArgumentException

Can't emulate order preserving row constructor through strin

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.

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)

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.