eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

1. Overview

JPA provides entity graphs to give us control over entity fetch plans at runtime. However, defining those graphs becomes verbose quickly with deeper association hierarchies.

Hibernate 7 introduces an enhanced Hibernate-specific @NamedEntityGraph annotation (org.hibernate.annotations.NamedEntityGraph) that enables defining entity graphs using a text-based graph language. Instead of nested annotation trees, we define the entity graphs using strings.

In this tutorial, we’ll explore the @NamedEntityGraph annotation, including a few examples of how it works.

2. Setup

Before diving into the usage, let’s prepare the proper environment.

2.1. Hibernate ORM

To use the new annotation, we need the Hibernate ORM dependency (version 7.0 or later):

<dependency>
    <groupId>org.hibernate.orm</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>7.3.1.Final</version>
    <scope>compile</scope>
</dependency>

Next, we set the data model.

2.2. Data Model

Here, we use the blog domain model from the JPA tutorial.

Let’s start with the User entity:

@Entity
@Table(name = "app_user")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;

    // standard getters and setters
}

The User entity serves as the base class in a single-table inheritance hierarchy.

Next, let’s define an Author, which represents a User who writes posts:

@Entity
public class Author extends User {

    private String bio;

    // standard getters and setters
}

Similarly, we define a Moderator, which represents a User who manages the site:

@Entity
public class Moderator extends User {

    private String department;

    // standard getters and setters
}

Next, we define the Post entity, which has a @ManyToOne association to User and a @OneToMany association to Comment:

@Entity
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String subject;
    
    @OneToMany(mappedBy = "post")
    private List<Comment> comments = new ArrayList<>();

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn
    private User user;

    // standard getters and setters
}

Finally, the Comment entity references both the Post it belongs to and the User who wrote it:

@Entity
public class Comment {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String reply;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn
    private Post post;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn
    private User user;

    // standard getters and setters
}

At this point, we are ready to explore @NamedEntityGraph in practice.

3. Text-Based Graph Language

Hibernate enables the creation of entity graphs by parsing a textual representation of a comma-separated list of attributes and subgraph specifications.

The grammar is fairly straightforward and can be summarized in three points:

  • simple attributes are comma-separated
  • subgraphs are enclosed in parentheses
  • nested subgraphs are supported

Here, we use this syntax to define the named graphs more concisely.

4. Understanding @NamedEntityGraph

Hibernate offers the @NamedEntityGraph annotation as an alternative to the JPA one, and accepts a graph attribute containing a text representation of a fetch plan.

4.1. Simple Graph

To fetch, we list the attributes we want using parentheses for subgraphs:

@NamedEntityGraph(graph = "subject, user, comments(user)")
@Entity
public class Post {

    // ....
}

Compared to the JPA approach, this is a clear and concise way to define the entity graph, which fetches the subject, user, and comments, and for each comment, also fetches its user. Under the hood, Hibernate parses the graph definition and builds the same Entity Graph structure that the JPA annotation tree would produce.

4.2. Defining Multiple Graphs

Hibernate registers the graph using the entity name by default. However, we can also name a graph to distinguish it from the others using the name attribute:

@NamedEntityGraph(name = "post-with-comment-users", graph = "subject, user, comments(user)")
@Entity
public class Post {

    // ...
}

Furthermore, we can use the @NamedEntityGraph annotation to define multiple entity graphs on the same entity:

