skip to content

How does the SpEL expression #{#entityName} work in a @Query, and why is it useful?

level: principalimportance: should knowfreq 30%

answer

  1. #{ } = SpEL block; inner #name = variable
  2. #entityName → entity name (or @Entity(name=...))
  3. generic base repo: SELECT e FROM #{#entityName} e
  4. text-rewriting SpEL = injection risk, not bound
  5. :#{#arg.prop} = safe SpEL-computed bind param

basics

~20 s

Spring Data lets you embed SpEL in a @Query using #{...}. The built-in #entityName variable resolves to the managed entity's name at runtime, so you can write generic base-repository queries like SELECT e FROM #{#entityName} e that work for any entity subtype.

solid answer

~50 s

Spring Data JPA supports **SpEL (Spring Expression Language)** inside `@Query` strings via the `#{ ... }` syntax, evaluated when the query is prepared. The most important built-in variable is `#entityName`, which resolves to the domain type's entity name — either the simple class name or the value of `@Entity(name = ...)`. This is powerful for **generic/base repositories**: you declare a shared `@Query` in a base interface using `SELECT e FROM #{#entityName} e ...`, and each concrete repository binds `#entityName` to its own entity, so one query definition serves many types. Note the two different `#`: outer `#{ }` delimits the SpEL block, inner `#entityName` references the variable. SpEL is evaluated at query-derivation time, not per-invocation with user data — never inject raw user input through SpEL, since it's concatenated into the query string, not bound as a parameter (SQL/JPQL-injection risk).

code

java · 15 lines
java
@NoRepositoryBean
public interface SoftDeleteRepository<T, ID> extends JpaRepository<T, ID> {

    // #{#entityName} resolves to each concrete repo's entity name
    @Query("SELECT e FROM #{#entityName} e WHERE e.deleted = false")
    List<T> findAllActive();
}

public interface UserRepository extends SoftDeleteRepository<User, Long> {
    // findAllActive() runs: SELECT e FROM User e WHERE e.deleted = false
}

public interface OrderRepository extends SoftDeleteRepository<Order, Long> {
    // findAllActive() runs: SELECT e FROM Order e WHERE e.deleted = false
}

go deeper

for a junior

Likely unaware; fine to not know this niche feature.

for a middle

May recognize SpEL in @Query but not the generic-repository use of #entityName.

for a senior

Can explain #entityName for base repositories and the #{ }/#name syntax.

for a principal

Judges when generic SpEL queries earn their complexity and flags the injection risk of text-rewriting SpEL versus bound :#{...} parameters.

## SpEL inside @Query Spring Data JPA can evaluate **SpEL (Spring Expression Language)** fragments embedded in a `@Query` value. The delimiter is `#{ ... }` — anything inside is evaluated as SpEL when Spring builds the query. This is distinct from JPQL/SQL parameter binding (`:name`, `?1`), which happens later at execution. ## The #entityName variable Spring Data exposes built-in SpEL variables via `JpaEntityMetadata`. The key one is **`#entityName`**. It resolves to the **entity name** of the repository's domain type: - Normally the simple class name (e.g. `User`). - If the entity overrides it with `@Entity(name = "AppUser")`, `#entityName` resolves to `AppUser`. So `#{#entityName}` in the query text is replaced by the correct entity name at runtime. ### The double-# is not a typo - `#{ }` — the outer braces open a **SpEL expression block**. - `#entityName` — inside SpEL, `#name` references a **variable** named `entityName`. So `#{#entityName}` = "evaluate the SpEL expression whose value is the variable `entityName`." ## Why it's useful: generic base repositories The canonical use case is a **shared base repository** with a query that must reference *whatever* entity the concrete repository manages. You can't hardcode the entity name in a generic interface, so you parameterize it with `#{#entityName}`. ```java @NoRepositoryBean public interface SoftDeleteRepository<T, ID> extends JpaRepository<T, ID> { @Query("SELECT e FROM #{#entityName} e WHERE e.deleted = false") List<T> findAllActive(); } public interface UserRepository extends SoftDeleteRepository<User, Long> {} public interface OrderRepository extends SoftDeleteRepository<Order, Long> {} ``` For `UserRepository`, `#{#entityName}` resolves to `User`; for `OrderRepository`, to `Order`. One query definition, reused across every entity that has a `deleted` flag — DRY at the repository layer. ## Other SpEL capabilities in @Query Beyond `#entityName`, Spring Data supports SpEL that references method arguments via `:#{...}`, e.g. accessing a property of a parameter: ```java @Query("SELECT u FROM User u WHERE u.city = :#{#address.city}") List<User> findByCity(@Param("address") Address address); ``` Here `:#{#address.city}` is a **SpEL-computed bind parameter**: the `:` makes it a bound parameter (safe), and the `#{ }` computes its value from the `address` argument. This is different from `#{#entityName}` (which rewrites the query text itself). ## Security caveat (principal-level judgment) `#{ }` expressions that rewrite query **text** are concatenated into the query, not bound as parameters. Therefore: - **Never** feed untrusted/user-controlled data into a text-rewriting SpEL fragment — it's an injection vector (JPQL/SQL injection). - `#entityName` is safe because it comes from framework metadata, not user input. - When you need a *value* from an argument, prefer the parameter-binding form `:#{#arg.prop}` (bound, escaped) over string interpolation. - SpEL is powerful (method calls, bean references via `@beanName`); keep expressions minimal and audited. Overuse hurts readability and can hide performance or security surprises. ## When to use Use `#{#entityName}` specifically for **generic/base repository abstractions** where a single query must adapt to many entity types. For everyday single-entity repositories it adds nothing — just name the entity directly. Treat broader SpEL-in-query as a sharp tool: valuable for computed bind parameters, dangerous if used to interpolate untrusted text.

  • What does #entityName resolve to if the entity is declared @Entity(name = "AppUser")?
    It resolves to "AppUser" — the explicit entity name overrides the default simple class name. #entityName mirrors the JPA entity name used in JPQL.
  • Why is #{#entityName} safe but interpolating a user-supplied value via #{...} into the query text dangerous?
    #entityName comes from trusted framework metadata. Text-rewriting SpEL is concatenated into the query string rather than bound, so untrusted input becomes an injection vector. For argument values use the bound form :#{#arg.prop} instead.
  • What's the difference between #{#entityName} and :#{#address.city}?
    #{#entityName} rewrites the query text (the entity name). :#{#address.city} is a bound bind-parameter whose value is computed by SpEL from an argument — safe and escaped. The leading colon marks it as a parameter.

saying these in an interview costs you the question

  • Thinking the double ## is a typo rather than SpEL-block + variable
  • Believing #entityName is the table name rather than the JPA entity name
  • Interpolating user input via #{...} text rewriting (injection)
  • Assuming SpEL is evaluated per-request with request data rather than at query preparation

context