Boost Spring Boot Performance with FetchType.LAZY in Hibernate

When working with Spring Boot and Hibernate, one of the biggest performance mistakes is using EAGER fetching for entity relationships.

Problem:

  • Fetching unnecessary data from the database
  • Increasing response time due to large joins
  • N+1 query problem (multiple queries slowing down performance)

💡 Solution: Use FetchType.LAZY to load only the necessary data when it's actually needed.

In this article, we’ll explore:
✅ What is FetchType.LAZY?
✅ Why EAGER fetching is bad for performance
✅ A complete example with one-to-many relationship
✅ How FetchType.LAZY reduces database queries


⚡ What is FetchType.LAZY in Hibernate?

In Hibernate, lazy loading (FetchType.LAZY) means that related entities are not fetched from the database until you explicitly access them.

📌 Example: Using FetchType.LAZY in One-to-Many Relationship

Let’s assume we have a Department and Employee relationship where a department has multiple employees.

1️⃣ Define Department Entity (One-to-Many Relationship)

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

    private String name;

    @OneToMany(mappedBy = "department", fetch = FetchType.LAZY) // Lazy Loading
    private List<Employee> employees;

    // Constructors, Getters, and Setters
}

FetchType.LAZY ensures that employees are not loaded until explicitly needed.


2️⃣ Define Employee Entity (Many-to-One Relationship)

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

    private String name;
    private String role;

    @ManyToOne
    @JoinColumn(name = "department_id")
    private Department department;

    // Constructors, Getters, and Setters
}

❌ What Happens with FetchType.EAGER? (Bad Practice)

If we set fetch = FetchType.EAGER, Hibernate will always fetch all employees when retrieving a department.

@OneToMany(mappedBy = "department", fetch = FetchType.EAGER) // BAD PRACTICE
private List<Employee> employees;

🚨 Problem: Unnecessary Data Fetching

📌 Query Generated (EAGER Fetching)

SELECT * FROM departments d
LEFT JOIN employees e ON d.id = e.department_id

✅ Fetching ALL employees even if we don’t need them 😨
Slows down performance when departments have thousands of employees


✅ Optimized API with FetchType.LAZY (Best Practice)

Now, let’s create a Spring Boot API that loads departments without employees (Lazy Loading).


3️⃣ Create a Repository Layer

public interface DepartmentRepository extends JpaRepository<Department, Long> {
}

4️⃣ Create a Service Layer (Lazy Loading in Action)

@Service
public class DepartmentService {

    private final DepartmentRepository departmentRepository;

    public DepartmentService(DepartmentRepository departmentRepository) {
        this.departmentRepository = departmentRepository;
    }

    public List<Department> getAllDepartments() {
        return departmentRepository.findAll(); // Employees are NOT fetched here
    }

    public Department getDepartmentById(Long id) {
        return departmentRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("Department not found"));
    }

    public List<Employee> getEmployeesByDepartment(Long departmentId) {
        Department department = getDepartmentById(departmentId);
        return department.getEmployees(); // Employees are only fetched here!
    }
}

How Lazy Loading Works Here

1️⃣ getAllDepartments() - Fetching Departments Without Employees

public List<Department> getAllDepartments() {
    return departmentRepository.findAll(); // Employees are NOT fetched here
}

📌 What happens here?

  • The method fetches only department details.
  • Employees are NOT loaded because FetchType.LAZY is set in the @OneToMany relationship.
  • Database Query (Generated by Hibernate):
    SELECT * FROM departments;
    
  • Result: API returns only department details, keeping the response lightweight.

2️⃣ getDepartmentById() - Fetching a Single Department Without Employees

public Department getDepartmentById(Long id) {
    return departmentRepository.findById(id)
            .orElseThrow(() -> new RuntimeException("Department not found"));
}

📌 What happens here?

  • The method retrieves only the department from the database.
  • Employees are NOT fetched at this stage.
  • Database Query (Generated by Hibernate):
    SELECT * FROM departments WHERE id = ?;
    
  • Result: Only department data is returned, avoiding unnecessary database calls.

3️⃣ getEmployeesByDepartment() - Fetching Employees When Needed

public List<Employee> getEmployeesByDepartment(Long departmentId) {
    Department department = getDepartmentById(departmentId);
    return department.getEmployees(); // Employees are only fetched here!
}

📌 What happens here?

  • We first fetch the department details using getDepartmentById().
  • When we call department.getEmployees()Hibernate triggers a separate SQL query to load the employees.
  • Database Queries (Generated by Hibernate):
    SELECT * FROM departments WHERE id = ?; -- Fetch department
    SELECT * FROM employees WHERE department_id = ?; -- Fetch employees only when needed
    
  • Result: Employees are loaded only when required, preventing unnecessary data fetching.

5️⃣ Create REST Controller for API Calls

@RestController
@RequestMapping("/api/departments")
public class DepartmentController {

    private final DepartmentService departmentService;

    public DepartmentController(DepartmentService departmentService) {
        this.departmentService = departmentService;
    }

    // Fetch departments WITHOUT employees (Lazy Loading)
    @GetMapping
    public ResponseEntity<List<Department>> getAllDepartments() {
        return ResponseEntity.ok(departmentService.getAllDepartments());
    }

    // Fetch employees of a department (Triggers Lazy Loading)
    @GetMapping("/{id}/employees")
    public ResponseEntity<List<Employee>> getEmployeesByDepartment(@PathVariable Long id) {
        return ResponseEntity.ok(departmentService.getEmployeesByDepartment(id));
    }
}

🔑 Key Takeaways

✅ Use FetchType.LAZY for one-to-many and many-to-many relationships
Avoid EAGER fetching unless absolutely necessary
✅ Lazy Loading reduces database load and improves API performance
✅ Load related entities only when needed (e.g., separate API call for employees)

By following these best practices, your Spring Boot applications will run faster and handle large datasets efficiently. 🚀

Comments

Spring Boot 3 Paid Course Published for Free
on my Java Guides YouTube Channel

Subscribe to my YouTube Channel (165K+ subscribers):
Java Guides Channel

Top 10 My Udemy Courses with Huge Discount:
Udemy Courses - Ramesh Fadatare