skip to content

DTO & Dynamic Projections

Class-based DTO projections bind results into a constructor, and a generic method type parameter lets the caller pick the shape at runtime. The clean answer to returning different views of the same entity without mapping code everywhere.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

4

What is a class-based DTO projection in Spring Data, and why use one instead of returning the entity?

level: juniorimportance: must knowfreq 70%

answer

  1. Return a record instead of the entity
  2. Spring instantiates via constructor, not a proxy
  3. Only needed columns selected (closed)
  4. Detached + immutable read model
  5. Constructor param names must match properties

basics

~20 s

A 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 s

A 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 lines
java
public 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

for a junior

Know it returns a lightweight class/record with only needed fields, fetched efficiently.

for a middle

Explain constructor-name matching and closed-projection column optimization for derived queries.

for a senior

Contrast class vs interface projections, detached/immutable nature, and @PersistenceCreator for multi-constructor classes.

for a principal

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

context

open as a page

How do you return a DTO from a custom @Query using a JPQL constructor expression, and what are its constraints?

level: middleimportance: must knowfreq 68%

basics

~10 s

In JPQL use select new com.example.MyDto(u.firstname, u.lastname) from User u. You must give the DTO's fully-qualified class name and pass constructor arguments in the exact order and types the constructor expects.

open as a page

What are dynamic projections in Spring Data, and how does the generic <T> method parameter choose the result shape at call time?

level: seniorimportance: should knowfreq 55%

basics

~20 s

A dynamic projection is one query method that can return different shapes depending on a Class<T> argument you pass at call time. You write <T> List<T> findByLastname(String lastname, Class<T> type) and call it with the DTO, interface, or entity class you want.

open as a page

When would you choose a class-based DTO projection over an interface projection, and what are the key tradeoffs and pitfalls at scale?

level: principalimportance: should knowfreq 40%

basics

~20 s

Choose a class/record DTO when you want a concrete, immutable, detached object you can pass around and serialize freely. Choose an interface projection when you want lightweight read-only views or need open SpEL-computed values. Both closed forms cut columns.

open as a page