Spring Data JPA Advanced Queries: Specifications, Projections, and Native SQL
Published 2026-05-17 by Shubham Bhati — Backend Engineer (Java 17, Spring Boot, Microservices).
We've all been there - stuck with a complex query in our Spring Boot application, trying to figure out how to use Spring Data JPA to fetch the required data. I recall a recent production issue where we had to optimize a query that was taking around 800ms to execute, and after implementing Spring Data JPA advanced queries, we reduced the p99 latency to 120ms. In this article, we'll explore how to use Spring Data JPA advanced queries, including specifications, projections, and native SQL, to solve real-world problems.
- Introduction to Specifications
- Using Projections
- Native SQL Queries
- Combining Specifications and Projections
- Optimizing Queries
- Common Mistakes
- FAQ
- Conclusion
Introduction to Specifications
Specifications in Spring Data JPA allow us to define reusable query logic. We can use the Specification interface to define a specification, and then use it to filter data. For example, let's say we have an User entity and we want to define a specification to filter users by name:
public class UserSpecifications {
public static Specification<User> nameContains(String name) {
return (root, query, criteriaBuilder) -> criteriaBuilder.like(root.get("name"), "%" + name + "%");
}
}
We can then use this specification to filter users:
@Repository
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
}
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public List<User> findUsersByName(String name) {
return userRepository.findAll(UserSpecifications.nameContains(name));
}
}
This way, we can reuse the specification logic across our application.
Using Projections
Projections in Spring Data JPA allow us to select only the required fields from an entity. We can use the @Value annotation to define a projection:
public interface UserProjection {
@Value("#{target.id}")
Long getId();
@Value("#{target.name}")
String getName();
}
We can then use this projection to select only the required fields:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u")
List<UserProjection> findAllProjections();
}
This way, we can reduce the amount of data transferred over the network.
Native SQL Queries
Native SQL queries in Spring Data JPA allow us to execute native SQL queries. We can use the @Query annotation to define a native SQL query:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT * FROM users WHERE name = ?1", nativeQuery = true)
List<User> findUsersByNameNative(String name);
}
We can also use named parameters:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT * FROM users WHERE name = :name", nativeQuery = true)
List<User> findUsersByNameNative(@Param("name") String name);
}
This way, we can execute complex native SQL queries.
Combining Specifications and Projections
We can combine specifications and projections to filter and select data. For example:
@Repository
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
@Query("SELECT u FROM User u")
List<UserProjection> findAllProjections(Specification<User> spec);
}
We can then use a specification to filter data and a projection to select only the required fields:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public List<UserProjection> findUsersByName(String name) {
return userRepository.findAllProjections(UserSpecifications.nameContains(name));
}
}
This way, we can reuse the specification logic and reduce the amount of data transferred over the network.
Optimizing Queries
We can optimize queries by using indexes, caching, and batch processing. For example, we can use the @Index annotation to define an index:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Index
private String name;
}
We can also use caching to reduce the number of database queries:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Cacheable(value = "users", key = "#name")
public List<User> findUsersByName(String name) {
return userRepository.findAll(UserSpecifications.nameContains(name));
}
}
This way, we can reduce the latency and improve the performance of our application.
Common Mistakes
Here are some common mistakes to avoid when using Spring Data JPA advanced queries:
* Not using the @Query annotation to define a query
* Not using the nativeQuery attribute to specify a native SQL query
* Not using the @Value annotation to define a projection
* Not using the Specification interface to define a specification
* Not using caching to reduce the number of database queries
FAQ
What is the difference between a specification and a projection?
A specification is used to filter data, while a projection is used to select only the required fields from an entity. We can use the Specification interface to define a specification, and the @Value annotation to define a projection.
How can I optimize my queries?
We can optimize queries by using indexes, caching, and batch processing. We can use the @Index annotation to define an index, and the @Cacheable annotation to enable caching.
Can I use native SQL queries with Spring Data JPA?
Yes, we can use native SQL queries with Spring Data JPA. We can use the nativeQuery attribute to specify a native SQL query.
What is the benefit of using Spring Data JPA advanced queries?
The benefit of using Spring Data JPA advanced queries is that we can reuse the query logic, reduce the amount of data transferred over the network, and improve the performance of our application. For more information, we can refer to the Spring Data JPA documentation.
Conclusion
In this article, we explored how to use Spring Data JPA advanced queries, including specifications, projections, and native SQL, to solve real-world problems. We can use these features to reuse the query logic, reduce the amount of data transferred over the network, and improve the performance of our application. To learn more about Spring Data JPA, we can refer to the official Spring documentation and Baeldung tutorials.
Further Reading
Written by Shubham Bhati — Backend Engineer at AlignBits LLC, specializing in Java 17, Spring Boot, microservices, and AI integration. Connect on LinkedIn, GitHub, or read more at shubh2-0.github.io.