Criteria API in JPA: Building Type-Safe Queries with Examples

Illustration for Criteria API in JPA: Building Type-Safe Queries with Examples
By Last updated:

While JPQL offers a SQL-like syntax for queries, it’s string-based, making it prone to runtime errors and hard to maintain in complex applications. To solve this, JPA introduced the Criteria API, which allows developers to build queries dynamically and type-safely using Java objects instead of raw strings.

The Criteria API is especially useful when queries need to be constructed programmatically (e.g., advanced search filters in enterprise applications). In this tutorial, we’ll cover everything from basic criteria queries to advanced use cases with examples.


What is the Criteria API?

The Criteria API is a JPA feature that provides a programmatic, type-safe way to build queries. Instead of writing queries as strings, you use Java objects (CriteriaBuilder, CriteriaQuery, Root) to construct queries.

Benefits of Criteria API:

  • Type-safe queries → detected at compile-time.
  • Dynamic query building → build queries based on user input or conditions.
  • Eliminates risk of SQL injection.
  • Integrates seamlessly with EntityManager.

Example Entity Setup

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 Criteria API Queries

1. Selecting All Employees

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

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

Equivalent JPQL:

SELECT e FROM Employee e

2. Filtering with WHERE

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
cq.select(root).where(cb.equal(root.get("department"), "HR"));

List<Employee> hrEmployees = entityManager.createQuery(cq).getResultList();

Equivalent JPQL:

SELECT e FROM Employee e WHERE e.department = 'HR'

3. Ordering Results

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

Equivalent JPQL:

SELECT e FROM Employee e ORDER BY e.salary DESC

Advanced Criteria API Features

1. Aggregates (COUNT, AVG, SUM)

CriteriaQuery<Long> cq = cb.createQuery(Long.class);
Root<Employee> root = cq.from(Employee.class);
cq.select(cb.count(root));

Long count = entityManager.createQuery(cq).getSingleResult();

2. Joins

Root<Employee> root = cq.from(Employee.class);
Join<Employee, Department> join = root.join("department");
cq.select(root).where(cb.equal(join.get("name"), "Finance"));

3. Subqueries

Subquery<Double> subquery = cq.subquery(Double.class);
Root<Employee> subRoot = subquery.from(Employee.class);
subquery.select(cb.avg(subRoot.get("salary")));

cq.select(root).where(cb.greaterThan(root.get("salary"), subquery));

Equivalent JPQL:

SELECT e FROM Employee e WHERE e.salary > (SELECT AVG(emp.salary) FROM Employee emp)

CRUD Operations with Criteria API

Insert

Inserts are still performed with persist():

Employee emp = new Employee();
emp.setName("Alice");
emp.setDepartment("Finance");
emp.setSalary(85000.0);

entityManager.getTransaction().begin();
entityManager.persist(emp);
entityManager.getTransaction().commit();

Read (Find with Criteria)

CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
cq.select(root).where(cb.equal(root.get("name"), "Alice"));

Employee emp = entityManager.createQuery(cq).getSingleResult();

Update

Updates still use merge():

entityManager.getTransaction().begin();
emp.setSalary(95000.0);
entityManager.merge(emp);
entityManager.getTransaction().commit();

Delete

entityManager.getTransaction().begin();
entityManager.remove(emp);
entityManager.getTransaction().commit();

Real-World Use Cases

  • Search Filters → Build queries dynamically from user input.
  • Analytics → Generate aggregate queries programmatically.
  • Enterprise Systems → Complex joins/subqueries without fragile JPQL strings.

Anti-Patterns and Pitfalls

  • Overusing Criteria API for simple queries → JPQL may be more readable.
  • Forgetting Metamodel usage → leads to string-based attribute access.
  • Complex Criteria queries can become verbose and harder to maintain.

Best Practices

  • Use Criteria API for dynamic queries, JPQL for static queries.
  • Leverage Metamodel API for type safety.
  • Always combine Criteria queries with pagination for large datasets.
  • Monitor generated SQL for optimization.

📌 JPA Version Notes

  • JPA 2.0 → Introduced Criteria API and Metamodel support.
  • JPA 2.1 → Added entity graphs and stored procedures.
  • JPA 2.2 → Improved Java 8 Date/Time API support.
  • Jakarta Persistence (EE 9/10/11) → Package renamed from javax.persistencejakarta.persistence.

Conclusion and Key Takeaways

  • The Criteria API provides type-safe, programmatic queries.
  • It’s ideal for dynamic queries and reduces runtime query errors.
  • Use it alongside JPQL and named queries depending on the use case.
  • Follow best practices to avoid verbose and unreadable code.

FAQ: Expert-Level Questions

1. What’s the difference between JPA and Hibernate?
JPA is a specification; Hibernate is an implementation.

2. How does JPA handle the persistence context?
It’s like a classroom attendance register, tracking entities in memory.

3. What are the drawbacks of eager fetching in JPA?
Loads unnecessary associations, reducing performance.

4. How can I solve the N+1 select problem with JPA?
Use JOIN FETCH, entity graphs, or batch fetching.

5. Can I use JPA without Hibernate?
Yes, alternatives include EclipseLink, OpenJPA, and DataNucleus.

6. What’s the best strategy for inheritance mapping in JPA?
Options: SINGLE_TABLE, JOINED, TABLE_PER_CLASS.

7. How does JPA handle composite keys?
Using @IdClass or @EmbeddedId.

8. What changes with Jakarta Persistence?
Package renamed to jakarta.persistence.

9. Is JPA suitable for microservices?
Yes, but lightweight solutions may be preferred in high-performance apps.

10. When should I avoid using JPA?
For batch-heavy processing, complex reporting, or ultra-low latency needs.