skip to content

What is the difference between a closed and an open interface projection, and how does each affect the generated SQL?

level: middleimportance: must knowfreq 68%

answer

  1. Closed = plain getters -> narrow SELECT
  2. Open = @Value SpEL -> full entity loaded
  3. 'target' = SpEL root = backing entity
  4. One @Value makes the whole projection open
  5. Default method over closed getters stays optimized

basics

~20 s

Closed projections have only plain getters that map to entity properties, so Spring narrows the SELECT to just those columns. Open projections use @Value with SpEL (or default methods computing values), so Spring can't optimize and fetches the whole entity.

solid answer

~50 s

A closed projection's getters all correspond directly to entity attributes, so Spring Data can determine exactly which properties are needed and narrow the generated SELECT to just those columns — the performance win. An open projection has at least one getter annotated with @Value holding a SpEL expression (e.g. combining fields or calling methods on the backing entity via the 'target' root). Because SpEL can reference anything, Spring can't infer the required columns, so it loads the full entity/aggregate and evaluates the expression in memory. Default methods on the interface behave similarly — they compute from other getters and don't restrict the query, but a default method alone (without any @Value) doesn't necessarily force full loading unless it references the backing target. Rule of thumb: use closed projections when you care about fetching fewer columns; reserve open projections for computed/derived values.

code

java · 18 lines
java
// Closed: SELECT firstname, lastname
interface NameOnly {
    String getFirstname();
    String getLastname();
}

// Open: loads full entity, evaluates SpEL in memory
interface FullNameView {
    @Value("#{target.firstname + ' ' + target.lastname}")
    String getFullName();
}

// Closed + default method: still narrowed, concatenation in Java
interface NameView {
    String getFirstname();
    String getLastname();
    default String getFullName() { return getFirstname() + " " + getLastname(); }
}

go deeper

for a junior

Know closed = only getters, open = uses @Value; closed is faster.

for a middle

Explain the column-narrowing mechanism and that one @Value flips the whole projection to open.

for a senior

Advise default-method-over-@Value to preserve narrowing; reason about per-row SpEL cost.

for a principal

Set team conventions on projection style vs query cost; know narrowing is a derived-query feature, not guaranteed with hand-written @Query.

## Closed projection Every getter maps 1:1 to an entity property. Example: ```java interface UserView { String getFirstname(); String getLastname(); } ``` Because Spring Data can resolve each getter to a concrete persistent attribute, it knows the *exact* set of columns required and can **narrow the SELECT** (with the default derived-query mechanism) to only `firstname` and `lastname`. This is the optimization that avoids fetching unmapped/unneeded columns (large text, blobs, lazy associations). ## Open projection At least one getter is annotated with `@Value` containing a **SpEL** (Spring Expression Language) expression: ```java interface UserView { @Value("#{target.firstname + ' ' + target.lastname}") String getFullName(); } ``` Here `target` is the SpEL root referring to the **backing entity** (the aggregate root the projection is built from). Because the expression can reference arbitrary properties or invoke methods, Spring Data **cannot determine** which columns are needed. Consequence: it **loads the entire entity** and evaluates the SpEL in memory. You lose the column-narrowing benefit. ## Default methods An interface `default` method computes a value from the other getters: ```java interface UserView { String getFirstname(); String getLastname(); default String getFullName() { return getFirstname() + " " + getLastname(); } } ``` Default methods run pure Java against the other (closed) getters. If the other getters are all closed, the query can still be narrowed — the default method itself doesn't reference the backing target. This is often a **better** alternative to `@Value` for simple concatenation because it keeps the projection closed and the query optimized. ## SQL impact summary | Projection kind | Column narrowing? | Mechanism | |---|---|---| | Closed (plain getters) | Yes — SELECT only needed columns | Property resolution | | Open (`@Value` SpEL) | No — full entity loaded | SpEL can reference anything | | Default method (over closed getters) | Yes | Computed in Java from narrowed columns | ## Gotchas - Adding a single `@Value` getter makes the whole projection open — you lose narrowing for *all* fields. - `@Value` SpEL is evaluated per row in Java; heavy expressions add CPU cost. - Column narrowing applies to derived query methods. Manually written `@Query` JPQL controls its own SELECT list; the projection must be compatible with the selected columns/aliases. - Prefer default methods over `@Value` for simple derivations to keep the projection closed.

  • What does 'target' refer to in an @Value SpEL projection expression?
    It's the SpEL evaluation root — the backing entity (aggregate root) the projection is built from. #{target.someProperty} accesses that entity's properties.
  • If you only need a concatenated name, is @Value or a default method better, and why?
    A default method — it keeps the projection closed so Spring still narrows the SELECT, whereas @Value makes it open and forces loading the whole entity.

saying these in an interview costs you the question

  • Claiming open projections still narrow the SELECT
  • Thinking @Value and default methods are equivalent for query optimization
  • Believing adding one @Value only affects that one getter's fetching

context