spring-data-jpa-entity-graph: choosing the fetch graph at runtime, not at compile time
Spring Data JPA extension allowing full dynamic usage of EntityGraph on repositories
At a glance
- What is it?
- Stock Spring Data JPA only supports EntityGraph through annotations, which means the fetch plan for a repository method is fixed before compilation. This extension from Cosium changes that: the graph becomes an ordinary method argument, so the same repository method can load a shallow object for a list view and a deep one for a detail page, chosen at runtime.
- Who is it for?
- Adopt spring-data-jpa-entity-graph if the same repository method serves both a list view and a detail view and the fetch plan differs between them, because moving the graph from an annotation to an argument is a smaller change than duplicating the query.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly Java, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem the extension removes
Spring Data JPA only supports EntityGraph through annotations, so for a repository method you must select at most one EntityGraph before compilation, and that prevents you from choosing the best EntityGraph considering the runtime context.
That is a real constraint rather than a stylistic one. A fetch graph in JPA says which associations get loaded eagerly and which stay lazy, and it changes the number of queries the persistence context issues, which changes whether you get an N+1 problem or not. With an annotation, that decision is made when the code is written, and every call site of that method gets the same answer.
The typical situation is a list screen and a detail screen. The list wants the entity with one or two shallow associations, because loading the whole graph for two hundred rows is a slow query you do not want. The detail screen wants the deep graph, with the maker and the maker's address. If both go through findById, the annotation forces one of those to be wrong.
The fix in Spring Data JPA without this library is to write a separate repository method for each shape, or to use different specifications, or to manage the persistence context yourself. All of those work around a compile-time decision that a runtime argument would make directly.
Spring Data JPA EntityGraph allows you to choose EntityGraph at runtime, and the choice is elegantly made by passing EntityGraph as an argument to any Spring Data JPA repository method.
The primary language is Java, the licence is MIT, the default branch is master, and the recent releases are 4.0.4 from 2026-04-23, 4.0.3 from 2026-04-22 and 4.0.2 from 2026-03-04. The last push was on 2026-10-19 with the project not archived. The Maven coordinates are the group com.cosium.spring.data and the artifact spring-data-jpa-entity-graph.
The setup, which is a dependency and one annotation
The quick start is three steps, and the first of them is a version check.
Select the correct version from the compatibility matrix, because the library tracks the Spring Data JPA version it extends and the matrix in the README is the authority on which pair works together.
Then add the dependency alongside Spring Data JPA:
<dependency>
<groupId>com.cosium.spring.data</groupId>
<artifactId>spring-data-jpa-entity-graph</artifactId>
<version>${spring-data-jpa-entity-graph.version}</version>
</dependency>The version is a property, ${spring-data-jpa-entity-graph.version}, which is the usual convention of keeping dependency versions in one place in the parent POM.
Then the third step is the one that makes the rest work, and it is a single annotation on your application class:
@SpringBootApplication
@EnableJpaRepositories(repositoryFactoryBeanClass = EntityGraphJpaRepositoryFactoryBean.class)
public class App {
//...
}Setting the repository factory bean class to EntityGraphJpaRepositoryFactoryBean is what makes Spring Data build repository proxies that understand an extra EntityGraph parameter. It is a one-line change to the bootstrap class, and it is the whole integration point.
The EntityGraph type used in the examples is one of the two implementations the library provides, and the naming convention there is the key to understanding the design. The argument to a repository method is not the JPA EntityGraph interface itself but a wrapper, and and the README shows a loading factory method on each. The example calls it NamedEntityGraph.loading("bar"), so the loading method takes a graph name.
The reason for the wrapper is that it lets the library pass information alongside the graph rather than only the graph. A runtime-selected graph is a different thing from a plain EntityGraph, and a wrapper type is how you carry that difference through a method signature without changing Spring Data's own interfaces.
Custom repository methods, where the change is smallest
The first usage section is the simplest and probably the most common case.
If you want to define a custom repository method accepting an EntityGraph, just do it. Given an entity having an attribute named label of type String, the declaration is:
interface MyRepository extends Repository<MyEntity, Long> {
Optional<MyEntity> findByLabel(String label, EntityGraph entityGraph);
}And the call passes the graph as the last argument:
myRepository.findByLabel("foo", NamedEntityGraph.loading("bar"));That is the whole change to your code. The query derivation still works, because Spring Data derives the query from the method name and the entity metadata, and the extra parameter is handled by the repository factory the annotation installed.
What this buys is that the same derived query can be issued with different fetch plans. A service layer can hold one method and pass a shallow graph in a list context and a deep one in a detail context, and the persistence behaviour follows the call rather than the annotation.
There is a subtlety in the method signature worth noticing. The EntityGraph parameter comes last, after the query criteria, which is what lets Spring Data's parameter resolution treat the leading parameters as the query arguments and the trailing one as the graph. So the convention is fixed: the graph goes at the end.
The argument type is the wrapper rather than the plain JPA interface, so the code imports the library's own EntityGraph class and shadows the javax or jakarta one. That is a readability cost that comes with the design, and it is worth being aware of when reading a project that uses this library, because an unqualified EntityGraph in a repository signature means the library's type.
The pre-defined method overloads, and the interface matrix
The second usage section is the one that does the bulk of the work, because it covers the methods Spring Data JPA already defines.
The library provides repository interfaces extending Spring Data JPA. For each pre-defined method, the provided interfaces overload the method with EntityGraph as an additional argument. The example given is Spring Data JPA CrudRepository defining Optional<T> findById(ID id), which is overloaded by EntityGraphCrudRepository as Optional<T> findById(ID id, EntityGraph entityGraph).
To be able to use these overloaded methods, you must extend one of the provided repository interfaces, and the README gives the full mapping table:
|-----------------------------------------------------------------------|---------------------------------------------------------------------------------------|
| CrudRepository | EntityGraphCrudRepository |
| JpaRepository | EntityGraphJpaRepository |
| JpaSpecificationExecutor | EntityGraphJpaSpecificationExecutor |
| ListCrudRepository | EntityGraphListCrudRepository |
| ListPagingAndSortingRepository | EntityGraphListPagingAndSortingRepository |
| PagingAndSortingRepository | EntityGraphPagingAndSortingRepository |
| QueryByExampleExecutor | EntityGraphQueryByExampleExecutor |
| QuerydslPredicateExecutor | EntityGraphQuerydslPredicateExecutor |
| Repository | EntityGraphRepository |So CrudRepository maps to EntityGraphCrudRepository, JpaRepository to EntityGraphJpaRepository, JpaSpecificationExecutor to EntityGraphJpaSpecificationExecutor, and the same pattern for the List and PagingAndSorting variants, the QueryByExampleExecutor, the QuerydslPredicateExecutor and the base Repository interface.
That is nine interfaces, and they are generated rather than hand-written, which is what the repository layout shows. There is a core/ directory, a generator/ directory, and a release.sh script, so the mapping is derived from the Spring Data JPA interfaces and regenerated when Spring Data changes. Writing those overloads by hand would be unmaintainable, and generating them means the library stays in step with the framework it extends.
Using one is then a matter of changing the extends clause:
interface MyRepository extends EntityGraphCrudRepository<MyEntity, Long> {
}And the call takes the graph as the second argument:
myRepository.findById(1L, NamedEntityGraph.loading("foo"));The practical difference from the custom method case is that this does not change your method names or your service code, only the interface your repository extends, and the calls that need a graph gain an argument while the calls that do not keep the original signature. So the extension is additive at the interface level and optional at the call level.
The two graph implementations, and why both exist
The library ships two ways of expressing a graph, and they cover different situations.
DynamicEntityGraph allows you to create an on-the-fly EntityGraph by defining their attribute paths, and the README notes this is similar to Spring's ad-hoc attribute paths.
The example entities make the case concrete. A Maker has a lazily fetched address, and a Product has a lazily fetched brand and a lazily fetched maker:
@Entity
class Maker {
//...
@OneToOne(fetch = FetchType.LAZY)
private Address address;
//...
}@Entity
class Product {
@Id
private long id = 0;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
private Brand brand;
@ManyToOne(fetch = FetchType.LAZY)
private Maker maker;
//...
} So a repository method takes the name and the graph:
public interface MyRepository extends Repository<Product, Long> {
List<Product> findByName(String name, EntityGraph entityGraph);
}And the call builds the graph on the fly with an ad-hoc product(brand, maker(address)) shape:
myRepository.findById(1L, DynamicEntityGraph.loading().addPath("brand").addPath("maker", "address").build());The path syntax reads like a nested property expression, brand is a direct association, and maker with address is two levels deep. The loading call on the builder is the same loading-versus-fetching distinction as before.
NamedEntityGraph is the other implementation, and it allows you to reference an EntityGraph by its name. Such an EntityGraph must have beforehand been registered through EntityManager#createEntityGraph(String graphName) or declared using the @NamedEntityGraph annotation.
So the two implementations correspond to two different lifetimes. A dynamic graph is built at the call site, so it is right when the shape depends on the request, for example a report that wants one extra field. A named graph is declared once, either in an annotation or at bootstrap, and referenced by name, so it is right when the shape is a known set of variants, and it is the one you can put in a configuration file or a test.
The dynamic form also has the advantage of not requiring the entity to be recompiled when you change the graph, which matters in a large entity model where annotations on the entity are inconvenient to edit.
How to evaluate it, and what to check first
There are four things to consider.
First, the version matrix. The library is an extension of Spring Data JPA and tracks it closely, and the recent releases are numbered 4.0.x. The first step in the quick start is picking the right version, and getting that wrong means the repository factory does not behave as the extension expects. Check the matrix rather than assuming the newest works.
Second, the cost of the mechanism. The library works by swapping the repository factory bean class, so every repository proxy in your application is created by a different factory than the stock one. That is a broad change to how your repositories are built, and it means the extension has to be compatible with every Spring Data feature you use, including specifications, the Querydsl executor and the example executor, which is why there are nine overloaded interfaces covering them. If you rely on a repository feature that is not in that list, you will find out at the point where you try to pass a graph to it.
Third, what a runtime graph does to your query pattern. The library does not change the SQL, it changes which associations are fetched, so the number of queries and the shape of the persistence context still depend on the graph you pass. The benefit is that the choice is now local to the call, and the risk is that a deep graph requested from a list endpoint will still be slow. The extension gives you the ability to choose, not the knowledge of what to choose.
Fourth, the EntityGraph naming collision. The argument type in the repository signature is the library's own EntityGraph, not the JPA one, and the same simple name is used for the wrapper. In a codebase that also uses the plain JPA type, that means an import that shadows the obvious meaning, and reading a repository interface without knowing which is which can be genuinely confusing.
What you get is a small, well-scoped extension that turns a compile-time annotation into a runtime argument, with both a dynamic and a named implementation and the full set of Spring Data repository interfaces covered. The costs are the repository factory swap, the version coupling, and the import shadowing.
Editorial conclusion
Adopt spring-data-jpa-entity-graph if the same repository method serves both a list view and a detail view and the fetch plan differs between them, because moving the graph from an annotation to an argument is a smaller change than duplicating the query. Do not adopt it without checking the compatibility matrix, since the library tracks a specific Spring Data JPA version, and remember that the repository factory bean class is swapped application-wide, so every repository proxy in your context comes from a different factory than the stock one. Before you commit, confirm that the Spring Data features you rely on are among the nine interfaces the extension covers, and read the code with the import shadowing in mind, since an unqualified EntityGraph in a repository signature is the library's wrapper type rather than the JPA interface.
Frequently asked questions
What does spring-data-jpa-entity-graph add to Spring Data JPA?
Stock Spring Data JPA only supports EntityGraph through annotations, so at most one graph can be selected per repository method before compilation. The extension lets you choose the EntityGraph at runtime by passing it as an argument to any Spring Data JPA repository method, with no change to the query derivation.
How do I set up spring-data-jpa-entity-graph?
Pick the matching version from the compatibility matrix, add the com.cosium.spring.data:spring-data-jpa-entity-graph dependency next to Spring Data JPA, and set the repository factory bean class to EntityGraphJpaRepositoryFactoryBean in the EnableJpaRepositories annotation on your application class.
How do I use an EntityGraph with a custom repository method?
Declare the method with an EntityGraph parameter after the query parameters, for example Optional<MyEntity> findByLabel(String label, EntityGraph entityGraph), then call it with the graph as the last argument, for example myRepository.findByLabel("foo", NamedEntityGraph.loading("bar")).
What is the difference between DynamicEntityGraph and NamedEntityGraph?
DynamicEntityGraph builds an EntityGraph on the fly from attribute paths, similar to Spring's ad-hoc attribute paths, for example product(brand, maker(address)). NamedEntityGraph references a graph by name, and that graph must already have been registered through EntityManager#createEntityGraph or declared with the @NamedEntityGraph annotation.
Which Spring Data JPA repository interfaces does the extension cover?
Nine, each with a matching EntityGraph-prefixed interface: CrudRepository to EntityGraphCrudRepository, JpaRepository to EntityGraphJpaRepository, JpaSpecificationExecutor to EntityGraphJpaSpecificationExecutor, plus the List and PagingAndSorting variants, QueryByExampleExecutor, QuerydslPredicateExecutor and the base Repository. The overloads are generated rather than written by hand.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/cosium-spring-data-jpa-entity-graph)Was this useful?