Hibernate Lazy vs Eager Loading Interview Questions and Answers

Master Hibernate Lazy vs Eager Loading with production-ready interview questions covering fetch defaults, LazyInitializationException, join fetch, Entity Graphs, DTO projections, Open Session in View, serialization risks, performance trade-offs, and senior-level best practices.


Introduction

Lazy and Eager Loading determine when Hibernate initializes entity relationships.

This decision has a major effect on:

  • SQL query count
  • Database round trips
  • Response time
  • Memory usage
  • Transaction boundaries
  • API serialization
  • N+1 queries
  • Application scalability

A relationship configured as LAZY is loaded only when the application accesses it.

A relationship configured as EAGER must be initialized when the owning entity is loaded.

Neither approach is always correct.

Lazy loading can reduce unnecessary database access, but it may cause:

  • LazyInitializationException
  • N+1 queries
  • Unexpected SQL execution

Eager loading can simplify access, but it may cause:

  • Over-fetching
  • Large joins
  • Excessive memory usage
  • Slow queries
  • Cartesian products

Senior developers should design fetch plans according to each business use case rather than relying only on static annotations.

This guide covers the 15 most frequently asked Hibernate Lazy vs Eager Loading interview questions with code examples, Mermaid diagrams, SQL behavior, production scenarios, common mistakes, and senior-level recommendations.


Q1. What is Lazy Loading in Hibernate?

Answer

Lazy Loading delays fetching an associated entity or collection until the application actually accesses it.

Example

@Entity
public class Customer {

    @Id
    private Long id;

    private String name;

    @OneToMany(
        mappedBy = "customer",
        fetch = FetchType.LAZY
    )
    private List<Order> orders =
        new ArrayList<>();
}

When Hibernate loads the customer:

Customer customer =
    entityManager.find(
        Customer.class,
        customerId
    );

Hibernate initially loads only the customer.

The orders are loaded when the application accesses:

customer.getOrders().size();

Lazy Loading Flow

sequenceDiagram
    participant App
    participant Hibernate
    participant Database
    App->>Hibernate: find Customer
    Hibernate->>Database: SELECT customer
    Database-->>Hibernate: Customer row
    Hibernate-->>App: Customer with lazy Orders
    App->>Hibernate: customer.getOrders()
    Hibernate->>Database: SELECT orders by customer_id
    Database-->>Hibernate: Order rows
    Hibernate-->>App: Initialized Orders collection

Benefits

  • Avoids loading unused relationships
  • Reduces initial query size
  • Reduces memory usage
  • Improves performance for summary use cases
  • Provides better fetch-plan control

Why Interviewers Ask This

Lazy loading is a core Hibernate feature and a common source of production issues.

Production Example

A customer search page displays only the customer name and email. Loading all historical orders would waste database and memory resources.

Common Mistake

Assuming LAZY means the relationship will never be queried.

Senior Follow-up

Lazy loading delays SQL. It does not eliminate SQL.


Q2. What is Eager Loading in Hibernate?

Answer

Eager Loading requires an association to be initialized when the owning entity is loaded.

Example

@Entity
public class Employee {

    @Id
    private Long id;

    private String name;

    @ManyToOne(fetch = FetchType.EAGER)
    private Department department;
}

When the employee is loaded, Hibernate must also make the department available.

Eager Loading Flow

flowchart TD
    A[Application loads Employee] --> B[Hibernate detects EAGER Department]
    B --> C[Load Employee]
    B --> D[Load Department]
    C --> E[Return initialized Employee graph]
    D --> E

Hibernate may load the department using:

  • SQL join
  • Separate SQL query
  • Existing persistence-context instance
  • Second-Level Cache

Important Point

EAGER defines when the association must be available.

It does not guarantee a single joined SQL query.

Production Example

A small configuration entity always requires its related region information and the association contains only one row.

Common Mistake

Assuming EAGER always means JOIN FETCH.

Senior Follow-up

EAGER is a semantic requirement. The actual SQL fetch strategy is provider-dependent.


Q3. What is the difference between Lazy and Eager Loading?

Answer

Lazy Loading Eager Loading
Loads association when accessed Loads association immediately
Smaller initial load Larger initial load
Better for optional data Useful when data is always required
May cause N+1 queries May cause over-fetching
May cause LazyInitializationException May create large joins or extra selects
Provides more runtime control Harder to override efficiently