@NamedEntityGraph(name = "post-basic", graph = "subject, user, comments")
@NamedEntityGraph(name = "post-with-comment-users", graph = "subject, user, comments(user)")
@NamedEntityGraph(name = "post-with-typed-users", graph = "subject, user(name),
    user(Author: bio), user(Moderator: department)")
@Entity
public class Post {

    // ...
}

Based on the definitions above, we can now use post-basic to fetch only comments without their users, while a detailed page can use post-with-comment-users to load the full comment tree.

Of course, we can also place the annotation at the package level using the package-info.java file. However, the graph string must be prefixed with the entity name:

@org.hibernate.annotations.NamedEntityGraph(name = "package-post-with-comment-users", graph = "Post: subject, user, comments(user)")
package com.baeldung.hibernate.entitygraph.model;

This way, Hibernate can infer which entity the graph belongs to.

4.3. Subtype-Specific Subgraphs

The text syntax also supports targeting specific subtypes in an inheritance hierarchy. A Post references a User, and Hibernate determines the concrete type at runtime.

To that end, we can target each subtype by prefixing the attributes with the subtype name:

@NamedEntityGraph(name = "post-with-typed-user", graph = "subject, user(name), user(Author: bio), user(Moderator: department)")
@Entity
public class Post {

    // ...
}

This graph definition tells Hibernate that the User name is to be fetched in all cases, and if it’s an Author, fetch its bio, or if it’s a Moderator, then fetch the department.

4.4. Map Key Subgraphs

If the attribute is a Map and the map’s key is itself a managed entity, we can define a subgraph for the key by appending .key to the attribute name.

For example, if the Post entity had a Map<User, Comment> attribute called commentsByUser, we can fetch the user name along with the map entry:

@org.hibernate.annotations.NamedEntityGraph(graph = "commentsByUser.key(name)")

Without the .key, the subgraph would apply to the map value type, which is Comment in this case.

5. Using the GraphParser

Hibernate also exposes GraphParser, which enables graph creation at runtime. The parse(), method accepts an entity class, the graph string, and EntityManager:

final EntityGraph<Post> graph = GraphParser
    .parse(Post.class, "subject, user, comments(user)", entityManager);

We can also enrich an existing graph or subgraph at runtime using the parseInto() method:

EntityGraph<Post> graph = GraphParser.createEntityGraph(Post.class);
GraphParser.parseInto(graph, "subject, user", entityManger);
GraphParser.parseInto(graph, "comments(user)", entityManger);

Thus, we ensure proper typing.

6. Merging Graphs

Different aspects of the graph may be defined at multiple parts of an application. Hibernate enables us to combine multiple entity graphs into a single graph, which is the union of all the graphs using the merge() method:

EntityGraph<Post> mergedGraph = EntityGraphs.merge(entityManager, Post.class,
    entityManager.getEntityGraph("post-basic"),
    entityManager.getEntityGraph("post-with-comment-users"));

The merged graph fetches everything from both graphs into a single query; we could’ve used the graph string approach as well with the same effect.

7. Using the Graphs

Once an entity graph has been defined, either at runtime or via an annotation, it needs to be applied to an actual query. There are two possible approaches: one is via the plain JPA EntityManager, as we saw in the JPA tutorial, and the other option is to use Spring Data JPA.

7.1. Using EntityManager

The standard JPA approach uses hints, where we pass a Map, with either jakarta.persistence.fetchgraph or jakarta.persistence.loadgraph as the key and the EntityGraph object as the value:

EntityGraph<Post> graph = entityManager.getEntityGraph("post-with-comment-users");
        
Map<String, Object> hints = Map.of("jakarta.persistence.fetchgraph", graph);
        
Post post = entityManager.find(Post.class, 1L, hints);

The hint key determines the behaviour; using fetchgraph or loadgraph determines what is loaded.

Importantly, we can apply the same hint either to EntityManager.find() or to a JPQL query.

7.2. Using Spring Data JPA

Critically, we can skip the hints while using Spring Data JPA. The @EntityGraph annotation handles the wiring internally once we reference the graph by name on a repository method. In addition, we can set the type explicitly:

public interface PostRepository extends JpaRepository<Post, Long> {

    @EntityGraph(value = "post-basic", type = EntityGraph.EntityGraphType.LOAD)
    List<Post> findAll(String subject);

    @EntityGraph(value = "post-with-comment-users")
    Post findBySubject(String subject);

}

Otherwise, Hibernate treats the graphs as fetchgraph by default.

8. Testing

Let’s explore a few scenarios to verify how these entity graphs work in practice.

8.1. Using a Named Graph

Let’s start with a simple named entity graph post-with-comment-users and apply it using fetchgraph:

@Test
void whenFindWithFetchGraph_thenAssociationsAreLoaded() {
    EntityManager entityManager = entityManagerFactory.createEntityManager();
    EntityGraph<Post> graph = (EntityGraph<Post>) entityManager.getEntityGraph("post-with-comment-users");
    Post post = entityManager.createQuery("Select p from Post p where p.subject = :subject", Post.class)
        .setParameter("subject", "First Post")
        .setHint("jakarta.persistence.fetchgraph", graph)
        .getSingleResult();
    entityManager.close();
    assertNotNull(post);
    assertEquals("First Post", post.getSubject());
    assertTrue(Hibernate.isInitialized(post.getUser()));
    assertTrue(Hibernate.isInitialized(post.getComments()));
    assertEquals(2, post.getComments().size());
    assertTrue(Hibernate.isInitialized(post.getComments().get(0).getUser()));
    assertTrue(Hibernate.isInitialized(post.getComments().get(1).getUser()));
}

Here, we verify that the named entity graph fetches Post and all its associations, including the commenting User.

8.2. Using EntityManager.find()

On the other hand, we can also apply the same named graph using EntityManager.find():

@Test
void whenFindingByIdWithEntityManagerHints_thenAssociationsAreLoaded() {
    EntityManager entityManager = entityManagerFactory.createEntityManager();
    Long postId = entityManager.createQuery("Select p.id from Post p where p.subject = :subject", Long.class)
        .setParameter("subject", "First Post")
        .getSingleResult();
    EntityGraph<Post> graph = (EntityGraph<Post>) entityManager.getEntityGraph("post-with-comment-users");
    Post post = entityManager.find(Post.class, postId, Map.of("jakarta.persistence.fetchgraph", graph));
    entityManager.close();

    assertNotNull(post);
    assertEquals("First Post", post.getSubject());
    assertTrue(Hibernate.isInitialized(post.getUser()));
    assertTrue(Hibernate.isInitialized(post.getComments()));
    assertEquals(2, post.getComments().size());
    assertTrue(Hibernate.isInitialized(post.getComments().get(0).getUser()));
    assertTrue(Hibernate.isInitialized(post.getComments().get(1).getUser()));
}

This shows that the same graph can be applied using JPA hints outside a JPQL query as well.

8.3. Comparing Multiple Graphs

Let’s take a look at how multiple named entity graphs defined on the entity work.

In this case, we use the post-basic named entity graph and compare it with the post-with-comments-user one:

@Test
void whenUsingPostBasicGraph_thenCommentUsersRemainLazy() {
    EntityManager entityManager = entityManagerFactory.createEntityManager();
    EntityGraph<Post> graph = (EntityGraph<Post>) entityManager.getEntityGraph("post-basic");
    Post post = entityManager.createQuery("Select p from Post p where p.subject = :subject", Post.class)
        .setParameter("subject", "First Post")
        .setHint("jakarta.persistence.fetchgraph", graph)
        .getSingleResult();
    entityManager.close();

    assertNotNull(post);
    assertEquals("First Post", post.getSubject());
    assertTrue(Hibernate.isInitialized(post.getUser()));
    assertTrue(Hibernate.isInitialized(post.getComments()));
    assertFalse(Hibernate.isInitialized(post.getComments().get(0).getUser()));
}

As we can see, the Post and all associations are loaded similarly to the previous example; however, the User for the Comments is lazy.

8.4. Subtype-Specific Subgraphs

Next, we verify how to fetch the subtype-specific subgraphs.

Specifically, we defined the post-with-typed-user graph:

@Test
void whenUsingTypedUserGraph_thenSubtypeAttributesAreLoaded() {
    EntityManager entityManager = entityManagerFactory.createEntityManager();
    EntityGraph<Post> graph = (EntityGraph<Post>) entityManager.getEntityGraph("post-with-typed-user");
    Post authorPost = entityManager.createQuery("Select p from Post p where p.subject = :subject", Post.class)
        .setParameter("subject", "First Post")
        .setHint("jakarta.persistence.fetchgraph", graph)
        .getSingleResult();
    Post moderatorPost = entityManager.createQuery("Select p from Post p where p.subject = :subject", Post.class)
        .setParameter("subject", "Second Post")
        .setHint("jakarta.persistence.fetchgraph", graph)
        .getSingleResult();
    entityManager.close();

    assertNotNull(authorPost);
    assertTrue(Hibernate.isInitialized(authorPost.getUser()));
    assertEquals("Author 1", authorPost.getUser().getName());
    assertInstanceOf(Author.class, authorPost.getUser());
    assertTrue(Hibernate.isPropertyInitialized(authorPost.getUser(), "bio"));
    assertEquals("A Baeldung Author", ((Author) authorPost.getUser()).getBio());

    assertNotNull(moderatorPost);
    assertTrue(Hibernate.isInitialized(moderatorPost.getUser()));
    assertEquals("Moderator 1", moderatorPost.getUser().getName());
    assertInstanceOf(Moderator.class, moderatorPost.getUser());
    assertTrue(Hibernate.isPropertyInitialized(moderatorPost.getUser(), "department"));
    assertEquals("A Baeldung Moderator", ((Moderator) moderatorPost.getUser()).getDepartment());
}

This code should fetch the name for all User types and the bio if it’s an Author or the department if it’s a Moderator instance.

8.5. Parsing Graphs at Runtime

Hibernate also enables us to build graphs at runtime.

When using the parse() method, we can create a new graph:

@Test
void whenParsingGraphsAtRuntime_thenAssociationsAreLoaded() {
    EntityManager entityManager = entityManagerFactory.createEntityManager();
    EntityGraph<Post> parsedGraph = GraphParser.parse(Post.class, "subject, user, comments(user)", entityManager);
    EntityGraph<Post> parsedIntoGraph = entityManager.createEntityGraph(Post.class);
    GraphParser.parseInto(parsedIntoGraph, "subject, user", entityManager);
    GraphParser.parseInto(parsedIntoGraph, "comments(user)", entityManager);

    Post parsedPost = entityManager.createQuery("Select p from Post p where p.subject = :subject", Post.class)
        .setParameter("subject", "First Post")
        .setHint("jakarta.persistence.fetchgraph", parsedGraph)
        .getSingleResult();
    Post parsedIntoPost = entityManager.createQuery("Select p from Post p where p.subject = :subject", Post.class)
        .setParameter("subject", "First Post")
        .setHint("jakarta.persistence.fetchgraph", parsedIntoGraph)
        .getSingleResult();
    entityManager.close();

    assertNotNull(parsedPost);
    assertEquals("First Post", parsedPost.getSubject());
    assertTrue(Hibernate.isInitialized(parsedPost.getUser()));
    assertTrue(Hibernate.isInitialized(parsedPost.getComments()));
    assertEquals(2, parsedPost.getComments().size());
    assertTrue(Hibernate.isInitialized(parsedPost.getComments().get(0).getUser()));
    assertTrue(Hibernate.isInitialized(parsedPost.getComments().get(1).getUser()));

    assertNotNull(parsedIntoPost);
    assertEquals("First Post", parsedIntoPost.getSubject());
    assertTrue(Hibernate.isInitialized(parsedIntoPost.getUser()));
    assertTrue(Hibernate.isInitialized(parsedIntoPost.getComments()));
    assertEquals(2, parsedIntoPost.getComments().size());
    assertTrue(Hibernate.isInitialized(parsedIntoPost.getComments().get(0).getUser()));
    assertTrue(Hibernate.isInitialized(parsedIntoPost.getComments().get(1).getUser()));
}

Notably, we can enrich an existing graph with the parseInto() method.

8.6. Merging Graphs

Finally, we can merge multiple graphs into a single graph:

@Test
void whenMergingGraphs_thenUnionOfAttributesIsLoaded() {
    EntityManager entityManager = entityManagerFactory.createEntityManager();
    EntityGraph<Post> basicGraph = (EntityGraph<Post>) entityManager.getEntityGraph("post-basic");
    EntityGraph<Post> postWithCommentUsersGraph = (EntityGraph<Post>) entityManager.getEntityGraph("post-with-comment-users");
    EntityGraph<Post> mergedGraph = EntityGraphs.merge(entityManager, Post.class,
        basicGraph,
        postWithCommentUsersGraph);
    Post post = entityManager.createQuery("Select p from Post p where p.subject = :subject", Post.class)
        .setParameter("subject", "First Post")
        .setHint("jakarta.persistence.fetchgraph", mergedGraph)
        .getSingleResult();
    entityManager.close();

    assertNotNull(post);
    assertEquals("First Post", post.getSubject());
    assertTrue(Hibernate.isInitialized(post.getUser()));
    assertTrue(Hibernate.isInitialized(post.getComments()));
    assertEquals(2, post.getComments().size());
    assertTrue(Hibernate.isInitialized(post.getComments().get(0).getUser()));
    assertTrue(Hibernate.isInitialized(post.getComments().get(1).getUser()));
}

Thus, we get the union of all the merged graphs.

9. Conclusion

In this article, we explored the @NamedEntityGraph annotation of Hibernate as well as the text-based graph language behind it.

First, we started with simple attribute lists, exploring nested subgraphs and subtype-specific subgraphs. Next, we looked into the GraphParser, which enables parsing graphs at runtime, and the EntityGraph merging of multiple graphs. Finally, we compared how simple, clear, and concise the Hibernate version is as compared to the JPA version.

As always, the code is available over on GitHub.

Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

Course – LSD – NPI (cat=JPA)
announcement - icon

Get started with Spring Data JPA through the reference Learn Spring Data JPA:

>> CHECK OUT THE COURSE

eBook Jackson – NPI EA – 3 (cat = Jackson)