hibernate/hibernate-orm · error · UnsupportedOperationException

Boolean expression does not support min()

Error message

Boolean expression does not support min()

What it means

SqmBooleanValuedSimplePath.min() is the mirror of max(): the min aggregate requires an orderable scalar type and Hibernate throws UnsupportedOperationException when .min() is invoked on a boolean-valued path. Like its sibling it fails during SQM construction, not at SQL execution. The message names min() explicitly.

Source

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

		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 before aggregating: "select min(cast(e.deleted as int)) from Document d" or cb.min(cb.toInteger(root.get("deleted"))).
  2. Express the intent directly: "select count(d) from Document d where d.deleted = false" or an every()/case-based predicate.
  3. Filter generic aggregation UIs to Number-typed expressions only.
  4. If native SQL min() on a boolean column is unavoidable, use a native query where the dialect decides.

Example fix

// before - deleted is boolean -> UnsupportedOperationException
Long r = em.createQuery("select min(cast(e.deleted as boolean)) from Document e", Long.class).getSingleResult();

// after - aggregate the integer cast
Integer r = em.createQuery("select min(cast(e.deleted as int)) from Document e", Integer.class).getSingleResult();
Defensive patterns

Strategy: validation

Validate before calling

static boolean minSupported(jakarta.persistence.criteria.Expression<?> e) {
    Class<?> t = e.getJavaType();
    if (t == null) return false;
    if (Boolean.class == t || boolean.class == t) return false; // min() unsupported on boolean
    return Number.class.isAssignableFrom(t) || 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.min(expr);
} catch (UnsupportedOperationException e) {
    if (Boolean.class == expr.getJavaType()) {
        return cb.min(cb.toInteger((jakarta.persistence.criteria.Expression<Boolean>) expr));
    }
    throw e;
}

Prevention

When it happens

Trigger: HQL "select min(e.deleted) from Document d" where deleted is boolean; Criteria code calling .min() on root.get("enabled") of a Boolean attribute; generic min/max toggle in a query builder applied to a flag column.

Common situations: Dashboards computing 'all rows still enabled' as min(flag); T-SQL/MySQL-style boolean aggregate habits carried into HQL; soft-delete flags aggregated with min() to check whether any row is deleted.

Related errors


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