What is Projection in JPA
A Projection in JPA and Spring Data is a mechanism for retrieving a strictly defined subset of entity fields or calculated expressions directly from the database rather than loa...
A Projection in JPA and Spring Data is a mechanism for retrieving a strictly defined subset of entity fields or calculated expressions directly from the database rather than loading entire managed entities. Utilizing projections is one of the most effective query optimization strategies: it minimizes network bandwidth between the database and the JVM, reduces heap memory allocation, and completely eliminates Hibernate’s overhead related to First-Level Cache management, entity snapshotting, and dirty checking.
🟢 Junior Level
Why Projections are Essential
When executing a standard query like SELECT u FROM User u, Hibernate:
- Generates a
SELECT *query fetching all table columns (including wide text columns, metadata, and audit fields). - Instantiates full
UserJava objects in heap memory. - Clones the state into an internal
loadedStatearray in the First-Level Cache (L1) for dirty checking. - Initializes relation proxy handlers.
If a view only requires displaying a user’s name and email address in a summary table, loading full entities wastes substantial system resources. Projections fetch only the required columns:
// Immutable Java Record:
public record UserSummary(String name, String email) {}
// JPQL Constructor Expression:
TypedQuery<UserSummary> query = entityManager.createQuery("""
SELECT new com.example.dto.UserSummary(u.name, u.email)
FROM User u
""", UserSummary.class);
List<UserSummary> summaries = query.getResultList();
// Generated SQL: SELECT name, email FROM users;
// Bypasses L1 snapshotting and dirty checking overhead!
🟡 Middle Level
5 Projection Varieties in JPA and Spring Data
1. Untyped Raw Array (Object[])
List<Object[]> rows = entityManager.createQuery("SELECT u.name, u.email FROM User u").getResultList();
String name = (String) rows.get(0)[0]; // Brittle and type-unsafe
2. Tuple Projection (jakarta.persistence.Tuple)
Provides column access via string aliases with type safety at extraction:
List<Tuple> tuples = entityManager.createQuery("""
SELECT u.name AS userName, u.email AS userEmail FROM User u
""", Tuple.class).getResultList();
String name = tuples.get(0).get("userName", String.class);
3. DTO Constructor Expressions (SELECT new ...)
Standard JPA mechanism supporting both standard Java classes and Java Records:
public record UserInfoDto(Long id, String email) {}
@Query("SELECT new com.example.dto.UserInfoDto(u.id, u.email) FROM User u")
List<UserInfoDto> fetchUsers();
Requirement: The fully qualified class name including package must be specified in the JPQL query string (new com.example.dto.UserInfoDto(...)).
4. Closed Interface Projections (Spring Data JPA)
Spring Data allows defining interface contracts with getter methods:
public interface UserContactView {
String getName();
String getEmail();
}
public interface UserRepository extends JpaRepository<User, Long> {
List<UserContactView> findAllByActiveTrue();
}
Spring Data inspects the interface getters and generates optimized SQL querying only the name and email columns.
5. Dynamic Projections
A single repository method can return different projection representations dynamically based on a type token:
public interface UserRepository extends JpaRepository<User, Long> {
<T> List<T> findByStatus(String status, Class<T> type);
}
// Usage:
List<UserSummary> list1 = repo.findByStatus("ACTIVE", UserSummary.class);
List<UserDetailDto> list2 = repo.findByStatus("ACTIVE", UserDetailDto.class);
🔴 Senior Level
The Open Projection Trap in Spring Data (@Value / SpEL)
Spring Data supports “Open Projections” using the Spring Expression Language (SpEL):
// ❌ HIDDEN PERFORMANCE DEGRADATION:
public interface UserFullNameView {
@Value("#{target.firstName + ' ' + target.lastName}")
String getFullName();
}
Why Open Projections are Harmful:
- With a Closed Projection (
String getFirstName()), Spring Data optimizes the SQL query:SELECT first_name FROM users. - With an Open Projection using
@Value, Spring Data cannot reliably determine which fields will be referenced by the SpEL expression during runtime evaluation. - Consequence: Spring Data executes a full
SELECT *, loading the entire database row into memory, wraps it inside a dynamic proxy, and evaluates the expression in the JVM. All database and network bandwidth optimization is lost.
Projections and Collections: JPA Specification Limitations
The JPA specification prohibits passing collections directly into constructor expressions:
// ❌ INVALID JPQL SYNTAX:
public record OrderWithItemsDto(Long orderId, List<ItemDto> items) {}
// Compiling this query throws a parsing error:
@Query("SELECT new com.example.OrderWithItemsDto(o.id, o.items) FROM Order o")
List<OrderWithItemsDto> findOrders();
Senior Strategies for Projecting 1:N Hierarchies:
- Two-Query Staged Retrieval: Retrieve the parent DTOs first, then fetch the child DTOs via an
IN (:parentIds)query, grouping children into their parents in application memory usingCollectors.groupingBy(). - Hibernate
TupleTransformer: In Hibernate 6, transform flat SQLJOINrows into a nested DTO structure using a customTupleTransformer. - Blaze-Persistence: An enterprise extension on top of JPA that provides native support for nested collection projections directly in queries.
4 Tricky Questions
1. Are DTOs or projections retrieved via SELECT new UserDto(...) managed by Hibernate’s Dirty Checking mechanism?
Answer:
- No, absolutely not.
- DTO objects are not JPA entities (
@Entity). They are instantiated directly via standard Java constructors (new UserDto(...)) populated from the JDBCResultSet. - They are not stored in the
StatefulPersistenceContext.entitiesByKeymap and have no correspondingEntityEntry. - Mutating properties on a DTO will never trigger an SQL
UPDATEstatement upon transaction commit.
2. Why are Java Records preferred over Spring Data Interface Projections in high-throughput (HighLoad) systems?
Answer:
- For interface projections, Spring Data creates a dynamic JDK proxy (
java.lang.reflect.Proxy) wrapping an internal attribute map for every single row in the result set. - Proxy generation and reflective getter invocation introduce measurable CPU overhead and heap allocation pressure when processing large result sets (10,000+ rows).
- Java Records compile to native bytecode constructors. They are instantiated directly without reflection, providing minimal heap footprint and zero proxy indirection.
3. Can you combine Spring Data pagination (Pageable) with an interface or record projection?
Answer:
- Yes, fully supported.
- A repository method such as
Page<UserView> findByActive(boolean active, Pageable pageable)executes two optimized queries:SELECT COUNT(id) FROM users WHERE active = ?(calculating the total count for pagination).SELECT id, name, email FROM users WHERE active = ? LIMIT ? OFFSET ?(fetching only the projected columns for the requested page slice).
4. What happens if a DTO constructor parameter type does not match the JPQL query expression type (e.g., Long in constructor vs. Integer from the database)?
Answer:
- Hibernate uses reflection to look up a constructor whose parameter types match the query expression types exactly.
- The JPA query compiler does not perform automatic widening conversions (e.g., auto-casting
IntegertoLongorDoubletoBigDecimal). - If no matching constructor is located, query bootstrap fails at runtime with:
org.hibernate.query.SemanticException: Unable to locate appropriate constructor on class [com.example.UserDto].
🎯 Interview Cheat Sheet
30-Second Elevator Pitch
“JPA Projections allow fetching only required columns rather than full managed entities. This reduces network payload size, cuts JVM heap memory allocation, and eliminates the overhead of First-Level Cache snapshotting and dirty checking. Core patterns include DTO Constructor Expressions (using Java Records
SELECT new ...) and Spring Data closed interface projections. A critical architectural caveat: open interface projections using@ValueSpEL expressions force Spring Data to fetch all columns (SELECT *), defeating the optimization. For read-only queries and REST API responses, projecting directly into Java Records is the industry gold standard.”
Key Production Metrics & Indicators
- GC Allocation Rate (MB/sec): Replacing entity queries with DTO projections typically reduces per-request memory allocation by 60–80%.
- Network Ingress/Egress on Database Links: Significant reduction in transferred bytes when excluding wide text/binary columns.
- HikariCP Connection Hold Time: Queries return faster because less tabular data is streamed and serialized across the network.
Red Flags (What NOT to Say)
- ❌ “Modifying a DTO projection updates the database when the transaction commits.” — Projections are plain unmanaged objects; dirty checking does not track them.
- ❌ “Open projections with
@Valueare the most performant way to compute fields.” — False; open projections cause Hibernate to fetch all entity columns. - ❌ “You can pass child
@OneToManycollections directly into JPQL constructor expressions.” — Forbidden by the JPA specification; causes query parsing errors. - ❌ “Always load full entities for read-only reports and API endpoints.” — Anti-pattern causing excessive memory consumption and potential $N+1$ query issues.
Related Topics
- What is JPQL and How Does It Differ from SQL — Query language fundamentals
- What is Criteria API and When to Use It — Programmatic projections
- How to Avoid Infinite Recursion When Serializing Entities — DTO mapping for REST APIs
- How to Use JOIN FETCH to Solve the N+1 Problem — Comparison with entity fetching