cyberangles blog

Hibernate Criteria Case-Insensitive Equals: Using `Restrictions.eq().ignoreCase()` to Generate `lower(property) = 'value'` SQL

When working with databases, case sensitivity in string comparisons can lead to unexpected results. For example, a query for username = 'Admin' might fail to return a user with username = 'admin' if the database uses a case-sensitive collation. Hibernate, a popular ORM (Object-Relational Mapping) framework, simplifies database interactions by providing a type-safe, object-oriented query API: the Hibernate Criteria API.

One common requirement is to perform case-insensitive equality checks. Hibernate’s Restrictions.eq().ignoreCase() method chain is designed for this exact scenario. In this blog, we’ll dive deep into how Restrictions.eq().ignoreCase() works, how it generates SQL with lower(property) = lower('value'), practical examples, edge cases, performance considerations, and alternatives.

2025-11

Table of Contents#

  1. Understanding Case Sensitivity in Database Queries
  2. Hibernate Criteria API Overview
  3. Case-Insensitive Equals with Restrictions.eq().ignoreCase()
  4. How Restrictions.eq().ignoreCase() Works Internally
  5. Practical Examples
  6. Edge Cases and Considerations
  7. Performance Implications
  8. Alternatives to Restrictions.eq().ignoreCase()
  9. Conclusion
  10. References

1. Understanding Case Sensitivity in Database Queries#

Database behavior for string comparisons varies by database system and collation settings:

  • Case-sensitive collations: e.g., utf8_bin in MySQL, C in PostgreSQL. Queries like username = 'Admin' will not match 'admin'.
  • Case-insensitive collations: e.g., utf8_general_ci (MySQL), en_US.UTF-8 (PostgreSQL with CITEXT). These collations ignore case by default.

Relying on database collations for case insensitivity is not portable (e.g., a query that works in MySQL may fail in PostgreSQL). To ensure consistent case-insensitive behavior across databases, explicitly normalizing case in queries is critical. Hibernate’s Restrictions.eq().ignoreCase() addresses this.

2. Hibernate Criteria API Overview#

The Hibernate Criteria API is a legacy (but still widely used) API for building queries programmatically. It provides a fluent, type-safe way to construct queries without writing raw SQL or HQL. Key components include:

  • Criteria: The main interface for building queries.
  • Restrictions: A utility class with static methods to create query conditions (e.g., eq(), like(), gt()).
  • Projections: For selecting specific properties or aggregations (e.g., count(), max()).

Example of a basic Criteria query (case-sensitive equality):

Criteria criteria = session.createCriteria(User.class);  
criteria.add(Restrictions.eq("username", "admin")); // Case-sensitive: matches 'admin' only  
List<User> users = criteria.list();  

3. Case-Insensitive Equals with Restrictions.eq().ignoreCase()#

To perform a case-insensitive equality check, Hibernate provides the ignoreCase() method, which can be chained to Restrictions.eq().

Syntax#

Restrictions.eq(propertyName, value).ignoreCase()  
  • propertyName: The name of the entity property to compare (e.g., "username").
  • value: The value to match (e.g., "Admin").
  • ignoreCase(): A modifier that ensures the comparison is case-insensitive.

4. How Restrictions.eq().ignoreCase() Works Internally#

The magic of Restrictions.eq().ignoreCase() lies in how it translates to SQL. Hibernate normalizes the case of both the entity property and the input value by wrapping them in the LOWER() function (or database-specific equivalent, e.g., LCASE() for SQLite).

Example:
For a User entity with a username property, the code:

criteria.add(Restrictions.eq("username", "Admin").ignoreCase());  

Generates SQL like:

SELECT * FROM user WHERE LOWER(username) = LOWER('Admin')  

This ensures the comparison is case-insensitive, regardless of the database collation.

5. Practical Examples#

Let’s walk through a concrete example with a User entity and a Criteria query using Restrictions.eq().ignoreCase().

Step 1: Define the Entity#

Consider a User entity with a username field (string):

@Entity  
@Table(name = "users")  
public class User {  
    @Id  
    @GeneratedValue(strategy = GenerationType.IDENTITY)  
    private Long id;  
    private String username;  
    private String email;  
 
    // Getters and setters  
}  

Step 2: Case-Insensitive Query with Restrictions.eq().ignoreCase()#

To find all users with username 'admin' (case-insensitive):

Session session = HibernateUtil.getSessionFactory().openSession();  
Transaction tx = session.beginTransaction();  
 
