skip to content

In JPA/Hibernate, how do you make a JPQL/HQL query return instances of your own DTO class instead of managed entity objects, and what must that class provide?

level: juniorimportance: must knowfreq 62%

answer

  1. select new com.app.Dto(...)
  2. fully qualified name, matching constructor
  3. no collections in arguments
  4. results are unmanaged — writes do nothing
  5. records + implicit instantiation (JPA 3.2 / HB 6.3)

basics

~20 s

Use a constructor expression: select new com.app.UserDto(u.id, u.name) from User u. Name the class fully qualified and give it a constructor whose parameter types match the selected expressions in order. Results are plain objects, not managed entities.

solid answer

~50 s

The portable way is a **constructor expression** in JPQL/HQL: ``` select new com.app.UserDto(u.id, u.name) from User u where u.active = true ``` Rules: the class is written with its **fully qualified name**, it needs a **public constructor whose parameter types and order match the select list**, and the select list holds scalar expressions only — you cannot pull a whole collection into a constructor argument. Execute it with `em.createQuery(hql, UserDto.class).getResultList()`. Jakarta Persistence 3.2 / Hibernate 6.3+ also support *implicit instantiation*: pass the DTO type to `createQuery` and, when the select list matches a constructor, the `new` clause can be omitted; Java **records** work as DTOs. The semantics matter more than the syntax: the returned objects are **not managed**. They have no persistence-context entry and no dirty checking, so mutating one persists nothing — which is exactly why projections are cheap on read paths.

code

java · 7 lines
java
public record UserSummary(Long id, String name, String city) {}

List<UserSummary> rows = em.createQuery(
        "select new com.app.UserSummary(u.id, u.name, u.address.city) " +
        "from User u where u.active = true",
        UserSummary.class)
    .getResultList();

go deeper

for a junior

Recall the syntax, the fully-qualified-name and matching-constructor rules, and that the results are not managed.

for a middle

Add why it is faster — narrower select, no persistence-context entry, no dirty checking — and the collection limitation with the two-query workaround.

for a senior

Discuss where DTOs belong in a read path, testing string queries, one DTO per use case, and when an entity read is still the right call.

for a principal

Frame it as read-model design: query-shaped types owned by the endpoint, kept out of the write model, and the maintenance cost of many narrow projections.

## What a projection is `select u from User u` tells the provider to select every mapped column of the entity, build a `User` instance, and register it in the persistence context as a **managed** object. A *projection* selects individual expressions instead — `u.id`, `u.name`, `count(o)` — and hands you the values. A **DTO projection** wraps those values in a class you control, one shaped for the caller (an HTTP response, a report row) rather than for the database table. ## Constructor expressions The standard mechanism is the constructor expression, part of JPQL since JPA 1.0: ``` select new com.app.UserSummary(u.id, u.name, u.address.city) from User u where u.active = true ``` Mechanically, the provider emits SQL selecting exactly those three columns, then for each row calls `new UserSummary(...)` with the converted values. Requirements: - **Fully qualified class name.** JPQL has no import mechanism, so `new UserSummary(...)` fails unless the provider is configured to resolve short names (Hibernate 6 allows registering imports; do not rely on it in portable code). - **A matching constructor.** Parameter count, order, and assignable types must line up. A `Long` id against an `int` parameter or two swapped `String` parameters is a runtime failure, not a compile-time one — the query is a string. - **Scalar arguments only.** You can pass entity-valued paths in Hibernate, but you cannot pass a collection: `new Dto(u.id, u.orders)` is invalid. Nested one-to-many data needs either a second query keyed by the parent ids, or Hibernate 6's `multiset()` HQL function. - The DTO does not need to be an entity, does not need a no-arg constructor, and should not be annotated with `@Entity`. ## Newer conveniences Jakarta Persistence 3.2 and Hibernate 6.3+ added **implicit instantiation**: `em.createQuery("select u.id, u.name from User u", UserSummary.class)` will find a constructor matching the select list. Java **records** are ideal DTO carriers — the canonical constructor matches the component order, and immutability suits a read model. Hibernate also offers `jakarta.persistence.Tuple` and raw `Object[]` when you do not want a class per query, and Hibernate 6 replaced the old `ResultTransformer` with `TupleTransformer`/`ResultListTransformer` for custom assembly. ## Why it matters: the results are detached values The objects a constructor expression produces are ordinary Java objects. Hibernate does not put them in the persistence context, does not keep a loaded-state snapshot of them, and never dirty-checks them at flush. Consequences: - Changing a field on the DTO changes nothing in the database. Candidates who expect an update here misunderstand the model. - There is no lazy loading. Anything not in the select list is simply absent; you cannot navigate from a DTO to an unselected association, and you will never see a lazy-initialization failure from one. - To *write*, you must load the entity by id (or run a bulk `update` statement). A DTO is a read artifact. ## A worked contrast ```java // managed read: all columns, entity in the persistence context, snapshot retained List<User> users = em.createQuery("from User u", User.class).getResultList(); // DTO read: two columns, nothing retained List<UserSummary> rows = em.createQuery( "select new com.app.UserSummary(u.id, u.name) from User u", UserSummary.class) .getResultList(); ``` The second form usually emits a narrower `select`, transfers fewer bytes, allocates one small object per row instead of an entity plus its state snapshot, and adds nothing to later flush cost. ## Practical notes Keep one DTO per read use case rather than a shared "wide" DTO that every query half-fills — half-populated DTOs are as confusing as partially initialised entities. Because the query is a string, cover each constructor expression with a test that actually executes it; a renamed constructor parameter type is otherwise found in production. And name the DTO for the *view* it serves (`UserSummary`, `InvoiceRow`), not for the entity, so nobody mistakes it for a second mapping of the table.

  • If I change a field on one of the returned DTOs and commit the transaction, what happens in the database?
    Nothing. The DTO was never added to the persistence context, so there is no loaded-state snapshot and no dirty check at flush. To change data you must load the managed entity by its identifier and mutate that, or issue a bulk JPQL update statement.
  • How would you return a parent with its child collection as DTOs, given that a constructor expression cannot take a collection?
    Run two queries: one for the parent rows, then one that fetches the child rows for the collected parent ids, and stitch them in memory by parent id. That is two round trips and no duplicated parent rows. Hibernate 6 alternatively offers the `multiset()` HQL function, which builds nested lists inside a single statement.

saying these in an interview costs you the question

  • Believing DTO results are managed and that mutating them will be persisted
  • Writing the simple class name in the `new` clause instead of the fully qualified name
  • Calling it a projection while actually selecting whole entities and mapping them to DTOs in Java afterwards
  • Trying to pass a collection association as a constructor argument
  • Annotating the DTO as `@Entity` so it ends up mapped and managed

context