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
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
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
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 wheredepartment = '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)
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
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.
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
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?