Comparison Diagram

flowchart TD
    A[Load Customer] --> B{Fetch Type}
    B -->|LAZY| C[Load Customer only]
    C --> D[Load Orders if accessed]
    B -->|EAGER| E[Load Customer and Orders immediately]

Practical Example

Customer List

Need:

  • Customer ID
  • Name
  • Email

Recommended:

LAZY relationships or DTO projection

Customer Detail

Need:

  • Customer
  • Address
  • Recent orders

Recommended:

Explicit Entity Graph or JOIN FETCH

Common Mistake

Choosing one loading style for all use cases.

Senior Follow-up

Static mapping defaults should be conservative. Runtime queries should define the actual fetch plan.


Q4. What are the default fetch types in JPA?

Answer

JPA defines the following defaults:

Relationship Default Fetch Type
@OneToOne EAGER
@ManyToOne EAGER
@OneToMany LAZY
@ManyToMany LAZY
@ElementCollection LAZY

Diagram

flowchart TD
    A[JPA Association] --> B{Cardinality}
    B -->|To-One| C[Default EAGER]
    B -->|To-Many| D[Default LAZY]
    C --> E[OneToOne]
    C --> F[ManyToOne]
    D --> G[OneToMany]
    D --> H[ManyToMany]
    D --> I[ElementCollection]

Explicit Mapping Recommendation

@ManyToOne(fetch = FetchType.LAZY)
private Department department;
@OneToMany(
    mappedBy = "customer",
    fetch = FetchType.LAZY
)
private List<Order> orders;

Explicit configuration makes the design intention clear.

Common Mistake

Forgetting that @ManyToOne is EAGER by default.

Senior Follow-up

Many production teams explicitly configure to-one relationships as LAZY to prevent silent over-fetching.


Q5. What is LazyInitializationException?

Answer

LazyInitializationException occurs when an application tries to access a lazy association after the Hibernate session or persistence context has closed.

Example

public Customer findCustomer(Long id) {

    Customer customer =
        entityManager.find(
            Customer.class,
            id
        );

    return customer;
}

Later, outside the transaction:

customer.getOrders().size();

Hibernate cannot load the orders because the entity is detached and no active persistence context is available.

Failure Flow

sequenceDiagram
    participant Service
    participant Hibernate
    participant Controller
    Service->>Hibernate: Load Customer
    Hibernate-->>Service: Customer with lazy Orders
    Service-->>Controller: Return Customer
    Note over Hibernate: Persistence context closes
    Controller->>Controller: Access customer.getOrders()
    Controller-->>Controller: LazyInitializationException

Typical Error

failed to lazily initialize a collection:
could not initialize proxy - no Session

Production Example

A controller serializes a detached entity containing an uninitialized lazy collection.

Common Mistake

Treating the exception as a reason to make every association EAGER.

Senior Follow-up

The real issue is usually incorrect fetch planning or transaction boundaries.


Q6. Why does LazyInitializationException occur?

Answer

Lazy associations need an active persistence context so Hibernate can execute the required SQL.

The exception occurs when:

  1. An entity is loaded.
  2. Its lazy relationship remains uninitialized.
  3. The persistence context closes.
  4. The application accesses that relationship.

Lifecycle

flowchart TD
    A[Load Entity] --> B[Lazy Association Uninitialized]
    B --> C[Transaction Ends]
    C --> D[Entity Becomes Detached]
    D --> E[Application Accesses Lazy Association]
    E --> F[No Session Available]
    F --> G[LazyInitializationException]

Common Triggers

  • Returning entities from service methods
  • Serializing entities directly in controllers
  • Accessing lazy fields in view templates
  • Mapping DTOs outside the transaction
  • Asynchronous processing with detached entities
  • Logging entity toString() methods that access collections
  • Calling equals() or hashCode() on lazy associations

Common Mistake

Assuming @Transactional on a controller always creates a good architecture.

Senior Follow-up

Fetch required data inside the service transaction and return a DTO.


Q7. How do you solve LazyInitializationException?

Answer

There are several valid solutions.

The correct choice depends on the use case.

Solution 1: Map to DTO Inside the Transaction

