When retrieving data from a database with Spring Data JPA, query methods are the most frequently used feature. By simply following the method naming conventions, SQL is automatically generated, eliminating the need to write implementation classes. However, for beginners, questions often arise such as “What method names should I write?” or “How do I implement complex search conditions?”
In this article, we’ll walk through Spring Data JPA query methods step by step—from the basics to combining multiple conditions, sorting and pagination, and custom queries using the @Query annotation. By the end, you should be able to implement the query methods you need on your own and choose the appropriate approach.
What Are Spring Data JPA Query Methods?
Query methods are one of the powerful features provided by Spring Data JPA. By simply defining methods that follow specific naming conventions, Spring Data JPA automatically generates SQL queries at runtime.
The basic mechanism works like this:
- Define methods in an interface that extends
JpaRepository - Method names follow specific naming conventions (e.g.,
findByNameorexistsByEmail) - Spring Data JPA generates a proxy at runtime, parses the method name, and automatically generates the query
- No need to write an implementation class
JpaRepository provides basic CRUD operations (save(), findById(), findAll(), delete(), etc.), but when you need custom search conditions, you define your own query methods.
public interface UserRepository extends JpaRepository<User, Long> {
// Basic CRUD operations are available at this point
User findByEmail(String email);
List<User> findByAgeGreaterThan(int age);
}
Basic Naming Conventions for Query Methods
Query Method Naming Convention Cheat Sheet
Let’s first get an overview at a glance. The following table maps the keywords Spring Data JPA parses to the generated SQL/JPQL and example method names.
| Prefix | Purpose | Example Return Type | 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 | Same as findBy (recommended: findBy) | Same as above | getByEmail(String) |
Condition keywords are 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 written 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 eliminate any confusion about “which keyword to use” when constructing method names. In the following sections, we’ll dive into each category with concrete examples.
Query method naming conventions are composed of prefixes combined with search conditions. Let’s look at the commonly used prefixes.
findBy / existsBy / countBy
Each is used 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 duplicate checks, etc.)
boolean existsByEmail(String email);
// Get the count
long countByActive(boolean active);
}
When retrieving a single result with findBy, using Optional<T> allows you to handle null safely. In practice, the use of Optional is recommended.
deleteBy
Deletes records matching the condition. Note that delete operations require @Transactional.
public interface UserRepository extends JpaRepository<User, Long> {
void deleteByStatus(String status);
long deleteByActiveIsFalse(); // It's also possible to return the number of deleted records
}
Combining Multiple Conditions (And/Or)
In practice, you’ll frequently combine multiple search conditions. 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 (described later).
Search Conditions Using Comparison Operators
Spring Data JPA supports various comparison operators. Let’s look at the 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);
// String partial match
List<User> findByNameContaining(String name); // %name%
List<User> findByNameStartingWith(String prefix); // prefix%
// NULL check
List<User> findByProfileImageIsNull();
List<User> findByDeletedAtIsNotNull();
// Match 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 automatically added. When using Like, note that you need to include the wildcards (%, _) yourself.
Sorting (OrderBy) and Pagination (Pageable)
Sorting and paginating search results is something you’ll frequently need in practice.
Specifying Sort with Method Names
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. When you want to dynamically specify 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);
By returning Page<T>, you can also obtain meta-information such as the total count and number of pages.
Custom Queries with the @Query Annotation
When you have complex search conditions that can’t be expressed with naming conventions alone, you can write JPQL (Java Persistence Query Language) directly using the @Query annotation.
public interface UserRepository extends JpaRepository<User, Long> {
// Use 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 - use properties of related entities as conditions
@Query("SELECT o FROM Order o JOIN o.user u WHERE u.name = :userName")
List<Order> findOrdersByUserName(@Param("userName") String userName);
// UPDATE - @Modifying and @Transactional are required
@Modifying
@Transactional
@Query("UPDATE User u SET u.active = false WHERE u.lastLoginAt < :date")
int deactivateInactiveUsers(@Param("date") LocalDateTime date);
}
JPQL is similar to SQL, but it uses entity class names instead of table names and property names instead of column names. Named parameters are recommended because they offer better readability and you don’t have to worry about parameter order.
Using Native Queries (nativeQuery=true)
When you want 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 break if you switch databases. Consider them when database-specific functions or syntax are essential, or when performance optimization is required.
Choosing Between Query Methods and @Query
Here’s a decision flow when you’re not sure which to use.
1. First, Consider Whether It Can Be Implemented with a Query Method
For simple search conditions (about 1 to 3 conditions), query methods are easier to understand.
List<User> findByNameAndActive(String name, boolean active);
2. For Complex Conditions, Consider @Query
When the method name would become too long, or when JOIN, GROUP BY, or aggregate functions are needed, @Query is more appropriate.
@Query("SELECT u FROM User u WHERE u.name LIKE %:keyword% OR u.email LIKE %:keyword%")
List<User> searchByKeyword(@Param("keyword") String keyword);
3. For Database-Specific Features, Use Native Queries
Consider native queries only when performance optimization is required or when database-specific features are essential.
In team development, it’s a good idea to standardize these decision criteria.
Common Pitfalls
Here are some common pitfalls you might encounter when implementing query methods.
PropertyReferenceException
If you specify a property name that doesn’t exist in the entity, a PropertyReferenceException will be thrown. Make sure to use the exact camelCase: if the entity property is username, use findByUsername; if it’s userName, use findByUserName.
Referencing Properties of Related Entities
When using properties of related entities as search conditions, it’s clearer to separate them with an underscore (findByUser_Name) or to use @Query.
Caveats When Using @Modifying
When using @Modifying, always add @Transactional. Otherwise, a TransactionRequiredException will be thrown.
Debugging Methods
When you want to check what SQL is actually being generated, it’s helpful to add the following to application.properties:
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
Implementation Patterns Used in Practice
Here are some 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 Necessary Information as a DTO
A method 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 that automatically generates SQL just by following naming conventions.
The basic rule of thumb is: use query methods for simple search conditions, and use @Query when you need complex conditions or JOINs. When method names become too long or hard to read, it’s time to switch to @Query.
By mastering query methods, you’ll be able to retrieve data from the database efficiently. Learning entity design and transaction management alongside this will help you build more practical application development skills.
More Advanced Query Techniques (Topics to Learn Next)
Once you’ve got query methods and @Query down, learning the following three topics in order will significantly broaden your expressive range in practice. Since they’re beyond the scope of this article, I’ve included links to dedicated articles for each.
Dynamic Queries (Search Forms with Optional Conditions)
Dynamic queries that “add only the entered fields as conditions”—like search forms—are an area that’s difficult to write with query methods or @Query alone. Combining JpaSpecificationExecutor with Specification allows you to build them in a type-safe manner. For details, see How to Implement Dynamic Queries with Spring Data JPA Specification.
The N+1 Problem and Optimizing Related Entity Retrieval
When you retrieve a list with findBy... and then loop through it to reference related entities, N+1 queries tend to occur. Methods for resolving this using @EntityGraph and JOIN FETCH are summarized in How to Solve the N+1 Problem in Spring Data JPA.
Projection (Retrieving Only the Necessary Columns)
In addition to the DTO Projection mentioned in the main text, Spring Data JPA also supports interface-based Projection. This is useful when you want to minimize responses in read-only APIs, and it requires less code than SELECT new ....
Equivalent Features on the NoSQL Side
MongoDB’s MongoRepository also supports the findBy* naming convention with nearly the same vocabulary. If you’re considering expanding beyond RDBMS, reading How to Use MongoDB with Spring Boot alongside this article will help you grasp the conceptual parallels.