Criteria API

About

The Criteria API is a type-safe, object-oriented API in JPA used to create dynamic queries programmatically. Instead of writing queries as strings (like JPQL), it lets you build queries using Java objects and methods.

It was introduced in JPA 2.0 to address issues like:

  • Lack of compile-time checking in JPQL

  • Difficulty in building dynamic queries using string concatenation

Characteristics of Criteria API

Feature
Description

Type-Safe

Uses Java types; errors are caught at compile time.

Dynamic

You can build queries conditionally at runtime.

Object-Oriented

Constructs queries using objects, not string-based syntax.

Complex Query Support

Good for building queries with complex filters and conditions.

Integrated with EntityManager

Uses EntityManager to execute queries like JPQL.

Syntax Structure

Here’s how a typical Criteria API query looks:

// 1. Get CriteriaBuilder from EntityManager
CriteriaBuilder cb = entityManager.getCriteriaBuilder();

// 2. Create CriteriaQuery
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);

// 3. Define root entity
Root<Employee> root = cq.from(Employee.class);

// 4. Add predicates and select
cq.select(root).where(cb.equal(root.get("department"), "IT"));

// 5. Execute query
List<Employee> result = entityManager.createQuery(cq).getResultList();

Step 1: Get CriteriaBuilder from EntityManager

CriteriaBuilder cb = entityManager.getCriteriaBuilder();

CriteriaBuilder is a factory object that helps create different parts of the query — like conditions (where), sorting (orderBy), grouping (groupBy), etc.

All Criteria queries start with a CriteriaBuilder. It provides methods like equal(), greaterThan(), and(), etc. — just like we would write in JPQL/SQL, but in an object-oriented way.

Step 2: Create CriteriaQuery

CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);

Creates the actual query object that will return Employee objects.

We must tell JPA what type of data we’re querying. This also sets the return type of your query.

Step 3: Define the Root Entity

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

Defines the main entity/table we're querying from — in SQL terms, this is like saying FROM employee.

It’s our main data source for the query. The root is what we use to access the entity fields like root.get("department").

Step 4: Add Select and Filter Conditions (Predicates)

cq.select(root).where(cb.equal(root.get("department"), "IT"));
  • cq.select(root): selects the entire Employee entity.

  • cb.equal(...): builds a condition where department = 'IT'.

  • .where(...): applies that condition to the query.

We are telling JPA

“I want all employees where department is 'IT'.”

We can also chain multiple conditions here using cb.and() or cb.or().

Step 5: Execute the Query

List<Employee> result = entityManager.createQuery(cq).getResultList();
  • Converts the CriteriaQuery to an executable JPA query.

  • Runs it and fetches the result as a list of Employee objects.

This is where the query is actually run on the database, and you get your data back.

Final Output

After all the steps, result will hold something like:

[
  Employee{id=1, name="Alice", department="IT"},
  Employee{id=5, name="Bob", department="IT"}
]

Breakdown (like SQL)

Criteria API Equivalent
SQL Equivalent

cb.createQuery(Employee.class)

SELECT *

cq.from(Employee.class)

FROM Employee

cb.equal(root.get("department"), "IT")

WHERE department = 'IT'

Examples

Prerequisites

// Entity Class
import jakarta.persistence.*;

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

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

    // Getters and Setters
}

// Repository Interface
import org.springframework.data.jpa.repository.JpaRepository;

public interface EmployeeRepository extends JpaRepository<Employee, Long> {
    // Empty - we will use Criteria API in service
}

// Service with Criteria API Logic

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class EmployeeService {

    @PersistenceContext
    private EntityManager entityManager;

    // Our methods
}

1. Basic SELECT

public List<Employee> getAllEmployees() {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
    Root<Employee> root = cq.from(Employee.class);
    cq.select(root);
    return entityManager.createQuery(cq).getResultList();
}

Fetch all employees.

2. SELECT with WHERE

public List<Employee> getEmployeesByDepartment(String department) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);

        Root<Employee> root = cq.from(Employee.class);
        Predicate deptPredicate = cb.equal(root.get("department"), department);

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

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

Get employees in Sales department.

3. Using Multiple Conditions

public List<Employee> getEmployeesFromITWithHighSalary() {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
    Root<Employee> root = cq.from(Employee.class);

    Predicate p1 = cb.equal(root.get("department"), "IT");
    Predicate p2 = cb.greaterThan(root.get("salary"), 50000);

    cq.select(root).where(cb.and(p1, p2));

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

IT employees with salary > 50,000.

4. Ordering

public List<Employee> getAllEmployeesSortedBySalaryDesc() {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
    Root<Employee> root = cq.from(Employee.class);

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

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

Order employees by salary descending.

5. Selecting Specific Fields (Projection)

public List<String> getAllEmployeeNames() {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<String> nameQuery = cb.createQuery(String.class);
    Root<Employee> empRoot = nameQuery.from(Employee.class);
    nameQuery.select(empRoot.get("name"));

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

Fetch employee names.

6. Joins

Our Employee entity has a field like

@ManyToOne private Department department;

public List<Employee> getEmployeesFromFinanceDepartment() {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
    Root<Employee> root = cq.from(Employee.class);

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

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

Get employees in the Finance department.

7. Aggregate Functions

public Double getAverageSalary() {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<Double> avgSalaryQuery = cb.createQuery(Double.class);
    Root<Employee> root = avgSalaryQuery.from(Employee.class);

    avgSalaryQuery.select(cb.avg(root.get("salary")));

    return entityManager.createQuery(avgSalaryQuery).getSingleResult();
}

Get average salary.

8. Group By and Having

public List<Object[]> getDepartmentsWithMoreThan5Employees() {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<Object[]> groupQuery = cb.createQuery(Object[].class);
    Root<Employee> root = groupQuery.from(Employee.class);

    groupQuery.multiselect(root.get("department"), cb.count(root))
              .groupBy(root.get("department"))
              .having(cb.gt(cb.count(root), 5));

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

Departments with more than 5 employees.

  • multiselect(): Selects multiple values (in this case, the department and the count of employees).

  • groupBy(): Groups the results by department.

  • having(): Filters the grouped results, in this case to return only those departments with more than 5 employees.

9. Using IN Clause

public List<Employee> getEmployeesInDepartments() {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
    Root<Employee> root = cq.from(Employee.class);

    Predicate inClause = root.get("department").in("IT", "HR", "Finance");
    cq.select(root).where(inClause);

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

Employees in IT, HR, or Finance.

10. Subqueries

public List<Employee> getEmployeesWithSalaryGreaterThanAvg() {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
    Root<Employee> root = cq.from(Employee.class);

    // Subquery to calculate the average salary
    Subquery<Double> sub = cq.subquery(Double.class);
    Root<Employee> subRoot = sub.from(Employee.class);
    sub.select(cb.avg(subRoot.get("salary")));

    // Main query selects employees where their salary is greater than the average
    cq.select(root).where(cb.greaterThan(root.get("salary"), sub));

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

Employees earning more than average salary.

Criteria API vs JPQL vs SQL

Feature
Criteria API
JPQL
SQL

Type-Safety

✅ Yes

❌ No

❌ No

Object-Oriented

✅ Yes

✅ Yes

❌ No

Complex Queries

✅ Yes

⚠️ Moderate

✅ Yes

Dynamic Building

✅ Best

❌ Hard

⚠️ Manual string concat

Compile-Time Check

✅ Yes

❌ No

❌ No

Portability

✅ High

✅ High

⚠️ DB-specific syntax

Learning Curve

⚠️ Steep

✅ Simple

⚠️ Moderate to Hard

Last updated

Was this helpful?