@Transactional(readOnly = true)
public CustomerDetailsResponse getCustomer(
    Long customerId
) {

    Customer customer =
        customerRepository.findById(customerId)
            .orElseThrow(
                CustomerNotFoundException::new
            );

    return new CustomerDetailsResponse(
        customer.getId(),
        customer.getName(),
        customer.getOrders()
            .stream()
            .map(OrderMapper::toSummary)
            .toList()
    );
}

Solution 2: Use JOIN FETCH

@Query(
    """
    select distinct c
    from Customer c
    left join fetch c.orders
    where c.id = :id
    """
)
Optional<Customer> findWithOrdersById(
    @Param("id") Long id
);

Solution 3: Use Entity Graph

@EntityGraph(attributePaths = "orders")
Optional<Customer> findDetailedById(Long id);

Solution 4: Use DTO Projection

@Query(
    """
    select new com.codewithvenu.CustomerSummary(
        c.id,
        c.name,
        count(o.id)
    )
    from Customer c
    left join c.orders o
    where c.id = :id
    group by c.id, c.name
    """
)
CustomerSummary findSummaryById(Long id);

Solution Selection

flowchart TD
    A[Need lazy association outside persistence layer?] -->|No| B[Keep entity inside transaction]
    A -->|Yes| C{What data is needed?}
    C -->|Full bounded relationship| D[JOIN FETCH or Entity Graph]
    C -->|Summary fields| E[DTO Projection]
    C -->|API response| F[Map to DTO inside transaction]

Invalid Quick Fixes

Avoid solving the problem by:

  • Making every relationship EAGER
  • Calling random getter methods in controllers
  • Enabling Open Session in View without understanding trade-offs
  • Using Hibernate.initialize() everywhere

Senior Follow-up

A production solution should define the fetch plan at the use-case boundary.


Q8. What is Open Session in View?

Answer

Open Session in View keeps the persistence context open for the entire HTTP request, including controller and view rendering.

Architecture

sequenceDiagram
    participant Client
    participant Filter
    participant Controller
    participant Service
    participant Database
    Client->>Filter: HTTP Request
    Filter->>Filter: Open EntityManager
    Filter->>Controller: Continue request
    Controller->>Service: Call service
    Service->>Database: Query entities
    Database-->>Service: Results
    Service-->>Controller: Return entities
    Controller->>Database: Lazy loading during serialization
    Controller-->>Filter: Response created
    Filter->>Filter: Close EntityManager
    Filter-->>Client: HTTP Response

Benefit

Lazy associations can still load during controller processing or view serialization.

Risks

  • SQL executes outside service transaction boundaries
  • N+1 queries become harder to notice
  • Serialization can trigger many hidden queries
  • Database connections may remain occupied longer
  • Presentation layer becomes coupled to persistence behavior
  • Performance becomes unpredictable
  • Error handling becomes more difficult

Production Example

A JSON serializer accesses several lazy relationships and silently generates dozens of SQL queries.

Common Mistake

Treating Open Session in View as the recommended solution for all lazy-loading problems.

Senior Follow-up

Many enterprise teams disable it and use explicit fetch plans plus DTOs.


Q9. Why is Open Session in View controversial?

Answer

Open Session in View is controversial because it simplifies lazy access while weakening architectural boundaries.

Advantages

  • Reduces immediate lazy-loading exceptions
  • Simplifies simple CRUD screens
  • Allows views to navigate lazy associations

Disadvantages

  • Hides poorly designed queries
  • Allows database access from presentation code
  • Can create N+1 queries during serialization
  • Increases transaction and connection-management complexity
  • Makes performance behavior difficult to predict
  • Encourages returning entities directly

Comparison

OSIV Enabled OSIV Disabled
Lazy loading available in controller/view Lazy data must be fetched explicitly
Faster initial development Better architecture control
Hidden SQL possible Query behavior more visible
Entity serialization easier DTO mapping required
Higher production risk More predictable service boundaries

Architecture Decision

flowchart TD
    A[Need data for API response] --> B[Fetch in Service Layer]
    B --> C[Map to DTO]
    C --> D[Close Persistence Context]
    D --> E[Controller returns DTO]

Common Mistake

Believing disabling OSIV automatically fixes N+1 queries.

Senior Follow-up

Disabling OSIV exposes incorrect fetch planning, but those queries must still be redesigned.


Q10. How does JOIN FETCH help with Lazy Loading?

Answer

JOIN FETCH overrides lazy behavior for a specific query and loads the association immediately.

Example

