Home/Learn/Hibernate & JPA/Entity Graphs

Entity Graphs

Intermediate
Performance

@EntityGraph specifies which associations to load for a specific query without changing the mapping's default fetch type; load hints override EAGER/LAZY per query.

Overview

Entity Graphs allow you to specify exactly which associations to eagerly load for a specific query, overriding the mapping's default EAGER or LAZY fetch type. This solves the tension between: always LAZY (safe default, causes N+1 when associations are accessed) and always EAGER (loads unneeded data for every query). With Entity Graphs, associations are LAZY by default but you opt into eager loading per query — exactly when needed. Two types: @NamedEntityGraph defined on the entity class (static, reusable) and ad-hoc entity graphs built programmatically. Spring Data JPA supports @EntityGraph on repository methods for a declarative approach.

@NamedEntityGraph on entity and @EntityGraph on repository

Define the graph on the entity class with @NamedEntityGraph, listing the attributePaths to load eagerly. Reference the graph name in the repository method with @EntityGraph. Spring Data JPA translates this to a JOIN FETCH in the generated query.

Java — @NamedEntityGraph with subgraph and @EntityGraph on repository
// Entity — define named entity graphs
@Entity
@Table(name = "orders")
@NamedEntityGraph(
    name = "Order.withCustomerAndItems",
    attributeNodes = {
        @NamedAttributeNode("customer"),         // load customer eagerly
        @NamedAttributeNode(value = "items",
                            subgraph = "items-with-product")  // with sub-graph
    },
    subgraphs = {
        @NamedSubgraph(
            name = "items-with-product",
            attributeNodes = @NamedAttributeNode("product")  // items → product
        )
    }
)
@NamedEntityGraph(
    name = "Order.withCustomerOnly",
    attributeNodes = @NamedAttributeNode("customer")  // lighter graph
)
public class Order {
    @ManyToOne(fetch = FetchType.LAZY)
    private Customer customer;

    @OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
    private List<OrderItem> items;
}

// Repository — apply graph per query method
public interface OrderRepository extends JpaRepository<Order, Long> {

    @EntityGraph("Order.withCustomerAndItems")
    Optional<Order> findWithFullGraphById(Long id);

    @EntityGraph("Order.withCustomerOnly")
    List<Order> findByStatus(OrderStatus status);

    // Without @EntityGraph — uses default LAZY fetch
    Page<Order> findByCustomerId(Long customerId, Pageable pageable);
}

Ad-hoc entity graphs and FETCH vs LOAD types

Build entity graphs programmatically with EntityManager when the graph cannot be defined statically. GraphType.FETCH treats unlisted associations as LAZY (even if mapped EAGER). GraphType.LOAD treats unlisted associations with their mapping default. Use FETCH for precise control over what is loaded.

Java — programmatic entity graph and FETCH vs LOAD hint difference
// Programmatic entity graph (ad-hoc)
@Service
public class OrderService {

    @PersistenceContext
    private EntityManager em;

    public Order findOrderWithDetails(Long orderId) {
        EntityGraph<Order> graph = em.createEntityGraph(Order.class);
        graph.addAttributeNodes("customer");           // load customer
        Subgraph<OrderItem> itemsGraph = graph.addSubgraph("items");
        itemsGraph.addAttributeNodes("product");       // items.product

        return em.find(Order.class, orderId, Map.of(
            "jakarta.persistence.fetchgraph", graph   // FETCH type: unlisted → LAZY
            // "jakarta.persistence.loadgraph", graph // LOAD type: unlisted → default mapping
        ));
    }
}

// FETCH vs LOAD hint keys:
// jakarta.persistence.fetchgraph → EntityGraph.FETCH
//   Unlisted associations: always LAZY (ignores mapping default)
//   → use this for precise "load only what I need" control

// jakarta.persistence.loadgraph → EntityGraph.LOAD
//   Unlisted associations: use their mapping default (LAZY or EAGER)
//   → use this when you only want to ADD eagerness, not override

// @EntityGraph in Spring Data uses FETCH by default (safer)
@EntityGraph(attributePaths = {"customer", "items.product"},
             type = EntityGraph.EntityGraphType.FETCH)
List<Order> findByStatusAndCreatedAtAfter(OrderStatus s, LocalDateTime dt);

Entity graph vs JOIN FETCH — when to use each

Both @EntityGraph and JOIN FETCH in @Query load associations eagerly. Key differences: @EntityGraph works with Spring Data derived methods and count queries (Pageable); JOIN FETCH in @Query gives more control but breaks count queries for pagination. Choose @EntityGraph for Spring Data pagination; choose JOIN FETCH for complex multi-join queries.

Java — @EntityGraph for pagination vs JOIN FETCH for single-entity lookups
// Pagination PROBLEM with JOIN FETCH — Hibernate warns about in-memory pagination
@Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.status = :status")
Page<Order> findByStatus(@Param("status") OrderStatus status, Pageable pageable);
// WARNING: HHH90003004: firstResult/maxResults specified with collection fetch;
// applying in memory! → Hibernate loads ALL rows then paginates in memory

// SOLUTION: use @EntityGraph for pagination (Spring Data handles count query)
@EntityGraph(attributePaths = {"items"})
Page<Order> findByStatus(OrderStatus status, Pageable pageable);
// → Spring Data generates separate count query; JOIN FETCH on data query only

// When JOIN FETCH is correct: single entity lookup or no pagination
@Query("SELECT o FROM Order o " +
       "JOIN FETCH o.customer c " +
       "JOIN FETCH o.items i " +
       "JOIN FETCH i.product " +
       "WHERE o.id = :id")
Optional<Order> findFullOrderById(@Param("id") Long id);
// → safe: no pagination, single result, explicit JOIN FETCH chains

// Cartesian product warning: fetching TWO bag collections
// SELECT o FROM Order o JOIN FETCH o.items JOIN FETCH o.tags
// → MultipleBagFetchException or cartesian product — use Set not List, or two queries

Key Points to Remember

  • 1@EntityGraph overrides the mapping's default fetch type per query — LAZY by default, eager opt-in where needed
  • 2@NamedEntityGraph on the entity class + @EntityGraph on the repository method is the cleanest declarative approach
  • 3EntityGraph.FETCH treats unlisted associations as LAZY; EntityGraph.LOAD uses their mapping default
  • 4Use @EntityGraph for paginated queries — it avoids Hibernate's in-memory pagination warning from JOIN FETCH
  • 5JOIN FETCH is safe for single entity lookups; @EntityGraph is safer for paginated list queries
  • 6Fetching two @OneToMany collections (bags) with JOIN FETCH causes MultipleBagFetchException — use @EntityGraph with two queries or Set instead of List

Interview Questions

Sign in to ask Aria
1

What problem does @EntityGraph solve that always-LAZY and always-EAGER mappings cannot?

MediumThoughtworks
2

What is the difference between EntityGraph.FETCH and EntityGraph.LOAD hint types?

HardNetflix
3

Why does JOIN FETCH cause issues with paginated queries (Pageable) in Spring Data?

HardAmazon
4

How would you load an Order with its customer and all order items (with products) in a single query?

MediumInfosys
5

What is MultipleBagFetchException and how do you fix it?

HardBooking.com

Ask Aria about Entity Graphs

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…