@Converter not applied when using CriteriaBuilder.lower() with like in Hibernate 6 (regression from Hibernate 5)

Hibernate Version: 6.6.23.Final

We are migrating from Hibernate 5 to Hibernate 6 and noticed a behavior change involving JPA @Converter and Criteria queries.

In Hibernate 5, when using a @Converter(autoApply = true) on a String attribute, the converter was invoked for both persistence and query parameter binding, even if the attribute was wrapped in a function like cb.lower().

In Hibernate 6, when we use CriteriaBuilder.lower(root.get("field")) inside a like predicate, the converter is not invoked for the parameter value. As a result, custom normalization logic (such as trimming or empty-string-to-null handling) is skipped. This leads to queries that used to work in Hibernate 5 no longer returning results in Hibernate 6.

<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="templatePU" transaction-type="RESOURCE_LOCAL">

        <description>Hibernate test case template Persistence Unit</description>
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

        <exclude-unlisted-classes>false</exclude-unlisted-classes>

        <properties>
            <property name="hibernate.archive.autodetection" value="class, hbm"/>

            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
            <property name="hibernate.connection.driver_class" value="org.h2.Driver"/>
            <property name="hibernate.connection.url" value="jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1"/>
            <property name="hibernate.connection.username" value="sa"/>

            <property name="hibernate.connection.pool_size" value="5"/>

            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.hbm2ddl.auto" value="create-drop"/>

            <property name="hibernate.max_fetch_depth" value="5"/>

            <property name="hibernate.cache.region_prefix" value="hibernate.test"/>
            <property name="hibernate.cache.region.factory_class"
                      value="org.hibernate.testing.cache.CachingRegionFactory"/>

            <!--NOTE: hibernate.jdbc.batch_versioned_data should be set to false when testing with Oracle-->
            <property name="hibernate.jdbc.batch_versioned_data" value="true"/>

            <property name="jakarta.persistence.validation.mode" value="NONE"/>
            <property name="hibernate.service.allow_crawling" value="false"/>
            <property name="hibernate.session.events.log" value="true"/>
        </properties>

    </persistence-unit>
</persistence>

package org.hibernate.entity;

import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;

import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Convert;
import jakarta.persistence.Converter;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;

@Entity
public class TestEntity {

    @Converter(autoApply = true)
    public static class SetConverter implements AttributeConverter<String, String> {

        @Override
        public String convertToDatabaseColumn(final String attribute) {
            return attribute.trim();
        }

        @Override
        public String convertToEntityAttribute(final String dbData) {
            return dbData;
        }
    }

    @Id
    @GeneratedValue
    public Long id;

    @Convert(converter = SetConverter.class)
    public String descriptions;
}

package org.hibernate.bugs;

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import org.hibernate.entity.TestEntity;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;

import java.util.List;

/**
 * This template demonstrates how to develop a test case for Hibernate ORM, using the Java Persistence API.
 */
public class JPAUnitTestCase {

    private EntityManagerFactory entityManagerFactory;

    @Before
    public void init() {
        entityManagerFactory = Persistence.createEntityManagerFactory("templatePU");

    }

    @After
    public void destroy() {
        entityManagerFactory.close();
    }

    // Entities are auto-discovered, so just add them anywhere on class-path
    // Add your tests, using standard JUnit.
    @Test
    public void hhh17693Test() {

        EntityManager entityManager = entityManagerFactory.createEntityManager();
        entityManager.getTransaction().begin();

        final TestEntity entity = new TestEntity();
        entity.descriptions = "P_1";
        entityManager.persist(entity);
        entityManager.getTransaction().commit();

        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<TestEntity> cq = cb.createQuery(TestEntity.class);

        Root<TestEntity> root = cq.from(TestEntity.class);

       // Create the LIKE predicate
        Predicate likePredicate = cb.like(cb.lower(root.get("descriptions")), cb.lower(cb.literal("    P_1")), '\\');

        cq.select(root).where(likePredicate);

        List<TestEntity> results = entityManager.createQuery(cq).getResultList();

        Assertions.assertEquals(1, results.size());


        entityManager.close();
    }


}

Please try to create a reproducer with our test case template and if you are able to reproduce the issue, create a bug ticket in our issue tracker and attach that reproducer.

Raised HHH-19766