List<Order> orders =
    entityManager.createQuery(
        """
        select distinct o
        from Order o
        join fetch o.customer
        left join fetch o.items
        where o.status = :status
        """,
        Order.class
    )
    .setParameter(
        "status",
        OrderStatus.OPEN
    )
    .getResultList();

Flow

flowchart TD
    A[Execute Order query] --> B[JOIN FETCH Customer and Items]
    B --> C[One SQL joined result]
    C --> D[Hibernate builds initialized graph]
    D --> E[No lazy query for fetched associations]

Benefits

  • Prevents lazy-loading failure for required associations
  • Can solve N+1
  • Defines fetch behavior per use case
  • Leaves mapping defaults unchanged

Risks

  • Large joined results
  • Duplicate root rows
  • Pagination problems
  • Cartesian explosion
  • Excessive memory usage

Common Mistake

Fetch joining multiple unbounded collections.

Senior Follow-up

Use joins for small bounded graphs. Use batch fetching or separate queries for larger collections.


Q11. How do Entity Graphs help with Lazy and Eager Loading?

Answer

Entity Graphs define query-specific fetch plans without changing static relationship mappings.

Entity

@Entity
@NamedEntityGraph(
    name = "Order.withCustomer",
    attributeNodes = {
        @NamedAttributeNode("customer")
    }
)
public class Order {
}

Repository

@EntityGraph(
    value = "Order.withCustomer"
)
Optional<Order> findDetailedById(Long id);

Dynamic Graph

EntityGraph<Order> graph =
    entityManager.createEntityGraph(
        Order.class
    );

graph.addAttributeNodes(
    "customer",
    "items"
);

Map<String, Object> hints =
    Map.of(
        "jakarta.persistence.fetchgraph",
        graph
    );

Order order =
    entityManager.find(
        Order.class,
        orderId,
        hints
    );

Flow

flowchart TD
    A[Repository Method] --> B[Apply Entity Graph]
    B --> C[Choose associations for this use case]
    C --> D[Hibernate generates fetch plan]
    D --> E[Return initialized entity graph]

Benefits

  • Reusable
  • Declarative
  • Avoids permanent EAGER mappings
  • Integrates with Spring Data JPA
  • Supports use-case-specific loading

Common Mistake

Creating very broad graphs that load entire domains.

Senior Follow-up

Entity Graphs should remain small, named by use case, and validated against generated SQL.


Q12. How do DTO projections help with Lazy Loading?

Answer

DTO projections retrieve only the exact data required by an API, page, or report.

They avoid loading managed entity graphs altogether.

DTO

public record OrderSummary(
    Long orderId,
    String customerName,
    OrderStatus status,
    BigDecimal total
) {
}

Query

List<OrderSummary> summaries =
    entityManager.createQuery(
        """
        select new com.codewithvenu.OrderSummary(
            o.id,
            o.customer.name,
            o.status,
            o.total
        )
        from Order o
        where o.status = :status
        order by o.createdAt desc
        """,
        OrderSummary.class
    )
    .setParameter(
        "status",
        OrderStatus.COMPLETED
    )
    .getResultList();

Flow

flowchart LR
    A[API needs summary] --> B[Projection query]
    B --> C[Select required columns]
    C --> D[Create DTO]
    D --> E[No lazy proxy or entity graph]

Benefits

  • No LazyInitializationException
  • Minimal selected columns
  • Lower persistence-context memory usage
  • No dirty-checking overhead
  • Safer API contracts
  • Better performance for read-only use cases

Limitations

  • DTOs are not managed
  • No automatic updates
  • Requires dedicated query design
  • More response models may be required

Common Mistake

Loading full entities first and converting them after unnecessary relationships have already loaded.

Senior Follow-up

DTO projections are often the best choice for list, dashboard, report, and read-model APIs.


Q13. How does Lazy Loading affect JSON serialization?

Answer

Returning Hibernate entities directly from REST controllers can create serialization problems.

Example

@GetMapping("/customers/{id}")
public Customer getCustomer(
    @PathVariable Long id
) {

    return customerService.findCustomer(id);
}

The JSON serializer may call getters for lazy relationships.

This can cause:

  • LazyInitializationException
  • N+1 queries
  • Huge JSON responses
  • Infinite recursion
  • Sensitive-data exposure
  • Unexpected database access

Serialization Flow

