hibernate/hibernate-orm · error · IllegalArgumentException
Can't cast expression to unknown type: {}
Error message
Can't cast expression to unknown type: {} What it means
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'.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/query/sqm/tree/spi/expression/AbstractSqmExpression.java:60
}
protected void internalApplyInferableType(@Nullable SqmBindableType<?> newType) {
// SqmTreeCreationLogger.LOGGER.tracef(
// "Applying inferable type to SqmExpression [%s]: %s -> %s",
// this,
// getExpressible(),
// newType
// );
setExpressibleType( highestPrecedenceType2( newType, getExpressible() ) );
}
@Nonnull
@Override
public <X> SqmExpression<X> as(@Nonnull Class<X> type) {
final BasicType<X> basicTypeForJavaType = nodeBuilder().getTypeConfiguration().getBasicTypeForJavaType( type );
if ( basicTypeForJavaType == null ) {
throw new IllegalArgumentException( "Can't cast expression to unknown type: " + type.getCanonicalName() );
}
return new AsWrapperSqmExpression<>( basicTypeForJavaType, this );
}
@Nonnull
@Override
public SqmPredicate isNull() {
return nodeBuilder().isNull( this );
}
@Nonnull
@Override
public SqmPredicate isNotNull() {
return nodeBuilder().isNotNull( this );
}
@Nonnull
@OverrideView on GitHub (pinned to fad1729dce)
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.
Example fix
// before
Expression<String> raw = root.get("amount");
raw.as(MonetaryAmount.class); // no BasicType for MonetaryAmount -> IllegalArgumentException
// after
// 1) register a converter for MonetaryAmount (e.g. @Converter auto-applied), then:
Expression<MonetaryAmount> amt = cb.toLong(...) /* or use the converted path directly */;
// 2) or avoid the cast entirely:
List<MonetaryAmount> amounts = rows.stream().map(r -> r.getAmount()).toList(); Defensive patterns
Strategy: validation
Validate before calling
static boolean castSupported(SessionFactory sf, Class<?> javaType) {
return ((SessionFactoryImplementor) sf).getTypeConfiguration()
.getBasicTypeForJavaType(javaType) != null; // null -> as() would throw
} Type guard
static boolean castableToBasic(Class<?> target) {
// quick allow-list of common basic types before calling Expression.as
return target == String.class || Number.class.isAssignableFrom(target)
|| target == Boolean.class || java.time.temporal.Temporal.class.isAssignableFrom(target);
} Try / catch
try {
casted = expression.as(targetClass);
} catch (IllegalArgumentException e) {
if (e.getMessage() != null && e.getMessage().startsWith("Can't cast expression")) {
casted = null; // bind as typed parameter or register a converter instead
} else throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Illegal empty CTE name
- Illegal CTE name [%s]. Names must start with an alphabetic c
- Expecting DML target entity type [%s] but got [%s]
- Passed attribute name [%s] did not correspond to a collectio
- Passed attribute name [%s] did not correspond to a collectio
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/c85ade3ccb8c1e59.
Report an issue: GitHub.