{"record":{"id":"c85ade3ccb8c1e59","repo":"hibernate/hibernate-orm","slug":"can-t-cast-expression-to-unknown-type","errorCode":null,"errorMessage":"Can't cast expression to unknown type: {}","messagePattern":"Can't cast expression to unknown type: (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/query/sqm/tree/spi/expression/AbstractSqmExpression.java","lineNumber":60,"sourceCode":"\t}\n\n\tprotected void internalApplyInferableType(@Nullable SqmBindableType<?> newType) {\n//\t\tSqmTreeCreationLogger.LOGGER.tracef(\n//\t\t\t\t\"Applying inferable type to SqmExpression [%s]: %s -> %s\",\n//\t\t\t\tthis,\n//\t\t\t\tgetExpressible(),\n//\t\t\t\tnewType\n//\t\t);\n\n\t\tsetExpressibleType( highestPrecedenceType2( newType, getExpressible() ) );\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic <X> SqmExpression<X> as(@Nonnull Class<X> type) {\n\t\tfinal BasicType<X> basicTypeForJavaType = nodeBuilder().getTypeConfiguration().getBasicTypeForJavaType( type );\n\t\tif ( basicTypeForJavaType == null ) {\n\t\t\tthrow new IllegalArgumentException( \"Can't cast expression to unknown type: \" + type.getCanonicalName() );\n\t\t}\n\t\treturn new AsWrapperSqmExpression<>( basicTypeForJavaType, this );\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic SqmPredicate isNull() {\n\t\treturn nodeBuilder().isNull( this );\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic SqmPredicate isNotNull() {\n\t\treturn nodeBuilder().isNotNull( this );\n\t}\n\n\t@Nonnull\n\t@Override","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/spi/expression/AbstractSqmExpression.java#L42-L78","documentation":"AbstractSqmExpression.as(Class) implements the JPA cast by looking up a registered BasicType for the target Java type (nodeBuilder().getTypeConfiguration().getBasicTypeForJavaType(type)). If the type registry has no basic type for that class — custom value objects, entity classes, Object, unregistered wrapper types — the lookup returns null and Hibernate throws IllegalArgumentException 'Can't cast expression to unknown type'.","triggerScenarios":"criteriaExpr.as(MonetaryAmount.class) or any .as(SomeCustomClass.class) where no BasicType/AttributeConverter/@JavaTypeRegistration exists for the class; attempting .as(OtherEntity.class) (entity narrowing is not a cast); casting to JDK types Hibernate does not map by default.","commonSituations":"Porting criteria code from providers where as() was a silent Java-side cast; using .as() on custom value types after upgrading to Hibernate 6, which made as() produce a real SQL cast requiring a resolved type; casting to DTO or entity classes.","solutions":["Cast only to Java types Hibernate maps as basic (String, Integer, Long, BigDecimal, LocalDate, ...).","For entity narrowing use treatAs()/HQL TREAT instead of as(); for pure Java-side casts, cast the value after the query returns.","Register a type for the custom class: an @JavaTypeRegistration/@Type registration, an AttributeConverter, or a TypeContributor — after that .as(CustomClass.class) resolves.","Bind the value as a typed parameter (parameter binding uses converters) instead of casting an expression."],"exampleFix":"// before\nExpression<String> raw = root.get(\"amount\");\nraw.as(MonetaryAmount.class); // no BasicType for MonetaryAmount -> IllegalArgumentException\n// after\n// 1) register a converter for MonetaryAmount (e.g. @Converter auto-applied), then:\nExpression<MonetaryAmount> amt = cb.toLong(...) /* or use the converted path directly */;\n// 2) or avoid the cast entirely:\nList<MonetaryAmount> amounts = rows.stream().map(r -> r.getAmount()).toList();","handlingStrategy":"validation","validationCode":"static boolean castSupported(SessionFactory sf, Class<?> javaType) {\n    return ((SessionFactoryImplementor) sf).getTypeConfiguration()\n            .getBasicTypeForJavaType(javaType) != null; // null -> as() would throw\n}","typeGuard":"static boolean castableToBasic(Class<?> target) {\n    // quick allow-list of common basic types before calling Expression.as\n    return target == String.class || Number.class.isAssignableFrom(target)\n            || target == Boolean.class || java.time.temporal.Temporal.class.isAssignableFrom(target);\n}","tryCatchPattern":"try {\n    casted = expression.as(targetClass);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"Can't cast expression\")) {\n        casted = null; // bind as typed parameter or register a converter instead\n    } else throw e;\n}","preventionTips":["Register @JavaTypeRegistration / AttributeConverter / TypeContributor entries for custom value classes before casting.","Use treatAs for entity narrowing; never .as(EntityClass.class).","Restrict .as() to standard basic types; do Java-side casts after the query when only typing is needed."],"tags":["hibernate","criteria-api","type-cast","basic-type","illegal-argument","type-registry"],"backgroundTag":"expression-cast-unknown-type","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}