Definition
JPA provides two annotations for declaring named, reusable queries directly on entity classes.
- @NamedQuery – declares a JPQL query with a unique name on an entity class; the JPA provider parses and validates it at application startup, caching the parsed query for reuse.
- @NamedNativeQuery – declares a native SQL query with a unique name; it is registered at startup but the SQL syntax is not validated because the provider does not parse vendor-specific SQL.
- Startup validation – the process where the JPA provider checks all JPQL named queries when the EntityManagerFactory is built, ensuring field names, entity names, and parameter usage are correct before any user request arrives.
- @SqlResultSetMapping – maps the columns returned by a native query to entity fields or DTO constructor parameters when the column names do not match automatically.
At National Bank of India, a named query is like a pre-approved standard form kept in the bank's forms cabinet. Compliance checked every form once when it was added, so every teller who pulls it knows it is valid and just fills in the blanks for the parameters. An inline query is like a teller writing a request freehand at their desk each time -- convenient and local, but the standard named form is the official, reusable, pre-validated one. Validating at startup is compliance checking every form in the cabinet the moment the branch opens, so a broken form is caught before any customer is served.
Understanding the Concepts
What is @NamedQuery?
@NamedQuery declares a reusable JPQL query with a unique name directly on an entity class. The convention is to prefix the name with the entity name, such as Account.findActiveByType. When the application starts, the JPA provider parses the JPQL, validates that the entity names, field names, and parameter bindings are correct, and caches the parsed query. If anything is wrong, the application fails to start with a clear error.
package com.bank.model;
import jakarta.persistence.*;
import java.math.BigDecimal;
@Entity
@Table(name = "accounts")
@NamedQuery(
name = "Account.findActiveByType",
query = "SELECT a FROM Account a WHERE a.status = 'ACTIVE' AND a.accountType = :type"
)
@NamedQuery(
name = "Account.findHighBalance",
query = "SELECT a FROM Account a WHERE a.balance > :minBalance ORDER BY a.balance DESC"
)
public class Account {
@Id
@Column(name = "account_id")
private String accountId; // SAV2001
@Column(name = "holder_name")
private String holderName; // Priya
private BigDecimal balance;
@Column(name = "account_type")
private String accountType;
private String status;
// getters and setters
}
You invoke a named query by its name through the EntityManager:
package com.bank.service;
import com.bank.model.Account;
import jakarta.persistence.EntityManager;
import java.math.BigDecimal;
import java.util.List;
public class AccountService {
private final EntityManager em;
public AccountService(EntityManager em) { this.em = em; }
public List<Account> findActiveSavings() {
return em.createNamedQuery("Account.findActiveByType", Account.class)
.setParameter("type", "SAVINGS")
.getResultList();
}
public List<Account> findHighBalanceAccounts() {
return em.createNamedQuery("Account.findHighBalance", Account.class)
.setParameter("minBalance", new BigDecimal("100000.00"))
.getResultList();
}
}
Expected output:
[Account{SAV2001, Priya, 500000.00, SAVINGS, ACTIVE},
Account{SAV2003, Rajesh, 150000.00, SAVINGS, ACTIVE}]
What is @NamedNativeQuery?
@NamedNativeQuery declares a native SQL query with a name. Native SQL is database-specific and is not validated for correctness at startup because the JPA provider does not parse vendor SQL. It is useful when you need database-specific features like window functions, CTEs, or vendor-specific hints.
package com.bank.model;
import jakarta.persistence.*;
@Entity
@Table(name = "accounts")
@NamedNativeQuery(
name = "Account.totalActiveBalance",
query = "SELECT SUM(balance) FROM accounts WHERE status = 'ACTIVE'"
)
@NamedNativeQuery(
name = "Account.findByBranch",
query = "SELECT * FROM accounts WHERE branch_code = :branchCode",
resultClass = Account.class
)
public class Account {
// fields as above
}
package com.bank.service;
import com.bank.model.Account;
import jakarta.persistence.EntityManager;
import java.math.BigDecimal;
import java.util.List;
public class BranchService {
private final EntityManager em;
public BranchService(EntityManager em) { this.em = em; }
public BigDecimal getTotalActiveBalance() {
return (BigDecimal) em.createNamedQuery("Account.totalActiveBalance")
.getSingleResult();
}
public List<Account> findByBranch(String branchCode) {
return em.createNamedQuery("Account.findByBranch", Account.class)
.setParameter("branchCode", branchCode)
.getResultList();
}
}
Expected output:
Total active balance: 1250000.00
Branch MUM001 accounts: [Account{SAV2001, Priya}, Account{CUR3001, Rajesh}]
Why are named queries validated at startup?
Startup validation is the main safety benefit of @NamedQuery. When the EntityManagerFactory is built, the JPA provider parses every registered JPQL named query. If the JPQL references a field that does not exist, misspells an entity name, or has a syntax error, the application fails to start with a clear error message.
At National Bank of India, Rajesh once shipped an inline JPQL query with a misspelled field name. It compiled fine because JPQL is just a string. The bug only exploded weeks later when a customer opened that specific screen. After moving the query to a @NamedQuery, the application would have refused to boot with the typo, catching it in the build pipeline.
// This typo compiles fine but fails at RUNTIME
String jpql = "SELECT a FROM Account a WHERE a.balace > :min"; // typo: balace
em.createQuery(jpql, Account.class); // fails only when this line runs
// As a @NamedQuery, the same typo fails at STARTUP
@NamedQuery(
name = "Account.findHighBalance",
query = "SELECT a FROM Account a WHERE a.balace > :min" // typo caught at boot!
)
// Application refuses to start:
// org.hibernate.QueryException: could not resolve property: balace of: Account
This fail-fast behavior means broken JPQL queries never reach production if you have a CI pipeline that starts the application context.
How is @NamedQuery different from inline @Query?
This comparison table captures the key differences between @NamedQuery on the entity and @Query on a Spring Data repository method.
| Feature | @NamedQuery | @Query (Spring Data) |
|---|---|---|
| Declared on | Entity class | Repository method |
| JPQL validation at startup | Yes (JPA standard) | Yes (Spring Data validates) |
| Native SQL validation | No | No |
| Reusable by name | Yes -- em.createNamedQuery("...") | No -- local to the method |
| Reusable across repositories | Yes | No |
| Usable from EntityManager | Yes | Not directly |
| Location | Centralized on entity | Local to repository method |
| Override in orm.xml | Yes | No |
| Discoverability | Must look at entity class | Visible right at the method |
| JPA standard | Yes | Spring-specific |
// @NamedQuery -- declared on entity, reusable
@Entity
@NamedQuery(
name = "Account.findByStatus",
query = "SELECT a FROM Account a WHERE a.status = :status"
)
public class Account { ... }
// Used from EntityManager
List<Account> result = em.createNamedQuery("Account.findByStatus", Account.class)
.setParameter("status", "ACTIVE")
.getResultList();
// Used from Spring Data (auto-matched by name convention)
public interface AccountRepository extends JpaRepository<Account, String> {
List<Account> findByStatus(String status); // matches Account.findByStatus
}
// @Query -- declared on repository method, local
public interface AccountRepository extends JpaRepository<Account, String> {
@Query("SELECT a FROM Account a WHERE a.status = :status")
List<Account> findActiveAccounts(@Param("status") String status);
}
// Cannot reuse this query from EntityManager or another repository
How is @NamedQuery different from @NamedNativeQuery?
The key difference is the query language and validation behavior.
| Feature | @NamedQuery | @NamedNativeQuery |
|---|---|---|
| Query language | JPQL (entity/field names) | Native SQL (table/column names) |
| Validated at startup | Yes -- syntax, entity, field names | No -- SQL is not parsed by JPA |
| Database portable | Yes -- JPQL is translated per dialect | No -- SQL is vendor-specific |
| Result mapping | Automatic from entity | May need @SqlResultSetMapping |
| Use case | Standard queries | DB-specific features (CTEs, window functions) |
// @NamedQuery -- JPQL, portable, validated
@NamedQuery(
name = "Account.findByHolder",
query = "SELECT a FROM Account a WHERE a.holderName = :name"
)
// Works on PostgreSQL, MySQL, Oracle -- JPA translates
// @NamedNativeQuery -- SQL, not portable, not validated
@NamedNativeQuery(
name = "Account.findByHolderNative",
query = "SELECT * FROM accounts WHERE holder_name = :name",
resultClass = Account.class
)
// Tied to the specific database schema
What are the rules and constraints?
- Named query names must be globally unique across all entities. The convention
EntityName.queryNameprevents clashes. - Multiple @NamedQuery annotations on one entity require Java 8+ repeatable annotations or
@NamedQuerieswrapper. - Parameters use
:name(named) or?1(positional) syntax. - A @NamedQuery cannot be a DML statement (INSERT, UPDATE, DELETE) in standard JPA. Use
@NamedQuerywithem.createNamedQuery(...).executeUpdate()only for JPQL bulk updates. - @NamedNativeQuery for entities should specify
resultClassorresultSetMapping. - Named queries are immutable once registered. You cannot change the query text at runtime.
package com.bank.model;
import jakarta.persistence.*;
@Entity
@Table(name = "accounts")
// Multiple named queries (Java 8+ repeatable)
@NamedQuery(name = "Account.findAll",
query = "SELECT a FROM Account a")
@NamedQuery(name = "Account.findByStatus",
query = "SELECT a FROM Account a WHERE a.status = :status")
@NamedQuery(name = "Account.countByType",
query = "SELECT COUNT(a) FROM Account a WHERE a.accountType = :type")
public class Account {
@Id private String accountId;
private String holderName;
private BigDecimal balance;
private String accountType;
private String status;
}
What are common mistakes?
Mistake 1: Duplicate named query names across entities.
// WRONG -- same name on two different entities
@Entity
@NamedQuery(name = "findByStatus",
query = "SELECT a FROM Account a WHERE a.status = :s")
public class Account { ... }
@Entity
@NamedQuery(name = "findByStatus",
query = "SELECT l FROM Loan l WHERE l.status = :s")
public class Loan { ... }
// Hibernate throws: DuplicateMappingException
// CORRECT -- prefix with entity name
@NamedQuery(name = "Account.findByStatus", ...)
@NamedQuery(name = "Loan.findByStatus", ...)
Mistake 2: Assuming native SQL is validated at startup.
// WRONG assumption -- this typo is NOT caught at startup
@NamedNativeQuery(
name = "Account.brokenNative",
query = "SELCT * FORM accounts" // typos: SELCT, FORM
)
// Application starts fine! Fails only at runtime when query is executed.
// CORRECT -- always test native queries in integration tests
@Test
void testBrokenNative() {
// This test catches the typo during the build
assertThrows(PersistenceException.class, () ->
em.createNamedQuery("Account.brokenNative").getResultList());
}
Mistake 3: Forgetting resultClass on native entity queries.
// WRONG -- no resultClass, returns Object[] not Account
@NamedNativeQuery(
name = "Account.findAll",
query = "SELECT * FROM accounts"
)
// em.createNamedQuery("Account.findAll").getResultList()
// returns List<Object[]>, not List<Account>!
// CORRECT -- specify resultClass
@NamedNativeQuery(
name = "Account.findAll",
query = "SELECT * FROM accounts",
resultClass = Account.class
)
// Now returns List<Account>
What about Spring Data integration?
Spring Data JPA integrates with named queries through a naming convention. If an entity has a @NamedQuery named Account.findByStatus, and the repository has a method findByStatus, Spring Data uses the named query instead of generating a derived query.
package com.bank.model;
import jakarta.persistence.*;
import java.math.BigDecimal;
@Entity
@Table(name = "accounts")
@NamedQuery(
name = "Account.findByStatus",
query = "SELECT a FROM Account a WHERE a.status = :status ORDER BY a.balance DESC"
)
public class Account {
@Id private String accountId;
private String holderName;
private BigDecimal balance;
private String status;
}
package com.bank.repository;
import com.bank.model.Account;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface AccountRepository extends JpaRepository<Account, String> {
// Spring Data finds Account.findByStatus named query and uses it
List<Account> findByStatus(String status);
}
package com.bank.service;
import com.bank.model.Account;
import com.bank.repository.AccountRepository;
import java.util.List;
public class AccountService {
private final AccountRepository repo;
public AccountService(AccountRepository repo) { this.repo = repo; }
public List<Account> getActiveAccounts() {
return repo.findByStatus("ACTIVE");
}
}
Expected output:
[Account{SAV2001, Priya, 500000.00, ACTIVE},
Account{SAV2003, Rajesh, 150000.00, ACTIVE}]
When should you use @NamedQuery?
Use @NamedQuery when:
- The same query is used from multiple repositories or services.
- You want centralized query declarations on the entity.
- You need fail-fast startup validation for critical queries.
- You want the option to override queries in orm.xml for production tuning.
Use inline @Query when:
- The query is specific to one repository method.
- You prefer keeping the query text next to the method for discoverability.
- You do not need cross-repository reuse.
At National Bank of India, Pankaj declares core business queries like "find active accounts by type" and "calculate total balance by branch" as named queries on the entity, because they are reused across the AccountService, the ReportService, and the BatchProcessor. One-off queries specific to a single endpoint use inline @Query.
// Core query reused everywhere -- @NamedQuery
@NamedQuery(name = "Account.findActiveByType", ...)
// One-off query for a specific endpoint -- inline @Query
@Query("SELECT a FROM Account a WHERE a.holderName LIKE :prefix%")
List<Account> searchByNamePrefix(@Param("prefix") String prefix);
What should a senior engineer know?
- orm.xml override. Named queries can be overridden in
META-INF/orm.xmlwithout recompiling. This is valuable for tuning queries in production or switching to optimized SQL for a specific database. - Query hints. @NamedQuery supports
hintsfor caching, timeouts, and fetch size. For example,@QueryHint(name = "org.hibernate.cacheable", value = "true")enables second-level cache for the query result. - JPQL bulk updates. You can declare a JPQL UPDATE or DELETE as a named query and execute it with
executeUpdate(). This bypasses the persistence context, so callem.clear()afterward. - Named query vs derived query precedence. In Spring Data, a named query takes precedence over a derived query if both match the method name.
- Performance. Named queries are parsed and compiled once at startup, so subsequent executions skip the parsing step. This is a minor but measurable optimization for queries executed thousands of times per second.
package com.bank.model;
import jakarta.persistence.*;
@Entity
@Table(name = "accounts")
@NamedQuery(
name = "Account.deactivateZeroBalance",
query = "UPDATE Account a SET a.status = 'INACTIVE' WHERE a.balance = 0",
hints = @QueryHint(name = "org.hibernate.timeout", value = "10")
)
public class Account {
@Id private String accountId;
private BigDecimal balance;
private String status;
}
package com.bank.service;
import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;
public class AccountMaintenanceService {
private final EntityManager em;
public AccountMaintenanceService(EntityManager em) { this.em = em; }
@Transactional
public int deactivateZeroBalanceAccounts() {
int updated = em.createNamedQuery("Account.deactivateZeroBalance")
.executeUpdate();
em.clear(); // clear persistence context after bulk update
System.out.println("Deactivated: " + updated + " accounts");
return updated;
}
}
Expected output:
Deactivated: 3 accounts
Visual Overview
Application Startup:
EntityManagerFactory builds
│
▼
┌─────────────────────────────────────┐
│ Parse all @NamedQuery (JPQL) │
│ ├── Validate entity names │
│ ├── Validate field names │
│ ├── Validate parameter bindings │
│ └── Cache parsed query │
│ │
│ Register all @NamedNativeQuery │
│ └── SQL is NOT validated │
└─────────────────────────────────────┘
│
├── All JPQL valid? → Application starts
└── Any JPQL broken? → Application FAILS TO START
Named Query Lifecycle:
@NamedQuery on Entity
│
│ startup: parse + validate + cache
▼
┌──────────────────────┐
│ Cached Parsed Query │
└──────────┬───────────┘
│
┌────────┴────────┐
▼ ▼
EntityManager Spring Data
.createNamedQuery Repository method
("Account.find") findByStatus()
│ │
▼ ▼
Set parameters Auto-matched by
Execute EntityName.methodName
@NamedQuery vs @Query vs Derived: ┌─────────────────────┐ │ @NamedQuery │ On entity → reusable, validated ├─────────────────────┤ │ @Query │ On method → local, validated (JPQL) ├─────────────────────┤ │ Derived query │ findByStatus → auto-generated └─────────────────────┘ Priority: @NamedQuery > @Query > Derived
Key Takeaway
A @NamedQuery defines a reusable JPQL query with a name, declared on the entity class, and the JPA provider parses and validates it once at application startup. If the JPQL has a typo, references a field that does not exist, or misuses a parameter, the application fails to start with a clear error instead of shipping a broken query that only explodes at runtime when a user hits that path. A @NamedNativeQuery is the same idea for raw SQL, but the SQL is not validated at startup because the provider does not parse vendor-specific SQL.
The difference from an inline @Query on a Spring Data repository method is mostly about location and reuse. A named query is declared centrally on the entity and can be invoked by name from multiple repositories and from the EntityManager directly. An inline @Query keeps the text right next to the method, which is more discoverable, but it is local and not reusable elsewhere by name. Spring Data also validates inline JPQL at startup, so the fail-fast benefit applies to both, but named queries are the JPA standard and offer orm.xml override for production tuning.
At National Bank of India, Pankaj declares core business queries as named queries on the entity because they are reused across multiple services. He keeps one-off queries as inline @Query for discoverability. The named queries can be overridden in orm.xml without recompiling, which proved valuable when the DBA optimized a slow query in production without a code release.
Remember one sentence: @NamedQuery is a reusable, startup-validated JPQL query declared on the entity; @NamedNativeQuery is the same for SQL but without startup validation.
---