Advanced JPA Criteria Queries in Spring Boot

Spring Data JPA gives us simple methods like findByName() or custom queries using @Query. But when you need to build dynamic, complex queries at runtime (especially with multiple filters), Criteria API is the most powerful and flexible tool.

This post will walk you through:

  • What is the JPA Criteria API
  • Why use it instead of JPQL or native SQL
  • How to use it in Spring Boot
  • Advanced use cases like joins, predicates, pagination
  • Best practices
Advanced JPA Criteria Queries in Spring Boot

🧠 What is Criteria API?

The JPA Criteria API allows you to construct queries programmatically in Java using a type-safe, object-oriented API.

It’s like building SQL queries without writing actual SQL.

⚑ Why Use Criteria API?

When to UseWhy
Dynamic FiltersBuild queries with optional parameters
Complex Joins and SubqueriesEasier than writing JPQL manually
Type SafetyNo syntax errors at runtime
Compile-Time CheckingRefactor-friendly

🧱 Basic Building Blocks

  • CriteriaBuilder: Factory for query parts
  • CriteriaQuery: Represents a query object
  • Root: Represents table/entity
  • Predicate: WHERE conditions

πŸ§ͺ Example Setup

Let’s use a User entity:


@Entity
public class User {
    @Id @GeneratedValue
    private Long id;

    private String name;
    private String email;
    private String status;

    @ManyToOne
    private Department department;

    // Getters and setters
}


@Entity
public class Department {
    @Id @GeneratedValue
    private Long id;

    private String name;
}

πŸ”§ 1. Basic Criteria Query


public List findUsersByStatus(String status) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery query = cb.createQuery(User.class);
    Root user = query.from(User.class);

Predicate condition = cb.equal(user.get("status"), status);
query.select(user).where(condition);

return entityManager.createQuery(query).getResultList();

🧩 2. Multiple Conditions (Dynamic Filters)


public List findUsers(String name, String email, String status) {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery query = cb.createQuery(User.class);
    Root user = query.from(User.class);

    List predicates = new ArrayList<>();

    if (name != null) {
        predicates.add(cb.like(user.get("name"), "%" + name + "%"));
    }
    if (email != null) {
        predicates.add(cb.equal(user.get("email"), email));
    }
    if (status != null) {
        predicates.add(cb.equal(user.get("status"), status));
    }

    query.select(user).where(cb.and(predicates.toArray(new Predicate[0])));
    return entityManager.createQuery(query).getResultList();
}

βœ… This is useful for building dynamic search filters in real-world applications.

πŸ” 3. Using Joins (e.g., User β†’ Department)


public List findUsersByDepartment(String deptName) {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery query = cb.createQuery(User.class);
    Root user = query.from(User.class);
    Join department = user.join("department");

    Predicate predicate = cb.equal(department.get("name"), deptName);
    query.select(user).where(predicate);

    return entityManager.createQuery(query).getResultList();
}

πŸ“„ 4. Sorting and Pagination


public List getUsersSortedAndPaged(int page, int size, String sortField) {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery query = cb.createQuery(User.class);
    Root user = query.from(User.class);

    query.orderBy(cb.asc(user.get(sortField)));
    TypedQuery typedQuery = entityManager.createQuery(query);

    typedQuery.setFirstResult(page * size);
    typedQuery.setMaxResults(size);

    return typedQuery.getResultList();
}

🎯 5. Using CriteriaQuery for Count

Useful for pagination metadata:

public long countUsersWithStatus(String status) {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery query = cb.createQuery(Long.class);
    Root user = query.from(User.class);
    query.select(cb.count(user)).where(cb.equal(user.get("status"), status));
    return entityManager.createQuery(query).getSingleResult();
}

🧠 6. CriteriaBuilder with IN Clause


public List findUsersInDepartments(List deptNames) {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery query = cb.createQuery(User.class);
    Root user = query.from(User.class);
    Join department = user.join("department");

    Predicate predicate = department.get("name").in(deptNames);
    query.select(user).where(predicate);

    return entityManager.createQuery(query).getResultList();
}

πŸ—οΈ 7. Reusable Utility for Dynamic Filtering

A reusable method to build Predicate list dynamically:


private List buildFilters(CriteriaBuilder cb, Root root, Map filters) {
    List predicates = new ArrayList<>();
    filters.forEach((field, value) -> {
        if (value != null) {
            predicates.add(cb.equal(root.get(field), value));
        }
    });
    return predicates;
}

Then use it in your repository:


public List searchUsers(Map filters) {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery query = cb.createQuery(User.class);
    Root user = query.from(User.class);

    List predicates = buildFilters(cb, user, filters);
    query.select(user).where(cb.and(predicates.toArray(new Predicate[0])));

    return entityManager.createQuery(query).getResultList();
}

βœ… Best Practices

PracticeWhy
βœ… Use Metamodel for type safety (via @StaticMetamodel)Prevents string-based errors
βœ… Encapsulate criteria logic into service or helperKeeps repository clean
βœ… Use Criteria for complex, dynamic queriesBetter than string-based JPQL
❌ Avoid mixing Criteria and native queriesKeep query styles consistent
❌ Don’t use Criteria for simple queriesNamed queries or method names are easier

πŸ§ͺ Summary

  • Criteria API is powerful for type-safe, dynamic queries
  • Useful for filtering, joins, pagination, and sorting
  • Consider using Specifications if you use Spring Data JPA (alternative abstraction)
  • Don’t overuse it for basic queries

References

JPA criteria Docs