I have a Java (openjdk-17) web application using Hibernate 6 for connecting to a datasource configured in Wildfly 29 and I am unable to use CriteriaQuery max() aggregation because Hibernate report error:
org.hibernate.QueryException: Parameter 1 of function max() has type COMPARABLE, but argument is of type ...
The issue comes from an entity attribute annotated with @Type, because attribute class implements interface Serializable
@Type(value = SalaryType.class)
private Salary salary;
If Serializable is removed from Salary class definition then the query works fine. Unfortunately, I cannot remove the Serializable interface from the class definition, so I need to fin another solution.
My web application is quite complex but I have reproduced the error in a simple web app that I will be sharing here.
DB table
CREATE TABLE t_jd_office_employee (
id varchar(40) NOT NULL PRIMARY KEY,
salary varchar
);
Entity
@Entity
@Table(name = "t_jd_office_employee")
public class OfficeEmployee {
@Id
@Column(length = 40)
private String id;
@Type(value = SalaryType.class)
private Salary salary;
}
Metamodel
@StaticMetamodel(OfficeEmployee.class)
@Generated("org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
public abstract class OfficeEmployee_ {
public static volatile SingularAttribute<OfficeEmployee, Salary> salary;
}
Salary
public class Salary implements Serializable, Comparable<Salary> {
private Long amount;
private String currency;
public Long getAmount() {
return amount;
}
public void setAmount(Long amount) {
this.amount = amount;
}
public String getCurrency() {
return currency;
}
public void setCurrency(String currency) {
this.currency = currency;
}
public String asStringValue() {
return Stream.of(amount, currency)
.filter(Objects::nonNull)
.map(Objects::toString)
.reduce((a, b) -> a + " " + b)
.orElse("");
}
@Override
public int compareTo(Salary o) {
return this.asStringValue().compareTo(o.asStringValue());
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass())
return false;
Salary salary = (Salary) o;
return Objects.equals(this.asStringValue(), salary.asStringValue());
}
@Override
public int hashCode() {
return Objects.hash(this.asStringValue());
}
@Override
public String toString() {
return this.asStringValue();
}
}
UserType
public class SalaryType implements UserType<Salary> {
@Override
public int getSqlType() {
return Types.VARCHAR;
}
@Override
public Class<Salary> returnedClass() {
return Salary.class;
}
@Override
public boolean equals(Salary x, Salary y) {
return x != null && x.equals(y);
}
@Override
public int hashCode(Salary x) {
return x == null ? Objects.hashCode(null) : x.hashCode();
}
@Override
public Salary nullSafeGet(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner)
throws SQLException {
final var salaryValue = rs.getString(position);
if (salaryValue == null) {
return null;
}
final var matcher = Pattern.compile("^(\\d+)?( )*([A-Z]{3})?$").matcher(salaryValue);
if (matcher.matches()) {
final var amount = matcher.group(1) == null ? null : Long.parseLong(matcher.group(1));
final var salary = new Salary();
salary.setAmount(amount);
salary.setCurrency(matcher.group(3));
return salary;
} else {
return null;
}
}
@Override
public void nullSafeSet(PreparedStatement st, Salary value, int index, SharedSessionContractImplementor session)
throws SQLException {
if (Objects.isNull(value))
st.setNull(index, Types.VARCHAR);
else {
st.setString(index, value.asStringValue());
}
}
@Override
public Salary deepCopy(Salary value) {
if (value == null) {
return null;
}
final var salary = new Salary();
salary.setAmount(value.getAmount());
salary.setCurrency(value.getCurrency());
return salary;
}
@Override
public boolean isMutable() {
return false;
}
@Override
public Serializable disassemble(Salary value) {
return value;
}
@Override
public Salary assemble(Serializable cached, Object owner) {
return (Salary) cached ;
}
}
QuereriaQuery
private Optional<Salary> queryMaxSalary() {
try (var em = emf.createEntityManager()) {
final var criteriaBuilder = em.getCriteriaBuilder();
final var query = criteriaBuilder.createQuery(Salary.class);
final var root = query.from(OfficeEmployee.class);
query.select(criteriaBuilder.greatest(root.get(OfficeEmployee_.salary)));
try {
final var result = em.createQuery(query).getSingleResult();
return Optional.of(result);
}
catch (NoResultException ex) {
logger.warn("No max salary found");
return Optional.empty();
}
}
}
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence
https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd"
version="3.0">
<persistence-unit name="p5ee" transaction-type="JTA">
<!-- Hibernate provider (Jakarta or JPA) -->
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<!-- JNDI name for the WildFly data source -->
<jta-data-source>java:jboss/datasources/P5XADS</jta-data-source>
<!-- Properties -->
<properties>
<!-- Hibernate dialect -->
<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
<!-- Hibernate show SQL (useful for debugging) -->
<property name="hibernate.show_sql" value="true"/>
<!-- Hibernate format SQL -->
<property name="hibernate.format_sql" value="true"/>
</properties>
</persistence-unit>
</persistence>
The error
2025-02-21 08:54:46,247 ERROR [org.jboss.resteasy.core.providerfactory.DefaultExceptionMapper] (default task-1) RESTEASY002375: Error processing request GET /p5-office/rest/office/salary/max - local.jd.example.office.OfficeResource.getMaxSalary: org.hibernate.QueryException: Parameter 1 of function max() has type COMPARABLE, but argument is of type local.jd.example.office.Salary
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>local.jd.example.office</groupId>
<artifactId>office-webapp</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- Wildfly -->
<version.server>29.0.1.Final</version.server>
<version.bom.ee>${version.server}</version.bom.ee>
<version.plugin.wildfly>5.1.1.Final</version.plugin.wildfly>
<!-- Wildfly sever -->
<wildfly.hostname>localhost</wildfly.hostname>
<wildfly.port>9990</wildfly.port>
<wildfly.username>admin</wildfly.username>
<wildfly.password>******</wildfly.password>
</properties>
<dependencies>
<!-- Jakarta -->
<dependency>
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>
<version>2.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.16</version>
<scope>provided</scope>
</dependency>
<!-- Hibernate ORM -->
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.2.6.Final</version>
</dependency>
</dependencies>
<build>
<finalName>p5-office</finalName>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.wildfly.plugins</groupId>
<artifactId>wildfly-maven-plugin</artifactId>
<version>${version.plugin.wildfly}</version>
<configuration>
<hostname>${wildfly.hostname}</hostname>
<port>${wildfly.port}</port>
<username>${wildfly.username}</username>
<password>${wildfly.password}</password>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<!--Build configuration for the WAR plugin: -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<!-- Jakarta EE doesn't require web.xml, Maven needs to catch up! -->
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
Full stack trace
2025-02-23 10:38:35,683 ERROR [org.jboss.resteasy.core.providerfactory.DefaultExceptionMapper] (default task-1) RESTEASY002375: Error processing request GET /p5-office/rest/office/salary/max - local.jd.example.office.OfficeResource.getMaxSalary: org.hibernate.QueryException: Parameter 1 of function max() has type COMPARABLE, but argument is of type local.jd.example.office.Salary
at org.hibernate@6.2.6.Final//org.hibernate.query.sqm.produce.function.ArgumentTypesValidator.throwError(ArgumentTypesValidator.java:253)
at org.hibernate@6.2.6.Final//org.hibernate.query.sqm.produce.function.ArgumentTypesValidator.checkType(ArgumentTypesValidator.java:199)
at org.hibernate@6.2.6.Final//org.hibernate.query.sqm.produce.function.ArgumentTypesValidator.validate(ArgumentTypesValidator.java:98)
at org.hibernate@6.2.6.Final//org.hibernate.query.sqm.function.AbstractSqmFunctionDescriptor.generateSqmExpression(AbstractSqmFunctionDescriptor.java:104)
at org.hibernate@6.2.6.Final//org.hibernate.query.sqm.function.SqmFunctionDescriptor.generateSqmExpression(SqmFunctionDescriptor.java:117)
at org.hibernate@6.2.6.Final//org.hibernate.query.sqm.internal.SqmCriteriaNodeBuilder.greatest(SqmCriteriaNodeBuilder.java:873)
at org.hibernate@6.2.6.Final//org.hibernate.query.sqm.internal.SqmCriteriaNodeBuilder.greatest(SqmCriteriaNodeBuilder.java:182)
at deployment.p5-office.war//local.jd.example.office.OfficeResource.queryMaxSalary(OfficeResource.java:56)
at deployment.p5-office.war//local.jd.example.office.OfficeResource.getMaxSalary(OfficeResource.java:36)
at deployment.p5-office.war//local.jd.example.office.OfficeResource$Proxy$_$$_WeldClientProxy.getMaxSalary(Unknown Source)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.jboss.resteasy.resteasy-core@6.2.5.Final//org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:154)
at org.jboss.resteasy.resteasy-core@6.2.5.Final//org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:118)
at org.jboss.resteasy.resteasy-core@6.2.5.Final//org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:560)
at org.jboss.resteasy.resteasy-core@6.2.5.Final//org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:452)
at org.jboss.resteasy.resteasy-core@6.2.5.Final//org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:413)
at org.jboss.resteasy.resteasy-core@6.2.5.Final//org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:321)
at org.jboss.resteasy.resteasy-core@6.2.5.Final//org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:415)
at org.jboss.resteasy.resteasy-core@6.2.5.Final//org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:378)
at org.jboss.resteasy.resteasy-core@6.2.5.Final//org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:356)
at org.jboss.resteasy.resteasy-core@6.2.5.Final//org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:70)
at org.jboss.resteasy.resteasy-core@6.2.5.Final//org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:429)
at org.jboss.resteasy.resteasy-core@6.2.5.Final//org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:240)
at org.jboss.resteasy.resteasy-core@6.2.5.Final//org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:154)
at org.jboss.resteasy.resteasy-core@6.2.5.Final//org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:321)
at org.jboss.resteasy.resteasy-core@6.2.5.Final//org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:157)
at org.jboss.resteasy.resteasy-core@6.2.5.Final//org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:229)
at org.jboss.resteasy.resteasy-core@6.2.5.Final//org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:222)
at org.jboss.resteasy.resteasy-core@6.2.5.Final//org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:55)
at org.jboss.resteasy.resteasy-core@6.2.5.Final//org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:51)
at jakarta.servlet.api@6.0.0//jakarta.servlet.http.HttpServlet.service(HttpServlet.java:614)
at io.undertow.servlet@2.3.7.Final//io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
at io.undertow.servlet@2.3.7.Final//io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
at io.undertow.servlet@2.3.7.Final//io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
at io.undertow.servlet@2.3.7.Final//io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
at org.wildfly.security.elytron-web.undertow-server@4.0.0.Final//org.wildfly.elytron.web.undertow.server.ElytronRunAsHandler.lambda$handleRequest$1(ElytronRunAsHandler.java:68)
at org.wildfly.security.elytron-base@2.2.1.Final//org.wildfly.security.auth.server.FlexibleIdentityAssociation.runAsFunctionEx(FlexibleIdentityAssociation.java:103)
at org.wildfly.security.elytron-base@2.2.1.Final//org.wildfly.security.auth.server.Scoped.runAsFunctionEx(Scoped.java:161)
at org.wildfly.security.elytron-base@2.2.1.Final//org.wildfly.security.auth.server.Scoped.runAs(Scoped.java:73)
at org.wildfly.security.elytron-web.undertow-server@4.0.0.Final//org.wildfly.elytron.web.undertow.server.ElytronRunAsHandler.handleRequest(ElytronRunAsHandler.java:67)
at io.undertow.servlet@2.3.7.Final//io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68)
at io.undertow.servlet@2.3.7.Final//io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:117)
at io.undertow.servlet@2.3.7.Final//io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
at io.undertow.core@2.3.7.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.core@2.3.7.Final//io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
at io.undertow.servlet@2.3.7.Final//io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
at io.undertow.core@2.3.7.Final//io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
at org.wildfly.security.elytron-web.undertow-server-servlet@4.0.0.Final//org.wildfly.elytron.web.undertow.server.servlet.CleanUpHandler.handleRequest(CleanUpHandler.java:38)
at io.undertow.core@2.3.7.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at org.wildfly.extension.undertow@29.0.1.Final//org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)
at io.undertow.core@2.3.7.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at org.wildfly.extension.undertow@29.0.1.Final//org.wildfly.extension.undertow.deployment.GlobalRequestControllerHandler.handleRequest(GlobalRequestControllerHandler.java:68)
at io.undertow.servlet@2.3.7.Final//io.undertow.servlet.handlers.SendErrorPageHandler.handleRequest(SendErrorPageHandler.java:52)
at io.undertow.core@2.3.7.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.servlet@2.3.7.Final//io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:276)
at io.undertow.servlet@2.3.7.Final//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135)
at io.undertow.servlet@2.3.7.Final//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:132)
at io.undertow.servlet@2.3.7.Final//io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
at io.undertow.servlet@2.3.7.Final//io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
at org.wildfly.extension.undertow@29.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1430)
at org.wildfly.extension.undertow@29.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1430)
at org.wildfly.extension.undertow@29.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1430)
at org.wildfly.extension.undertow@29.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1430)
at org.wildfly.extension.undertow@29.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1430)
at io.undertow.servlet@2.3.7.Final//io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:256)
at io.undertow.servlet@2.3.7.Final//io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:101)
at io.undertow.core@2.3.7.Final//io.undertow.server.Connectors.executeRootHandler(Connectors.java:393)
at io.undertow.core@2.3.7.Final//io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:859)
at org.jboss.threads@2.4.0.Final//org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1990)
at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486)
at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1377)
at org.jboss.xnio@3.8.9.Final//org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1282)
at java.base/java.lang.Thread.run(Thread.java:833)
Debug
In the end, the cause of this problem seems to be a bug in Hibernate 6.2.6.Final. This is the version included in Wildfly 29. I ran the same test with Wildfly 35 and Hibernate 6.6.8.Final and it worked fine.