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'.

Solutions

  1. Cast only to Java types Hibernate maps as basic (String, Integer, Long, BigDecimal, LocalDate, ...).
  2. For entity narrowing use treatAs()/HQL TREAT instead of as(); for pure Java-side casts, cast the value after the query returns.
  3. Register a type for the custom class: an @JavaTypeRegistration/@Type registration, an AttributeConverter, or a TypeContributor — after that .as(CustomClass.class) resolves.
  4. 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

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


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/c85ade3ccb8c1e59. Report an issue: GitHub.

Appendix: 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
	@Override

View on GitHub (pinned to fad1729dce)