Criteria API with Hibernate: Dynamic Queries for Java Developers

Illustration for Criteria API with Hibernate: Dynamic Queries for Java Developers
By Last updated:

In Java enterprise development, data persistence and querying form the backbone of almost every application. While SQL and HQL are powerful, they can sometimes feel static and inflexible when queries need to be built dynamically at runtime. This is where Hibernate’s Criteria API shines.

Criteria API allows developers to build queries programmatically using a fluent, type-safe approach, reducing the chances of syntax errors and improving maintainability. Think of it as constructing queries like LEGO blocks—you combine smaller parts to form complex queries.

In this tutorial, we’ll cover the Criteria API in Hibernate, how it works, when to use it, and best practices for production-ready applications.


What is Criteria API in Hibernate?

The Criteria API is Hibernate’s object-oriented query language. Unlike HQL or native SQL, it allows developers to construct queries dynamically using Java objects rather than string-based queries.

Key Benefits:

  • Type-safe queries (errors are caught at compile time).
  • Dynamic query building (perfect for complex search filters).
  • Cleaner code compared to concatenating strings.
  • Works with Hibernate Session and JPA.

Setup and Configuration

Before diving into examples, ensure you have Hibernate dependencies in your project:

Maven Dependency

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>6.4.4.Final</version>
</dependency>

Entity Example

import jakarta.persistence.*;

@Entity
@Table(name = "employees")
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String department;
    private double salary;

    // Getters and Setters
}

Basic Usage of Criteria API

Creating a Criteria Query

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);

// Select all employees
cq.select(root);

Query<Employee> query = session.createQuery(cq);
List<Employee> results = query.getResultList();

tx.commit();
session.close();

Filtering Records (WHERE Clause)

Example: Fetch employees from IT department

CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);

cq.select(root).where(cb.equal(root.get("department"), "IT"));

List<Employee> employees = session.createQuery(cq).getResultList();

Sorting Results (ORDER BY)

cq.orderBy(cb.desc(root.get("salary")));

This will return employees sorted by salary in descending order.


Multiple Conditions

cq.where(
    cb.and(
        cb.equal(root.get("department"), "Finance"),
        cb.greaterThan(root.get("salary"), 50000)
    )
);

Aggregations (COUNT, AVG, SUM, MAX, MIN)

CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
countQuery.select(cb.count(countQuery.from(Employee.class)));
Long totalEmployees = session.createQuery(countQuery).getSingleResult();

Joins in Criteria API

Example: Employee → Department relationship

Join<Employee, Department> departmentJoin = root.join("department");
cq.select(root).where(cb.equal(departmentJoin.get("name"), "HR"));

CRUD Operations with Criteria API

While Criteria API mainly focuses on Read operations, you can combine it with Hibernate’s session methods for Create, Update, Delete.

Create

Employee emp = new Employee();
emp.setName("John Doe");
emp.setDepartment("IT");
emp.setSalary(60000);
session.persist(emp);

Update with CriteriaUpdate

CriteriaUpdate<Employee> update = cb.createCriteriaUpdate(Employee.class);
Root<Employee> root = update.from(Employee.class);
update.set("salary", 70000).where(cb.equal(root.get("name"), "John Doe"));
session.createQuery(update).executeUpdate();

Delete with CriteriaDelete

CriteriaDelete<Employee> delete = cb.createCriteriaDelete(Employee.class);
Root<Employee> root = delete.from(Employee.class);
delete.where(cb.equal(root.get("department"), "Temporary"));
session.createQuery(delete).executeUpdate();

Performance Considerations

  • Use pagination (setFirstResult, setMaxResults) to avoid loading large datasets.
  • Leverage caching (2nd level cache for repeated queries).
  • Avoid N+1 select problem by using fetch joins.
  • Always close sessions properly.

Real-World Use Case: Search Filters in Spring Boot

In enterprise apps, Criteria API is often used for dynamic search filters:

public List<Employee> searchEmployees(String dept, Double minSalary) {
    Session session = sessionFactory.openSession();
    CriteriaBuilder cb = session.getCriteriaBuilder();
    CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
    Root<Employee> root = cq.from(Employee.class);

    List<Predicate> predicates = new ArrayList<>();

    if (dept != null) {
        predicates.add(cb.equal(root.get("department"), dept));
    }
    if (minSalary != null) {
        predicates.add(cb.greaterThan(root.get("salary"), minSalary));
    }

    cq.select(root).where(predicates.toArray(new Predicate[0]));

    return session.createQuery(cq).getResultList();
}

This approach ensures flexible querying without hardcoding conditions.


📌 Hibernate Version Notes

Hibernate 5.x

  • Relies on javax.persistence namespace.
  • Legacy Criteria API (org.hibernate.Criteria) exists but is deprecated.

Hibernate 6.x

  • Uses Jakarta Persistence (jakarta.persistence).
  • Enhanced Criteria API with better type safety.
  • Improved SQL rendering and new query APIs.

Best Practices

  • Prefer Criteria API for dynamic queries, but use HQL for static queries.
  • Combine Criteria API with DTO projections for efficiency.
  • Monitor lazy loading to avoid performance pitfalls.
  • Use batch fetching and caching where possible.

Common Pitfalls

  • Overusing Criteria API for simple queries (adds unnecessary complexity).
  • Forgetting to close sessions.
  • Not handling large result sets with pagination.
  • Ignoring the N+1 select problem.

Conclusion and Key Takeaways

The Hibernate Criteria API is a powerful tool for building dynamic, type-safe queries in enterprise applications. By combining it with best practices, caching, and proper fetching strategies, developers can achieve highly efficient and maintainable persistence logic.


FAQ

Q1: What’s the difference between Hibernate and JPA?
Hibernate is a JPA implementation with additional features like Criteria API, caching, and HQL.

Q2: How does Hibernate caching improve performance?
It avoids redundant database hits by storing entities and queries in memory.

Q3: What are the drawbacks of eager fetching?
It loads unnecessary data upfront, causing performance overhead.

Q4: How do I solve the N+1 select problem in Hibernate?
Use fetch joins or batch fetching.

Q5: Can I use Hibernate without Spring?
Yes, Hibernate works standalone, but Spring simplifies configuration.

Q6: What’s the best strategy for inheritance mapping?
Depends on use case—Single Table for speed, Joined for normalization, Table per Class for flexibility.

Q7: How does Hibernate handle composite keys?
With @EmbeddedId or @IdClass annotations.

Q8: How is Hibernate 6 different from Hibernate 5?
Hibernate 6 uses Jakarta namespace, better SQL support, and enhanced Criteria API.

Q9: Is Hibernate suitable for microservices?
Yes, but lightweight alternatives like JOOQ may be better for small services.

Q10: When should I not use Hibernate?
When raw SQL performance is critical or schema is highly dynamic.