sequenceDiagram
    participant Controller
    participant Serializer
    participant Hibernate
    participant Database
    Controller->>Serializer: Return Customer entity
    Serializer->>Serializer: Read basic fields
    Serializer->>Hibernate: Access getOrders()
    Hibernate->>Database: Load Orders
    Database-->>Hibernate: Order rows
    Serializer->>Hibernate: Access nested relationships
    Hibernate->>Database: More queries

Infinite Recursion Example

Customer
  └── Orders
        └── Customer
              └── Orders

Safer Solution

Return DTOs:

public record CustomerResponse(
    Long id,
    String name,
    List<OrderSummary> orders
) {
}

Common Mistake

Using serialization annotations as the only fix.

Annotations can reduce recursion but do not solve query design or entity exposure.

Senior Follow-up

Entities represent persistence models. DTOs represent external contracts.


Q14. What are the performance trade-offs of Lazy and Eager Loading?

Answer

Both strategies involve trade-offs.

Lazy Loading Benefits

  • Smaller initial queries
  • Lower memory usage when associations are unused
  • Better use-case flexibility
  • Suitable for large relationships

Lazy Loading Risks

  • N+1 queries
  • Extra round trips
  • Session-bound access
  • LazyInitializationException
  • Hidden SQL during serialization

Eager Loading Benefits

  • Required data immediately available
  • Fewer lazy-loading failures
  • Convenient for small always-required relationships

Eager Loading Risks

  • Over-fetching
  • Large SQL joins
  • Increased memory usage
  • Duplicate rows
  • Slow query execution
  • Pagination complications
  • Hard-to-override defaults

Performance Matrix

Scenario Better Approach
Customer list DTO projection
Single detail with small association Join fetch or Entity Graph
Large collection LAZY + batch fetching
Reporting DTO projection
Multiple collections Separate queries
Reference to-one data Explicit fetch based on use case
Paginated roots Page IDs, then fetch graph
REST API DTO response

Decision Diagram

flowchart TD
    A[Does use case require association?] -->|No| B[Keep LAZY]
    A -->|Yes| C{Association size}
    C -->|Small and bounded| D[Join Fetch or Entity Graph]
    C -->|Large| E[Batch Fetch or separate query]
    C -->|Only summary data| F[DTO Projection]

Common Mistake

Comparing LAZY and EAGER without considering data volume.

Senior Follow-up

The number of rows and query shape matter more than the annotation itself.


Q15. What are senior-level Lazy vs Eager Loading best practices?

Answer

Senior developers should use conservative mappings and explicit query-level fetch plans.

Best Practices

  • Keep to-many associations LAZY.
  • Consider LAZY for to-one associations.
  • Avoid permanent EAGER mappings when possible.
  • Fetch only what each use case requires.
  • Use DTO projections for read-only APIs.
  • Use Entity Graphs for reusable detail fetch plans.
  • Use JOIN FETCH for bounded associations.
  • Use batch fetching for multiple lazy associations.
  • Avoid accessing entities outside transaction boundaries.
  • Disable or carefully evaluate Open Session in View.
  • Do not return entities directly from controllers.
  • Review generated SQL.
  • Measure query count and row volume.
  • Test with realistic relationship sizes.
  • Monitor production latency and database load.
flowchart TD
    A[Controller] --> B[Transactional Service]
    B --> C[Repository Query with Explicit Fetch Plan]
    C --> D[Hibernate]
    D --> E[Database]
    C --> F[Map Entity or Projection to DTO]
    F --> G[Return DTO to Controller]

Fetch Planning Checklist

Understand API Response
        │
        ▼
Identify Required Relationships
        │
        ▼
Estimate Collection Size
        │
        ▼
Choose Projection / Join / Batch / Graph
        │
        ▼
Review SQL
        │
        ▼
Test Query Count
        │
        ▼
Load Test
        │
        ▼
Monitor Production

Senior Follow-up

The best design is explicit, use-case-driven loading with clear transaction and API boundaries.


Lazy Loading State Diagram

stateDiagram-v2
    [*] --> UninitializedAssociation
    UninitializedAssociation --> InitializedAssociation: Access inside active Session
    UninitializedAssociation --> LazyInitializationException: Access after Session closes
    InitializedAssociation --> [*]

Lazy Loading Request Lifecycle

