Spring Data JPA Query Methods: A Complete Guide to Naming Conventions, Sorting, Paging, and @Query
When retrieving data from a database with Spring Data JPA, query methods are what you’ll use most often. SQL is generated automatically just by following the method naming convention, so there’s no need to write an implementation class. However, this is also an area that tends to raise questions for beginners, such as “what method name should I write?” and “how do I implement complex search conditions?”
This article walks through Spring Data JPA query methods step by step, from the basics to combining multiple conditions, sorting and paging, and custom queries using the @Query annotation. By the time you finish reading, you should be able to implement the query methods you need on your own and choose the appropriate approach for each situation.
What Are Spring Data JPA Query Methods?
List of Built-in Methods Provided by JpaRepository
Before writing custom query methods, let’s go over the built-in methods available simply by extending JpaRepository<T, ID> (verified with Spring Boot 3.x / Spring Data JPA 3.x).
| Method | Return Value | Purpose |
|---|---|---|
save(entity) | S | INSERT or UPDATE (automatically determined based on whether a primary key is present, etc.) |
saveAll(entities) | List<S> | Saves multiple entities. Note that by default, SQL is issued one row at a time |
findById(id) | Optional<T> | Retrieves a single record by primary key |
existsById(id) | boolean | Checks existence by primary key |
findAll() | List<T> | Retrieves all records |
findAll(Pageable) | Page<T> | Retrieves a list with paging and sorting |
findAllById(ids) | List<T> | Retrieves multiple records at once by a list of primary keys (IN clause) |
count() | long | Gets the total count |
deleteById(id) / delete(entity) | void | Deletes a single record |
deleteAllInBatch() | void | Deletes all records in a single query (bypasses the first-level cache) |
getReferenceById(id) | T | Returns a lazy proxy. Useful for scenarios like linking associations where no SELECT is needed |
Since findById returns Optional<T>, combining it with orElseThrow() is a standard pattern in real-world code.
User user = userRepository.findById(userId)
.orElseThrow(() -> new EntityNotFoundException("User not found: " + userId));
Note that getById() / getOne(), used in older versions, are now deprecated and have been unified into getReferenceById(). While findById issues a SELECT immediately, getReferenceById doesn’t issue any SQL and instead returns a lazily-loaded proxy. Use findById when you want to fetch data while also performing an existence check, and use getReferenceById when you only need to link a related entity (without reading its own columns).
Also, when saving large amounts of data with saveAll(), INSERTs are issued one at a time by default. For configuring batching, see How to Speed Up Bulk INSERTs with Spring Data JPA.
Query methods, which we’ll look at from here, are what let you handle search conditions that these built-in methods alone can’t cover.
The mechanism behind query methods is simple. When you define a method like findByName in an interface that extends JpaRepository, Spring Data JPA generates a proxy at runtime, parses the method name, and automatically generates the query.
public interface UserRepository extends JpaRepository<User, Long> {
// Basic CRUD operations are already available at this point
User findByEmail(String email);
List<User> findByAgeGreaterThan(int age);
}
Basic Query Method Naming Conventions
Query Method Naming Convention Quick Reference
Let’s first get an overview. The table below maps the keywords Spring Data JPA parses to the generated SQL/JPQL and example method names.
| Prefix | Purpose | Example Return Value | Example Method Name |
|---|---|---|---|
findBy | Retrieve data | Optional<T> / List<T> | findByEmail(String) |
existsBy | Check existence | boolean | existsByEmail(String) |
countBy | Get count | long | countByActive(boolean) |
deleteBy | Delete (requires @Transactional) | void / long | deleteByStatus(String) |
getBy / readBy / queryBy | Synonymous with findBy (findBy is recommended) | Same as above | getByEmail(String) |
getBy, readBy, and queryBy are aliases for findBy, and their behavior is completely identical. Mixing these naming styles reduces searchability, so it’s recommended that your team standardize on findBy, which is the most widely used.
Condition keywords can be combined as follows.
| Keyword | Generated Condition | Example Method Name |
|---|---|---|
And | cond1 AND cond2 | findByNameAndEmail |
Or | cond1 OR cond2 | findByNameOrEmail |
GreaterThan / GreaterThanEqual | > / >= | findByAgeGreaterThan |
LessThan / LessThanEqual | < / <= | findByAgeLessThanEqual |
Between | BETWEEN ? AND ? | findByAgeBetween |
Like | LIKE ? (wildcards must be added manually) | findByNameLike |
Containing | LIKE %?% | findByNameContaining |
StartingWith | LIKE ?% | findByNameStartingWith |
EndingWith | LIKE %? | findByNameEndingWith |
In | IN (?, ?, ...) | findByStatusIn |
NotIn | NOT IN (...) | findByStatusNotIn |
IsNull / IsNotNull | IS NULL / IS NOT NULL | findByDeletedAtIsNull |
True / False | = true / = false | findByActiveTrue |
IgnoreCase | Case-insensitive | findByEmailIgnoreCase |
OrderBy<Field>Asc/Desc | ORDER BY | findByActiveOrderByNameAsc |
Keeping this table handy will keep you from getting stuck when constructing method names. In the following sections, we’ll dig into each category with concrete examples.
findBy / existsBy / countBy
Use each one according to its purpose.
public interface UserRepository extends JpaRepository<User, Long> {
// Retrieve data
Optional<User> findByEmail(String email);
List<User> findByName(String name);
// Existence check (useful for things like duplicate checks)
boolean existsByEmail(String email);
// Get count
long countByActive(boolean active);
}
When retrieving a single result with findBy, using Optional<T> lets you handle nulls safely. In practice, using Optional is recommended.
deleteBy
Deletes records matching the given conditions. Note that delete operations require @Transactional.
public interface UserRepository extends JpaRepository<User, Long> {
void deleteByStatus(String status);
long deleteByActiveIsFalse(); // Can also return the number of deleted records
}
Combining Multiple Conditions (And/Or)
In real-world development, combining multiple search conditions is common. You can combine conditions using And and Or.
public interface UserRepository extends JpaRepository<User, Long> {
// And - all conditions must be satisfied
User findByNameAndEmail(String name, String email);
List<User> findByActiveAndAgeGreaterThanEqual(boolean active, int age);
// Or - matches if any one condition is satisfied
List<User> findByNameOrEmail(String name, String email);
}
You can also mix And and Or, but if the method name becomes long and complex, consider using @Query, which is covered later.
Search Conditions Using Comparison Operators
Spring Data JPA supports a variety of comparison operators. Let’s go over some patterns commonly used in practice.
public interface UserRepository extends JpaRepository<User, Long> {
// Numeric comparison
List<User> findByAgeGreaterThan(int age);
List<User> findByAgeBetween(int startAge, int endAge);
// Partial string match
List<User> findByNameContaining(String name); // %name%
List<User> findByNameStartingWith(String prefix); // prefix%
// NULL check
List<User> findByProfileImageIsNull();
List<User> findByDeletedAtIsNotNull();
// Matches any of multiple values
List<User> findByStatusIn(List<String> statuses);
// Boolean type
List<User> findByActiveTrue();
List<User> findByActive(boolean active); // Same as above
}
Containing, StartingWith, and EndingWith are convenient because wildcards are added automatically. When using Like, note that you need to include the wildcards (%, _) yourself.
Sorting (OrderBy) and Paging (Pageable)
For those who arrived at this article by searching “how to write sorting in Spring Boot,” here’s the short answer up front: if the sort order can be fixed, include OrderBy in the method name; if you want to change it dynamically at runtime, pass a Sort or Pageable. Let’s look at both approaches in detail below.
Sorting and paging search results are things you’ll need frequently in real-world development.
Specifying Sort Order via Method Name
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByActiveOrderByNameAsc(boolean active);
List<User> findByActiveOrderByAgeDesc(boolean active);
}
Specifying Dynamically with Pageable (Recommended)
Including the sort order in the method name reduces flexibility. If you want to dynamically specify the sort order or page size at runtime, use a Pageable parameter.
public interface UserRepository extends JpaRepository<User, Long> {
Page<User> findByActive(boolean active, Pageable pageable);
}
// Usage example
Pageable pageable = PageRequest.of(page, size, Sort.by("name").descending());
Page<User> users = userRepository.findByActive(true, pageable);
Returning Page<T> also gives you metadata such as the total number of records and page count.
Custom Queries with the @Query Annotation
If you’d like to go a bit deeper into the basics of @Query introduced here — such as when to use JPQL versus native SQL, or how to write update queries with @Modifying — see How to Use the @Query Annotation in Spring Boot + Spring Data JPA for a detailed breakdown. It’s a good reference when you’re not sure whether to use findBy or @Query.
When you have complex search conditions that can’t be expressed through naming conventions alone, you can write JPQL (Java Persistence Query Language) directly using the @Query annotation.
public interface UserRepository extends JpaRepository<User, Long> {
// Using named parameters (recommended)
@Query("SELECT u FROM User u WHERE u.name = :name AND u.active = :active")
List<User> findActiveUsersByName(@Param("name") String name,
@Param("active") boolean active);
// JOIN - using a related entity's property as a condition
@Query("SELECT o FROM Order o JOIN o.user u WHERE u.name = :userName")
List<Order> findOrdersByUserName(@Param("userName") String userName);
// UPDATE - requires @Modifying and @Transactional
@Modifying
@Transactional
@Query("UPDATE User u SET u.active = false WHERE u.lastLoginAt < :date")
int deactivateInactiveUsers(@Param("date") LocalDateTime date);
}
JPQL resembles SQL, but it uses entity class names instead of table names, and property names instead of column names. Named parameters are recommended because they’re more readable and you don’t need to worry about parameter order.
Using Native Queries (nativeQuery=true)
If you need to use database-specific features that can’t be expressed in JPQL, you can write native SQL directly.
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT * FROM users WHERE DATE(created_at) = :date",
nativeQuery = true)
List<User> findByCreatedDate(@Param("date") String date);
}
Native queries are powerful, but they may stop working if you change databases. Consider them only when database-specific functions or syntax are required, or when performance optimization is necessary.
Choosing Between Query Methods and @Query
Here’s a decision flow for when you’re not sure which to use.
1. First, consider whether it can be implemented with a query method
For simple search conditions (roughly 1 to 3), query methods are easy to understand.
List<User> findByNameAndActive(String name, boolean active);
2. For complex conditions, consider @Query
If the method name would become too long, or if you need JOINs, GROUP BY, or aggregate functions, @Query is more suitable.
@Query("SELECT u FROM User u WHERE u.name LIKE %:keyword% OR u.email LIKE %:keyword%")
List<User> searchByKeyword(@Param("keyword") String keyword);
3. Use native queries only if database-specific features are required
Only consider native queries when performance optimization is needed or when database-specific features are essential.
In team development, it’s a good idea to standardize on this decision criteria.
Common Pitfalls
Here are a few common pitfalls when implementing query methods.
PropertyReferenceException
If you specify a property name that doesn’t exist on the entity, a PropertyReferenceException will occur. Write the method name using the exact camelCase form of the entity’s property — findByUsername if the property is username, findByUserName if it’s userName.
Referencing Properties of Related Entities
When using a property of a related entity as a search condition, it’s clearer to either separate it with an underscore (findByUser_Name) or use @Query.
Notes When Using @Modifying
When using @Modifying, always add @Transactional. Without it, a TransactionRequiredException will occur.
Debugging Tips
If you want to check what SQL is actually being generated, it’s useful to add the following to application.properties.
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
Practical Implementation Patterns
Here are a few concrete query patterns commonly used in real-world development.
Retrieving Aggregated Results
public interface OrderRepository extends JpaRepository<Order, Long> {
@Query("SELECT SUM(o.totalAmount) FROM Order o WHERE o.user.id = :userId")
BigDecimal getTotalAmountByUser(@Param("userId") Long userId);
@Query("SELECT o.user.name as userName, COUNT(o) as orderCount " +
"FROM Order o GROUP BY o.user.id, o.user.name")
List<UserOrderStats> getUserOrderStats();
}
Retrieving Only the Needed Information as a DTO
This is a way to improve performance by retrieving only the information you need.
@Query("SELECT new com.example.dto.UserSummaryDto(u.id, u.name, u.email) " +
"FROM User u WHERE u.active = true")
List<UserSummaryDto> findActiveUserSummaries();
Summary
Spring Data JPA query methods are a convenient feature where SQL is generated automatically just by following the naming convention.
The basic rule of thumb is: use query methods for simple search conditions, and use @Query when you need complex conditions or JOINs. When the method name gets too long or hard to read, that’s your cue to switch to @Query.
Further Advanced Query Techniques (Topics to Learn Next)
Once you’ve got a handle on query methods and @Query, learning the following three topics in order will greatly expand what you can express in practice. Since these are beyond the scope of this article, links to dedicated articles for each are provided below.
Dynamic Queries (Search Forms with Optional Conditions)
Dynamic queries — like a search form where “only the fields that were filled in get added as conditions” — are an area that’s hard to write with query methods or @Query alone. Combining JpaSpecificationExecutor with Specification lets you build these in a type-safe way. For details, see How to Implement Dynamic Queries with Spring Data JPA’s Specification.
The N+1 Problem and Optimizing Related Entity Retrieval
When you retrieve a list with findBy... and then reference related entities in a loop right after, N+1 queries tend to occur. Resolution approaches using @EntityGraph and JOIN FETCH are covered in How to Solve the N+1 Problem in Spring Data JPA.
Projections (Retrieving Only the Columns You Need)
In addition to the DTO Projection mentioned in the main text, Spring Data JPA also supports interface-based Projections. This is effective when you want to minimize the response for a read-only API, and requires less code than SELECT new ....
Equivalent Features on the NoSQL Side
With MongoDB’s MongoRepository, the findBy* naming convention uses almost the same vocabulary. If you’re thinking about extending this to non-RDBMS stores, reading How to Use MongoDB with Spring Boot alongside this article will make it easier to grasp the conceptual parallels.
A Collection of Practical @Query Patterns - LIKE Search, Paging, and Dynamic Sorting
The basics of @Query (named parameters, JOIN, @Modifying) are covered in the main text, but let’s dig a little deeper into patterns that come up often in practice.
Writing LIKE Searches Safely with Parameters
Embedding wildcards directly in JPQL like %:keyword% can be interpreted inconsistently depending on the implementation, so concatenating with CONCAT is a more portable and safer approach.
@Query("SELECT u FROM User u WHERE u.name LIKE CONCAT('%', :keyword, '%')")
List<User> searchByName(@Param("keyword") String keyword);
If user input might contain % or _, also consider escaping with an ESCAPE clause. For simple partial matches, keep in mind that the Containing query method lets you write it more concisely.
Positional Parameters (?1) vs. Named Parameters (:name)
@Query also supports positional parameters like ?1 and ?2, but since simply swapping argument order can introduce bugs, named parameters are recommended.
// Positional parameters: works, but order-dependent and fragile
@Query("SELECT u FROM User u WHERE u.name = ?1 AND u.active = ?2")
List<User> findByNameAndActive(String name, boolean active);
// Named parameters: more readable and order-independent (recommended)
@Query("SELECT u FROM User u WHERE u.name = :name AND u.active = :active")
List<User> findByNameAndActiveSafely(@Param("name") String name,
@Param("active") boolean active);
Combining @Query with Pageable (countQuery)
You can also pass a Pageable to @Query for paging. Since counting total records in queries involving JOINs tends to be inefficient, explicitly specifying countQuery is the standard approach.
@Query(value = "SELECT o FROM Order o JOIN o.user u WHERE u.active = true",
countQuery = "SELECT COUNT(o) FROM Order o JOIN o.user u WHERE u.active = true")
Page<Order> findActiveUserOrders(Pageable pageable);
If you want to narrow down the retrieved columns themselves in a list API, the interface projections and class projections explained in How to Retrieve DTOs Directly with Spring Data JPA Projections are effective. Also, for how to use JOIN FETCH when you want to load related entities together in a query that includes a JOIN, see How to Solve the N+1 Problem in Spring Data JPA.
Dynamic Sorting with a Sort Parameter
If you only want to change the sort order dynamically (without needing paging), you can pass a Sort on its own.
@Query("SELECT u FROM User u WHERE u.active = :active")
List<User> findActiveUsers(@Param("active") boolean active, Sort sort);
// Usage example
List<User> users = userRepository.findActiveUsers(true, Sort.by("createdAt").descending());
Unlike the approach of including OrderBy in the method name, the advantage here is that the caller can switch the sort key. As a rule of thumb: move to @Query once you have four or more conditions and the method name becomes hard to read, and move to Specification once you need variable conditions like a search form that filters only by the inputs provided.