When Should You Use nativeQuery=true in Spring Data JPA @Query Annotations
Problem
When I wrote a SQL query in a @Query annotation, I got this error:
Caused by: java.lang.IllegalArgumentException: Validation failed for query for method public abstract java.util.List com.example.repository.UserRepository.findActiveUsers()
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token: * near line 1, column 8 [SELECT * FROM users WHERE status = 1]I just wanted to run a simple SQL query. Why was Spring trying to parse it as JPQL?
Environment
- Spring Boot 3.2.x
- Spring Data JPA 3.2.x
- PostgreSQL database
- Hibernate as JPA provider
What happened?
I wrote what I thought was a straightforward query:
@Repositorypublic interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT * FROM users WHERE status = 1") List<User> findActiveUsers();}The error occurred because Spring Data JPA parses all @Query annotations as JPQL by default. My query used SQL syntax (SELECT * FROM users), not JPQL syntax (SELECT u FROM User u).
How to solve it?
Add nativeQuery = true to tell Spring to treat the query as raw SQL:
@Repositorypublic interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT * FROM users WHERE status = 1", nativeQuery = true) List<User> findActiveUsers();}Now the query bypasses JPQL validation and executes directly against the database.
When to use JPQL vs native SQL
Here’s how I decide:
Use JPQL (default) for:
// Standard entity queries - database agnostic@Query("SELECT u FROM User u WHERE u.status = :status")List<User> findByStatus(@Param("status") Integer status);
// Joins between entities@Query("SELECT u FROM User u JOIN u.orders o WHERE o.total > 1000")List<User> findUsersWithLargeOrders();
// Aggregations@Query("SELECT COUNT(u) FROM User u WHERE u.active = true")Long countActiveUsers();Use native SQL (nativeQuery = true) for:
// Database-specific functions@Query(value = "SELECT * FROM users WHERE created_at > NOW() - INTERVAL '7 days'", nativeQuery = true)List<User> findRecentUsers();
// Complex SQL that JPQL doesn't support@Query(value = "SELECT u.* FROM users u JOIN orders o ON u.id = o.user_id WHERE o.total > 1000", nativeQuery = true)List<User> findUsersWithLargeOrders();
// Raw SQL for performance optimization@Query(value = "SELECT * FROM users WHERE id IN (SELECT user_id FROM orders GROUP BY user_id HAVING COUNT(*) > 10)", nativeQuery = true)List<User> findUsersWithManyOrders();
// Working with existing SQL queries@Query(value = "SELECT * FROM users WHERE status = 1", nativeQuery = true)List<User> findActiveUsers();Key differences
| Aspect | JPQL | Native SQL |
|---|---|---|
| Syntax | Entity/field names | Table/column names |
| Portability | Database-agnostic | Database-specific |
| Validation | At startup | At runtime |
| Type safety | Compile-time hints | None |
| Features | Limited to JPA spec | Full SQL power |
Test verification
@Testvoid givenActiveUser_whenFindActiveUsers_thenReturnsUser() { User user = new User(); user.setFirstName("Jane"); user.setStatus(1); userRepository.save(user);
List<User> result = userRepository.findActiveUsers();
assertEquals(1, result.size()); assertEquals(1, result.get(0).getStatus());}Summary
In this post, I explained when to use nativeQuery = true in Spring Data JPA. The key point is that JPQL (default) queries Java entities and is database-agnostic, while native SQL queries database tables directly.
Use native SQL when you need database-specific features, complex SQL, or are working with existing queries. Use JPQL for standard entity operations to maintain database portability.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments