Let's get started with a Microservice Architecture with Spring Cloud:
A Guide to @NamedEntityGraph in Hibernate
Last updated: April 25, 2026
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.
















