hibernate/hibernate-orm · error · IllegalArgumentException
Can't emulate equality preserving row constructor through st
Error message
Can't emulate equality preserving row constructor through string concatenation for expression [%s] which is of type [%s]
What it means
Twin of the order-preserving case: on dialects without row value constructor syntax, Hibernate emulates tuple equality (a,b) = (c,d) by concatenating components into one string and comparing strings. The equality-preserving wrapper only handles STRING cast types (numeric falls through to the failure branch here when no padding rule exists). This IllegalArgumentException reports the expression and its cast type when no equality-preserving string encoding is known.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java:3411
case OFFSET_TIMESTAMP:
case ZONE_TIMESTAMP:
if ( dialect.requiresCastForConcatenatingNonStrings() ) {
return castToString( expression );
}
// Should we maybe always cast instead? Not sure what is faster/better...
final BasicType<String> stringType = getStringType();
return new SelfRenderingFunctionSqlAstExpression<>(
"concat",
findSelfRenderingFunction( "concat", 2 ),
List.of(
expression,
new QueryLiteral<>( "", stringType )
),
stringType,
stringType
);
}
throw new IllegalArgumentException(
String.format(
"Can't emulate equality preserving row constructor through string concatenation for expression [%s] which is of type [%s]",
expression,
jdbcMapping.getCastType()
)
);
}
private int wrapRowComponentAsEqualityPreservingConcatArgumentSizeEstimate(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
- Split the tuple comparison into one comparison per component (where a = :a and b = :b)
- Cast components to string in the query so the STRING branch applies
- Switch to a database with native row value constructor support
- Upgrade Hibernate for broader cast-type coverage in the emulation
Example fix
// before
session.createQuery("from Order o where (o.id.customerId, o.id.seqNo) = (:cid, :seq)")...
// after
session.createQuery("from Order o where o.id.customerId = :cid and o.id.seqNo = :seq")... Defensive patterns
Strategy: fallback
Validate before calling
boolean nativeRowCtor = dialect.supportsRowValueConstructorSyntax();
boolean eqEmulable = components.stream().allMatch(e ->
e.getExpressionType().getSingleJdbcMapping().getCastType() == CastType.STRING);
if (!nativeRowCtor && !eqEmulable) { /* compare component-wise */ } Try / catch
try {
query.list();
} catch (IllegalArgumentException e) {
if (e.getMessage() != null && e.getMessage().startsWith("Can't emulate equality preserving row constructor")) {
// rewrite as component-wise equality
} else throw e;
} Prevention
- Avoid (a,b) = :tuple syntax on non-row-constructor dialects; expand to a = :a and b = :b
- Compare embeddables field-by-field in JPQL/HQL
- Add integration tests that run queries against the actual target dialect, not only H2/PostgreSQL
When it happens
Trigger: An HQL/Criteria equality or comparison predicate over a tuple (where (a,b) = :pair, comparing an embeddable/id-class, (key(m), value(m)) comparisons) rendered on a dialect without native row constructor support, where a component's jdbcMapping.getCastType() is not handled by the wrapRowComponentAsEqualityPreservingConcatArgument switch.
Common situations: Filtering by composite id class on SQL Server or MySQL < 8.0.19; comparing embeddables in where clauses; @EmbeddedId entities used in Map keys with element comparisons; custom value types with non-standard CastType.
Related errors
- Can't emulate order preserving row constructor through strin
- Can't emulate relational tuple subquery predicate with limit
- Can't emulate quantified tuple subquery predicate with limit
- Summarization is not supported by DBMS!
- SingleStore doesn't support ANY clause
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/bc9f79533dce32db.
Report an issue: GitHub.