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., findByName or existsByEmail)
  • 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.

PrefixPurposeExample Return TypeExample Method Name
findByRetrieve dataOptional<T> / List<T>findByEmail(String)
existsByCheck existencebooleanexistsByEmail(String)
countByGet countlongcountByActive(boolean)
deleteByDelete (requires @Transactional)void / longdeleteByStatus(String)
getBy / readBy / queryBySame as findBy (recommended: findBy)Same as abovegetByEmail(String)

Condition keywords are combined as follows:

KeywordGenerated ConditionExample Method Name
Andcond1 AND cond2findByNameAndEmail
Orcond1 OR cond2findByNameOrEmail
GreaterThan / GreaterThanEqual> / >=findByAgeGreaterThan
LessThan / LessThanEqual< / <=findByAgeLessThanEqual
BetweenBETWEEN ? AND ?findByAgeBetween
LikeLIKE ? (wildcards written manually)findByNameLike
ContainingLIKE %?%findByNameContaining
StartingWithLIKE ?%findByNameStartingWith
EndingWithLIKE %?findByNameEndingWith
InIN (?, ?, ...)findByStatusIn
NotInNOT IN (...)findByStatusNotIn
IsNull / IsNotNullIS NULL / IS NOT NULLfindByDeletedAtIsNull
True / False= true / = falsefindByActiveTrue
IgnoreCaseCase-insensitivefindByEmailIgnoreCase
OrderBy<Field>Asc/DescORDER BYfindByActiveOrderByNameAsc

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);
}

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.

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.

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.