When would you choose an interface projection versus a class-based (DTO) projection, and what are dynamic projections?
answer
- Interface = proxy + getters (open/nested capable)
- DTO = constructor match, always closed, serializable
- DTO needs -parameters (Spring Boot default)
- Dynamic = <T> ... Class<T> type argument
- Return entity when you must modify data
basics
~20 sInterface projections use proxies and getters and support open/nested projections. Class (DTO) projections use a constructor and give you a concrete, serializable value type. Dynamic projections let one repository method return different projection types via a Class<T> parameter.
solid answer
~50 sInterface projections are proxy-backed interfaces of getters; they support closed and open (@Value/default-method) styles and nesting, and are great as lightweight internal read models. Class-based (DTO) projections are concrete classes whose constructor parameter names match the selected properties — Spring matches the constructor to the query columns and instantiates real objects; they're closed by nature (constructor forces the exact columns), easy to serialize, and don't rely on proxies, but you can't compute derived values via SpEL. Dynamic projections use a generic method signature — <T> List<T> findByActive(boolean active, Class<T> type) — so the same query can return the full entity, an interface projection, or a DTO depending on the Class you pass at call time. Choose interface projections for flexible internal views, DTOs for a stable serializable contract, and dynamic projections when different callers need different shapes from one query.
code
java · 12 linesrecord UserDto(String username, String email) {} // class/DTO projection
interface UserView { String getUsername(); String getEmail(); } // interface projection
interface UserRepository extends JpaRepository<User, Long> {
List<UserDto> findByActiveTrue(); // DTO result
<T> List<T> findByActive(boolean active, Class<T> type); // dynamic projection
}
// dynamic usage
List<UserView> views = repo.findByActive(true, UserView.class);
List<UserDto> dtos = repo.findByActive(true, UserDto.class);
List<User> ents = repo.findByActive(true, User.class);go deeper
Know interface projections use getters and DTO projections use a constructor/record.
Explain DTOs are always closed and dynamic projections return different shapes via Class<T>.
Advise per-use-case choice and the -parameters/name-matching requirement for DTOs.
Define team conventions: entities never leave the service boundary; standardize on DTOs for contracts, dynamic projections to avoid method sprawl.
## Three ways to shape results ### 1. Interface-based projection An interface of getters; Spring returns a **proxy** per row. Supports **closed** (narrows SELECT), **open** (`@Value` SpEL / default methods), and **nested** projections. Lightweight, no implementation to write. Downsides: it's a proxy (not a plain value object), and open projections lose column narrowing. ### 2. Class-based (DTO) projection A concrete class (or Java **record**) whose **constructor** parameters correspond to the properties you want: ```java record UserDto(String username, String email) {} interface UserRepository extends JpaRepository<User, Long> { List<UserDto> findByActiveTrue(); } ``` Spring Data matches the constructor parameter **names** to the query result and instantiates the DTO. Characteristics: - **Always closed**: the constructor fixes exactly which properties are selected, so the SELECT is narrowed. - A real, immutable value object — trivially serializable, no proxy semantics. - **No open/SpEL** support — you can't declare computed `@Value` fields the way interfaces allow (you compute in application code instead). - Parameter names must match property names (compile with `-parameters`, which Spring Boot enables by default). ### 3. Dynamic projection One repository method returns *whichever* type the caller requests, via a generic `Class<T>` argument: ```java interface UserRepository extends JpaRepository<User, Long> { <T> List<T> findByActive(boolean active, Class<T> type); } // callers List<User> all = repo.findByActive(true, User.class); List<UserView> views = repo.findByActive(true, UserView.class); // interface List<UserDto> dtos = repo.findByActive(true, UserDto.class); // DTO ``` Spring inspects the passed `Class` and applies the matching projection strategy (entity, interface proxy, or DTO). This avoids writing three near-identical query methods. ## Choosing | Need | Pick | |---|---| | Lightweight internal read model, maybe computed/nested fields | Interface projection | | Stable, serializable value/API contract; immutable record | DTO/class projection | | One query, multiple result shapes for different callers | Dynamic projection | | Ability to modify/persist | Return the entity, not a projection | ## Gotchas - DTO constructor parameter names must match property names — a build without parameter-name retention breaks matching (Spring Boot compiles with `-parameters`). - DTOs can't use `@Value` SpEL; move derivations to service code. - Dynamic projection type must be a known interface/DTO/entity; passing an unrelated class fails. - Both interface and DTO closed projections narrow the SELECT; only interface projections offer the open style.
- Why must a DTO projection be compiled with -parameters?Spring Data matches constructor parameter names to result properties. Without retained parameter names (default in Spring Boot builds) the names are lost and matching fails.
- Can a class-based DTO projection use @Value SpEL like an open interface projection?No. DTO projections are always closed and instantiated via constructor; there's no @Value/SpEL mechanism. Compute derived values in application/service code instead.
saying these in an interview costs you the question
- Thinking DTO projections support @Value/open style
- Believing dynamic projections need separate query methods per type
- Assuming DTO parameter names don't matter for mapping