flowchart TD
    A[Begin Transaction] --> B[Load Parent Entity]
    B --> C[Lazy Association remains uninitialized]
    C --> D{Association accessed?}
    D -->|Yes, transaction active| E[Execute secondary SELECT]
    D -->|No| F[No association query]
    D -->|Yes, transaction closed| G[LazyInitializationException]

Eager Loading Request Lifecycle

flowchart TD
    A[Load Parent Entity] --> B[Detect EAGER associations]
    B --> C[Load association immediately]
    C --> D{SQL strategy}
    D --> E[JOIN query]
    D --> F[Secondary SELECT]
    E --> G[Return initialized graph]
    F --> G

Lazy vs Eager SQL Example

Lazy

Initial query:

SELECT
    c.id,
    c.name
FROM customers c
WHERE c.id = ?;

Later:

SELECT
    o.id,
    o.customer_id,
    o.status
FROM orders o
WHERE o.customer_id = ?;

Eager with Join

SELECT
    c.id,
    c.name,
    o.id,
    o.status
FROM customers c
LEFT JOIN orders o
    ON o.customer_id = c.id
WHERE c.id = ?;

Eager with Secondary Select

SELECT
    c.id,
    c.name,
    c.department_id
FROM customers c
WHERE c.id = ?;
SELECT
    d.id,
    d.name
FROM departments d
WHERE d.id = ?;

N+1 with Lazy Loading

sequenceDiagram
    participant App
    participant Hibernate
    participant Database
    App->>Hibernate: Load Customers
    Hibernate->>Database: SELECT customers
    Database-->>Hibernate: 100 Customers
    loop Each Customer
        App->>Hibernate: Access Orders
        Hibernate->>Database: SELECT orders WHERE customer_id = ?
        Database-->>Hibernate: Orders
    end

Total queries:

1 + 100 = 101

Over-Fetching with Eager Loading

flowchart TD
    A[Load 100 Customers] --> B[EAGER Orders]
    B --> C[Each Customer has 500 Orders]
    C --> D[50,000 Order rows loaded]
    D --> E[High Memory and Query Cost]

LazyInitializationException Example

@Service
public class CustomerService {

    @Transactional(readOnly = true)
    public Customer findCustomer(
        Long customerId
    ) {
        return entityManager.find(
            Customer.class,
            customerId
        );
    }
}
@RestController
public class CustomerController {

    @GetMapping("/customers/{id}")
    public int getOrderCount(
        @PathVariable Long id
    ) {

        Customer customer =
            customerService.findCustomer(id);

        return customer.getOrders().size();
    }
}

The service transaction has ended before the lazy collection is accessed.


Safer DTO Pattern

public record CustomerDetailsResponse(
    Long id,
    String name,
    List<OrderSummaryResponse> orders
) {
}
@Transactional(readOnly = true)
public CustomerDetailsResponse getCustomer(
    Long customerId
) {

    Customer customer =
        customerRepository
            .findWithOrdersById(customerId)
            .orElseThrow(
                CustomerNotFoundException::new
            );

    List<OrderSummaryResponse> orders =
        customer.getOrders()
            .stream()
            .map(
                order -> new OrderSummaryResponse(
                    order.getId(),
                    order.getStatus(),
                    order.getTotal()
                )
            )
            .toList();

    return new CustomerDetailsResponse(
        customer.getId(),
        customer.getName(),
        orders
    );
}

Entity Graph Repository Example

public interface CustomerRepository
    extends JpaRepository<Customer, Long> {

    @EntityGraph(
        attributePaths = {
            "primaryAddress",
            "orders"
        }
    )
    @Query(
        """
        select c
        from Customer c
        where c.id = :id
        """
    )
    Optional<Customer> findDetailedById(
        @Param("id") Long id
    );
}

Join Fetch Repository Example

@Query(
    """
    select distinct c
    from Customer c
    left join fetch c.orders
    where c.id = :id
    """
)
Optional<Customer> findWithOrdersById(
    @Param("id") Long id
);

Batch Fetch Example

@OneToMany(
    mappedBy = "customer",
    fetch = FetchType.LAZY
)
@BatchSize(size = 50)
private List<Order> orders =
    new ArrayList<>();

Global option:

spring.jpa.properties.hibernate.default_batch_fetch_size=50

Pagination Warning