// Create Criteria for User entity  
Criteria criteria = session.createCriteria(User.class);  
 
// Add case-insensitive equality condition  
criteria.add(Restrictions.eq("username", "Admin").ignoreCase());  
 
// Execute query  
List<User> users = criteria.list();  
 
tx.commit();  
session.close();  
 
// Results: Users with username 'admin', 'ADMIN', 'Admin', etc.  

Generated SQL#

Hibernate translates the above query to:

SELECT this_.id AS id1_0_, this_.username AS username2_0_, this_.email AS email3_0_  
FROM users this_  
WHERE LOWER(this_.username) = LOWER('Admin')  

Example with Detached Criteria#

DetachedCriteria allows building queries outside a session (e.g., in service layers). Here’s how to use ignoreCase() with it:

DetachedCriteria detachedCriteria = DetachedCriteria.forClass(User.class);  
detachedCriteria.add(Restrictions.eq("email", "[email protected]").ignoreCase());  
 
// Later, in a session:  
Criteria criteria = detachedCriteria.getExecutableCriteria(session);  
List<User> users = criteria.list();  

6. Edge Cases and Considerations#

Null Values#

  • If the value is null, Restrictions.eq("username", null).ignoreCase() behaves like a case-sensitive IS NULL check (since LOWER(null) is null in SQL).
  • To handle nulls explicitly, combine with Restrictions.isNull():
    criteria.add(Restrictions.or(  
        Restrictions.eq("username", "Admin").ignoreCase(),  
        Restrictions.isNull("username")  
    ));  

Non-String Properties#

ignoreCase() only works with string properties. Applying it to non-string fields (e.g., Integer, Date) will throw a HibernateException:

// Throws exception: "ignoreCase() not supported for non-string types"  
criteria.add(Restrictions.eq("age", 25).ignoreCase());  

Database-Specific Functions#

Hibernate uses LOWER() by default, but it adapts to database-specific functions (e.g., LCASE() for Access, NLOWER() for SQL Server with Unicode). This ensures portability.

7. Performance Implications#

Using LOWER(property) in the WHERE clause can impact performance:

  • Index Invalidation: If username has an index, LOWER(username) will not use the index (unless a functional index is defined).
  • Full Table Scan: Without a functional index, the database may perform a full table scan to compute LOWER(username) for every row.

Mitigation: Functional Indexes#

To optimize lower(property) queries, create a functional index on the normalized column:

MySQL:

CREATE INDEX idx_username_lower ON users (LOWER(username));  

PostgreSQL:

CREATE INDEX idx_username_lower ON users (LOWER(username));  

SQL Server:

CREATE INDEX idx_username_lower ON users (LOWER(username));  

Functional indexes allow the database to quickly look up rows by LOWER(username).

8. Alternatives to Restrictions.eq().ignoreCase()#

JPA Criteria API (Modern Alternative)#

Hibernate’s legacy Criteria API is deprecated in Hibernate 5.2+. The JPA Criteria API (using CriteriaBuilder) is the recommended replacement. For case-insensitive equality:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();  
CriteriaQuery<User> cq = cb.createQuery(User.class);  
Root<User> root = cq.from(User.class);  
 
cq.where(cb.equal(cb.lower(root.get("username")), cb.lower(cb.literal("Admin"))));  
 
List<User> users = entityManager.createQuery(cq).getResultList();  

HQL with LOWER()#

HQL (Hibernate Query Language) explicitly uses LOWER() for case insensitivity:

Query query = session.createQuery("FROM User u WHERE LOWER(u.username) = LOWER(:username)");  
query.setParameter("username", "Admin");  
List<User> users = query.list();  

Database Collations#

As noted earlier, using a case-insensitive collation (e.g., utf8_general_ci in MySQL) avoids LOWER() entirely. However, this is not portable and tied to the database.

9. Conclusion#

Restrictions.eq().ignoreCase() is a powerful tool for writing portable, case-insensitive equality queries in Hibernate’s legacy Criteria API. It generates SQL with LOWER(property) = LOWER(value), ensuring consistent behavior across databases.

Key takeaways:

  • Use Restrictions.eq(property, value).ignoreCase() for case-insensitive string comparisons.
  • Be mindful of null values and non-string properties.
  • Optimize performance with functional indexes on LOWER(property).
  • For new projects, prefer the JPA Criteria API over Hibernate’s legacy Criteria.

10. References#