What is a class-based DTO projection in Spring Data, and why use one instead of returning the entity?
answer
- Return a record instead of the entity
- Spring instantiates via constructor, not a proxy
- Only needed columns selected (closed)
- Detached + immutable read model
- Constructor param names must match properties
basics
~20 sA repository query method returns a plain data-carrier class (often a record) holding only the fields you need, instead of the full entity. Spring Data fetches only those columns, so it is smaller and faster.
solid answer
~40 sA class-based DTO projection has a repository query method return a dedicated class (a POJO or Java record) that carries only the fields the caller needs, rather than the managed JPA entity. You declare it by using the DTO type as the method return type, e.g. List<UserNameDto> findByActiveTrue(). Spring Data matches the DTO constructor parameters to entity properties and, for a closed/derived query, generates SQL that selects only those columns. Benefits: less data over the wire, no lazy-loading surprises, the DTO is detached (not tied to the persistence context), and it forms a stable API contract decoupled from the schema. It is the class analogue of an interface projection, but you get a concrete instantiated object instead of a proxy.
code
java · 7 linespublic record UserNameDto(String firstname, String lastname) {}
public interface UserRepository extends JpaRepository<User, Long> {
// Derived query: Spring Data matches constructor params to entity
// properties and selects only firstname, lastname.
List<UserNameDto> findByActiveTrue();
}go deeper
Know it returns a lightweight class/record with only needed fields, fetched efficiently.
Explain constructor-name matching and closed-projection column optimization for derived queries.
Contrast class vs interface projections, detached/immutable nature, and @PersistenceCreator for multi-constructor classes.
Frame DTOs as a stable read-model contract decoupling API from schema; weigh against entity graphs and CQRS read models.
**The problem.** A JPA entity is a managed object attached to the persistence context (EntityManager). Returning it from a query loads all its mapped columns, may trigger lazy loading of associations later, and leaks your schema into the API. A **DTO (Data Transfer Object) projection** solves this by returning a purpose-built class carrying only the fields you need. **Class-based vs interface projection.** Spring Data supports two projection styles: - *Interface projection*: you declare an interface with getters; Spring Data returns a runtime **proxy** implementing it. - *Class-based (DTO) projection*: you declare a real class or **Java record**; Spring Data **instantiates** it via its constructor. The returned object is a concrete instance, not a proxy. **How you declare it.** Set the DTO as the query method's return type: ```java record UserNameDto(String firstname, String lastname) {} interface UserRepository extends Repository<User, Long> { List<UserNameDto> findByActiveTrue(); } ``` For a **derived query** (method name parsed by Spring Data), the framework inspects the DTO's constructor, matches each parameter **name** to an entity property, and builds a query selecting only those properties. Because the underlying JPQL becomes a *constructor expression* (`select new ...`), only the needed columns are fetched — this is a **closed projection**, which allows query optimization. **Constructor matching rules.** The DTO must expose a constructor whose parameter names match the property names you want. Records give you this for free (the canonical constructor's component names are the property names). If the class has multiple constructors, mark the intended one with `@PersistenceCreator` so Spring Data knows which to use. Parameter **order** does not matter for name-based matching, but names must match (compile with `-parameters` so names survive, though records preserve them regardless). **Detached and immutable.** The DTO is **not** a managed entity — mutating it does nothing to the database, and it survives after the persistence context closes (no `LazyInitializationException`). Records make it immutable, which is ideal for a read model. **When to use.** Read-only views, list/summary screens, API responses, anything where you don't need to modify and persist the object. Use the entity when you need to update and flush changes back. **Gotchas.** (1) With a hand-written `@Query`, name-based selection is *not* automatic — you must write a JPQL constructor expression yourself (see the constructor-expression question). (2) Class-based DTOs do **not** support open (SpEL `@Value`) expressions — that is an interface-projection-only feature. (3) Nested DTO projections have limited support compared to interface projections.
- Does a class-based DTO projection return a managed entity?No. It returns a plain, detached instance created via the constructor. It is not attached to the persistence context, so changes are not tracked or persisted, and it cannot throw LazyInitializationException.
- What must match between the DTO and the entity for a derived query?The DTO constructor parameter names must match the entity property names. Records satisfy this automatically because component names are preserved.
saying these in an interview costs you the question
- Saying the DTO is still managed and mutations persist
- Claiming class DTOs support @Value SpEL open expressions
- Thinking you always need a manual @Query for DTO projections