flowchart TD
    A[Paginate Customers] --> B[Fetch Join Orders Collection]
    B --> C[One Customer appears in multiple SQL rows]
    C --> D[Database applies LIMIT to rows]
    D --> E[Incomplete or incorrect parent page]

Safer Two-Step Approach

sequenceDiagram
    participant App
    participant Hibernate
    participant Database
    App->>Hibernate: Fetch page of Customer IDs
    Hibernate->>Database: SELECT IDs with LIMIT/OFFSET
    Database-->>Hibernate: Customer IDs
    Hibernate->>Database: Fetch Customers and required associations by IDs
    Database-->>Hibernate: Complete page graph
    Hibernate-->>App: Stable result

Multiple Collection Fetch Warning

Assume:

  • One customer has 10 orders.
  • One customer has 5 addresses.

One joined query may return:

10 × 5 = 50 rows
flowchart LR
    A[Customer] --> B[10 Orders]
    A --> C[5 Addresses]
    B --> D[Cartesian Product]
    C --> D
    D --> E[50 SQL Rows]

Lazy vs Eager Decision Matrix

Use Case Recommended Approach
Summary list DTO projection
Detail screen Entity Graph or bounded join fetch
Large child collection LAZY + batch fetch
Multiple child collections Separate queries
REST response DTO
Report Projection query
Paginated roots Avoid collection fetch join
Shared reference entity Explicit use-case fetch
Background batch DTO or streaming query
Editable aggregate Load managed entity inside transaction

Common Lazy Loading Mistakes

  • Accessing associations after transaction completion.
  • Returning entities directly to controllers.
  • Calling every getter to force initialization.
  • Relying on Open Session in View.
  • Ignoring N+1 queries.
  • Using LAZY without query-count testing.
  • Logging lazy collections accidentally.
  • Including associations in toString().
  • Including lazy relationships in equals() or hashCode().
  • Mapping DTOs after the persistence context closes.

Common Eager Loading Mistakes

  • Making all associations EAGER.
  • Making large collections EAGER.
  • Assuming EAGER means one query.
  • Ignoring duplicate SQL rows.
  • Fetching several collections together.
  • Combining collection fetch joins with pagination.
  • Loading large graphs for simple list screens.
  • Hiding N+1 behavior behind EAGER secondary selects.
  • Ignoring memory consumption.
  • Using EAGER to avoid transaction-design problems.

Interview Quick Revision

Concept Purpose
LAZY Load association when accessed
EAGER Load association immediately
Proxy Delays entity initialization
Persistent Collection Lazy collection wrapper
LazyInitializationException Lazy access without active context
Open Session in View Keeps context open for HTTP request
JOIN FETCH Query-specific eager loading
Entity Graph Declarative use-case fetch plan
DTO Projection Load only required data
N+1 One parent query plus N child queries
Over-Fetching Loading unused data
Batch Fetching Load associations in ID groups
Persistence Context Required for lazy initialization
Serialization Can trigger hidden lazy queries
Transaction Boundary Defines managed entity lifetime

Key Takeaways

  • Lazy Loading delays association initialization until the relationship is accessed.
  • Eager Loading requires the association to be available immediately when the owner is loaded.
  • LAZY reduces unnecessary data loading but can cause N+1 queries and LazyInitializationException.
  • EAGER avoids detached lazy access but can create over-fetching, large SQL joins, and high memory usage.
  • JPA defaults to EAGER for to-one relationships and LAZY for to-many relationships.
  • EAGER does not guarantee one SQL join; Hibernate may use secondary selects.
  • LazyInitializationException occurs when an uninitialized association is accessed after the persistence context closes.
  • DTO mapping inside the transaction is one of the safest solutions for API responses.
  • JOIN FETCH is useful for bounded relationship graphs but should be used carefully with collections.
  • Entity Graphs provide reusable, query-specific fetch plans.
  • DTO projections are ideal for list pages, dashboards, reports, and read-only APIs.
  • Open Session in View simplifies lazy loading but can hide N+1 queries and weaken architectural boundaries.
  • Returning Hibernate entities directly from REST controllers can cause serialization recursion, hidden SQL, and data exposure.
  • Fetch joins involving multiple collections can produce Cartesian products.
  • Collection fetch joins should generally not be combined directly with pagination.
  • Senior developers use conservative mapping defaults, explicit fetch plans, DTO boundaries, SQL review, query-count tests, and production monitoring.