hibernate/hibernate-orm · error · UnsupportedOperationException

Boolean expression does not support max()

Error message

Boolean expression does not support max()

What it means

SqmBooleanValuedSimplePath.max() (inherited surface: SqmComparableExpression.max(), the criteria counterpart of HQL max()) throws UnsupportedOperationException because the max aggregate is defined over ordered scalar types and Hibernate's type system does not order booleans. Calling .max() on a boolean-typed path/expression fails immediately while the SQM tree is assembled. The SQL is never produced.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/query/sqm/tree/spi/domain/SqmBooleanValuedSimplePath.java:83

		return new SqmBooleanExpressionWrapper( nodeBuilder().coalesce( this, y ) );
	}

	@Nonnull
	@Override
	public SqmBooleanExpression nullif(@Nonnull Expression<? extends Boolean> y) {
		return new SqmBooleanExpressionWrapper( nodeBuilder().nullif( this, y ) );
	}

	@Nonnull
	@Override
	public SqmBooleanExpression nullif(Boolean y) {
		return new SqmBooleanExpressionWrapper( nodeBuilder().nullif( this, y ) );
	}

	@Nonnull
	@Override
	public SqmBooleanExpression max() {
		throw new UnsupportedOperationException( "Boolean expression does not support max()" );
	}

	@Nonnull
	@Override
	public SqmBooleanExpression min() {
		throw new UnsupportedOperationException( "Boolean expression does not support min()" );
	}
}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Cast the boolean to an integer before aggregating: "select max(cast(e.active as int)) from Employee e" or cb.max(cb.toInteger(root.get("active"))).
  2. If you need 'at least one true' semantics, use a case/count instead: "select case when count(e.id) > 0 then true else false end ... where e.active = true".
  3. Restrict generic aggregation code so max() is only offered for Number-typed expressions.
  4. Model the flag as a smallint column in the database if SQL-level max() over it is a hard requirement.

Example fix

// before - active is boolean -> UnsupportedOperationException at query build
Object r = em.createQuery("select max(e.active) from Employee e").getSingleResult();

// after - aggregate over the cast integer value
Integer r = em.createQuery("select max(cast(e.active as int)) from Employee e", Integer.class).getSingleResult();
Defensive patterns

Strategy: validation

Validate before calling

// Check the operand type before aggregating
static boolean maxSupported(jakarta.persistence.criteria.Expression<?> e) {
    Class<?> t = e.getJavaType();
    return Number.class.isAssignableFrom(t) || (t != null && t.isPrimitive())
        || java.time.temporal.Temporal.class.isAssignableFrom(t)
        || String.class == t;
}

Type guard

static boolean isBooleanExpr(jakarta.persistence.criteria.Expression<?> e) {
    return Boolean.class == e.getJavaType() || boolean.class == e.getJavaType();
}

Try / catch

try {
    return cb.max(expr);
} catch (UnsupportedOperationException e) {
    if (Boolean.class == expr.getJavaType()) {
        return cb.max(cb.toInteger((jakarta.persistence.criteria.Expression<Boolean>) expr));
    }
    throw e;
}

Prevention

When it happens

Trigger: HQL "select max(e.active) from Employee e" where active is a boolean column; Criteria code ((JpaExpression<Boolean>) root.get("active")).max(); HQLQueryBuilder DSLs that call max() on whatever selection the user picked, including flags.

Common situations: Porting SQL Server habits where max() over a bit column is accepted; report queries that want 'any row active' or 'latest flag value' semantics; generic aggregation UIs letting users apply max to every numeric-looking column, including boolean flags stored as true/false.

Related errors


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