{"record":{"id":"5011edeafac55436","repo":"hibernate/hibernate-orm","slug":"can-t-emulate-order-preserving-row-constructor-thr-5011ed","errorCode":null,"errorMessage":"Can't emulate order preserving row constructor through string concatenation for expression [%s] which is of type [%s]","messagePattern":"Can't emulate order preserving row constructor through string concatenation for expression \\[(.+?)\\] which is of type \\[(.+?)\\]","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java","lineNumber":3325,"sourceCode":"\t\t\tcase INTEGER, LONG -> castNumberToString( expression, 19, 0 );\n\t\t\tcase FIXED -> {\n\t\t\t\tif ( expression.getExpressionType() instanceof SqlTypedMapping sqlTypedMapping ) {\n\t\t\t\t\tif ( sqlTypedMapping.getPrecision() != null && sqlTypedMapping.getScale() != null ) {\n\t\t\t\t\t\tyield castNumberToString(\n\t\t\t\t\t\t\t\texpression,\n\t\t\t\t\t\t\t\tsqlTypedMapping.getPrecision(),\n\t\t\t\t\t\t\t\tsqlTypedMapping.getScale()\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\tString.format(\n\t\t\t\t\t\t\t\t\"Can't emulate order preserving row constructor through string concatenation for numeric expression [%s] without precision or scale\",\n\t\t\t\t\t\t\t\texpression\n\t\t\t\t\t\t)\n\t\t\t\t);\n\t\t\t}\n\t\t\tdefault -> throw new IllegalArgumentException(\n\t\t\t\t\tString.format(\n\t\t\t\t\t\t\t\"Can't emulate order preserving row constructor through string concatenation for expression [%s] which is of type [%s]\",\n\t\t\t\t\t\t\texpression,\n\t\t\t\t\t\t\tjdbcMapping.getCastType()\n\t\t\t\t\t)\n\t\t\t);\n\t\t};\n\t}\n\n\tprivate int wrapRowComponentAsOrderPreservingConcatArgumentSizeEstimate(Expression expression) {\n\t\tfinal JdbcMapping jdbcMapping = expression.getExpressionType().getSingleJdbcMapping();\n\t\tswitch ( jdbcMapping.getCastType() ) {\n\t\t\tcase STRING:\n\t\t\t\tif ( expression.getExpressionType() instanceof SqlTypedMapping sqlTypedMapping ) {\n\t\t\t\t\tfinal Long length = sqlTypedMapping.getLength();\n\t\t\t\t\tif ( length != null ) {\n\t\t\t\t\t\treturn length.intValue();\n\t\t\t\t\t}","sourceCodeStart":3307,"sourceCodeEnd":3343,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java#L3307-L3343","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Rewrite the query to order by the individual columns instead of the tuple (order by e.a, e.b)","Cast the offending component to a string in the query (e.g. cast(e.x as string)) so the STRING branch applies","Use a database/dialect with native row value constructor support (PostgreSQL, MySQL >= 8.0.19) so emulation is not needed","Ensure numeric attributes map with explicit precision/scale (@Column(precision=..., scale=...)) so the numeric branch applies","Upgrade Hibernate - cast-type coverage of the emulation improves across versions"],"exampleFix":"// before (SQL Server dialect)\nList<Order> orders = session.createQuery(\"from Order o order by (o.id.customerId, o.id.seqNo)\", Order.class).list();\n\n// after\nList<Order> orders = session.createQuery(\"from Order o order by o.id.customerId, o.id.seqNo\", Order.class).list();","handlingStrategy":"fallback","validationCode":"// Before running tuple ordering, check the dialect and component cast types\nboolean nativeRowCtor = dialect.supportsRowValueConstructorSyntax();\nboolean emulable = orderComponents.stream().allMatch(e -> {\n    CastType t = e.getExpressionType().getSingleJdbcMapping().getCastType();\n    return t == CastType.STRING || t == CastType.NUMERIC || t == CastType.EXACT_NUMERIC;\n});\nif (!nativeRowCtor && !emulable) { /* use per-column ordering instead */ }","typeGuard":null,"tryCatchPattern":"try {\n    query.list();\n} catch (IllegalArgumentException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"Can't emulate order preserving row constructor\")) {\n        // fall back to ordering by individual columns\n    } else throw e;\n}","preventionTips":["Prefer ordering by individual columns over tuple/row-constructor ordering","On SQL Server / MySQL < 8.0.19, avoid order by (a, b) and composite embeddable ordering","Keep numeric mappings annotated with explicit precision/scale"],"tags":["hibernate","sql-translator","row-constructor","order-by","dialect-emulation","tuple"],"backgroundTag":"row-value-constructor-emulation","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}