Here is the English translation of the article body.

When you retrieve data from a database with Spring Data JPA, query methods are the feature you will use most often. Because SQL is generated automatically just by following the method naming conventions, you never have to write an implementation class. For beginners, however, this is also where questions tend to arise, such as “What method name should I write?” or “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 with the @Query annotation. By the end, you should be able to implement the query methods you need on your own and choose the right approach for each situation.

What Are Spring Data JPA Query Methods?

Methods Provided Out of the Box by JpaRepository

Before writing your own query methods, let’s review the built-in methods you get simply by extending JpaRepository<T, ID> (verified with Spring Boot 3.x / Spring Data JPA 3.x).

MethodReturn typePurpose
save(entity)SINSERT or UPDATE (determined automatically, e.g. by whether a primary key is present)
saveAll(entities)List<S>Saves multiple entities. Note that by default one SQL statement is issued per entity
findById(id)Optional<T>Fetches a single record by primary key
existsById(id)booleanChecks for existence by primary key
findAll()List<T>Fetches all records
findAll(Pageable)Page<T>Fetches a list with paging and sorting
findAllById(ids)List<T>Fetches multiple records by a list of primary keys (IN clause)
count()longReturns the total number of records
deleteById(id) / delete(entity)voidDeletes a single record
deleteAllInBatch()voidDeletes all records in a single query (bypasses the first-level cache)
getReferenceById(id)TReturns a lazy proxy. Useful when no SELECT is needed, such as when linking associations

Because findById returns Optional<T>, combining it with orElseThrow() is the standard pattern in real-world code.

User user = userRepository.findById(userId)
        .orElseThrow(() -> new EntityNotFoundException("User not found: " + userId));

Note that getById() and getOne(), which were used in older versions, are now deprecated and have been unified into getReferenceById(). Whereas findById issues a SELECT immediately, getReferenceById issues no SQL and returns a lazy-loading proxy instead. Use findById when you want to fetch data while also checking that it exists, and getReferenceById when you only need to link an associated entity without reading the entity’s own columns.

Also, when saving large amounts of data with saveAll(), one INSERT is issued per record by default. For batching configuration, see How to Speed Up Bulk INSERTs with Spring Data JPA.

Query methods, which we’ll look at from here on, are what let you implement search conditions that these standard methods alone cannot cover.

The mechanism behind query methods is simple. When you define a method such as findByName in an interface that extends JpaRepository, Spring Data JPA generates a proxy at runtime, parses the method name, and automatically generates the query for you.

public interface UserRepository extends JpaRepository<User, Long> {
    // この時点で基本的なCRUD操作は使える
    User findByEmail(String email);
    List<User> findByAgeGreaterThan(int age);
}

Basic Naming Rules for Query Methods

Query Method Naming Rules Cheat Sheet

Let’s start with the big picture. The tables below map the keywords Spring Data JPA parses to the SQL/JPQL they generate, along with example method names.

PrefixPurposeExample return typeExample method name
findByFetch dataOptional<T> / List<T>findByEmail(String)
existsByCheck existencebooleanexistsByEmail(String)
countByCount recordslongcountByActive(boolean)
deleteByDelete (requires @Transactional)void / longdeleteByStatus(String)
getBy / readBy / queryBySame as findBy (findBy is recommended)Same as abovegetByEmail(String)

getBy, readBy, and queryBy are aliases for findBy and behave exactly the same. Mixing them makes code harder to search, so we recommend standardizing on findBy, which is by far the most widely used, across your team.

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 ? (you write the wildcards yourself)findByNameLike
ContainingLIKE %?%findByNameContaining
StartingWithLIKE ?%findByNameStartingWith
EndingWithLIKE %?findByNameEndingWith
InIN (?, ?, ...)findByStatusIn
NotInNOT IN (...)findByStatusNotIn
IsNull / IsNotNullIS NULL / IS NOT NULLfindByDeletedAtIsNull
True / False= true / = falsefindByActiveTrue
IgnoreCaseCase-insensitive matchfindByEmailIgnoreCase
OrderBy<Field>Asc/DescORDER BYfindByActiveOrderByNameAsc

Keep this table handy and you will never have to guess when assembling a method name. The following sections dig into each category with concrete examples.

findBy / existsBy / countBy

Use each one according to its purpose.

public interface UserRepository extends JpaRepository<User, Long> {
    // データを取得する
    Optional<User> findByEmail(String email);
    List<User> findByName(String name);

    // 存在チェック(重複チェックなどに便利)
    boolean existsByEmail(String email);

    // 件数を取得
    long countByActive(boolean active);
}

When fetching a single result with findBy, using Optional<T> lets you handle null safely. Using Optional is the recommended practice in production code.

deleteBy

Deletes the records matching the condition. Be aware that delete operations require @Transactional.

public interface UserRepository extends JpaRepository<User, Long> {
    void deleteByStatus(String status);
    long deleteByActiveIsFalse(); // 削除した件数を返すことも可能
}

Combining Multiple Conditions (And/Or)

In real-world development, you frequently need to combine several search conditions. You can combine them using And and Or.

public interface UserRepository extends JpaRepository<User, Long> {
    // And - 全ての条件を満たす必要がある
    User findByNameAndEmail(String name, String email);
    List<User> findByActiveAndAgeGreaterThanEqual(boolean active, int age);

    // Or - いずれか一つでも満たせばマッチ
    List<User> findByNameOrEmail(String name, String email);
}

You can also mix And and Or, but if the method name becomes long and complicated, consider using @Query, which is covered later.

Search Conditions with Comparison Operators

Spring Data JPA supports a wide range of comparison operators. Let’s look at the patterns most commonly used in practice.

public interface UserRepository extends JpaRepository<User, Long> {
    // 数値の比較
    List<User> findByAgeGreaterThan(int age);
    List<User> findByAgeBetween(int startAge, int endAge);

    // 文字列の部分一致
    List<User> findByNameContaining(String name);  // %name%
    List<User> findByNameStartingWith(String prefix);  // prefix%

    // NULL判定
    List<User> findByProfileImageIsNull();
    List<User> findByDeletedAtIsNotNull();

    // 複数の値のいずれかに一致
    List<User> findByStatusIn(List<String> statuses);

    // Boolean型
    List<User> findByActiveTrue();
    List<User> findByActive(boolean active);  // 上記と同じ
}

Containing, StartingWith, and EndingWith are convenient because the wildcards are added automatically. When using Like, remember that you must include the wildcards (%, _) yourself.

Sorting (OrderBy) and Paging (Pageable)

Sorting and paging search results are things you need all the time in real-world development.

Specifying Sort Order in the Method Name

public interface UserRepository extends JpaRepository<User, Long> {
    List<User> findByActiveOrderByNameAsc(boolean active);
    List<User> findByActiveOrderByAgeDesc(boolean active);
}

Embedding the sort order in the method name reduces flexibility. If you want to specify the sort order or page size dynamically at runtime, use a Pageable parameter.

public interface UserRepository extends JpaRepository<User, Long> {
    Page<User> findByActive(boolean active, Pageable pageable);
}

// 使用例
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 count and number of pages.

Custom Queries with the @Query Annotation

When you have complex search conditions that cannot 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> {
    // 名前付きパラメータを使う(推奨)
    @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 - 関連エンティティのプロパティを条件にする
    @Query("SELECT o FROM Order o JOIN o.user u WHERE u.name = :userName")
    List<Order> findOrdersByUserName(@Param("userName") String userName);

    // UPDATE - @Modifyingと@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 are more readable and you don’t have to worry about parameter order.

Using Native Queries (nativeQuery=true)

If you need database-specific features that cannot 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 switch databases. Consider them only when database-specific functions or syntax are essential, or when performance optimization is required.

Choosing Between Query Methods and @Query

When in doubt, here is a decision flow you can follow.

1. First, Check Whether a Query Method Will Do

For simple search conditions (roughly one to three), a query method is the clearest option.

List<User> findByNameAndActive(String name, boolean active);

2. For Complex Conditions, Consider @Query

If the method name is getting too long, or you need JOIN, GROUP BY, or aggregate functions, @Query is the better fit.

@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 When You Need Database-Specific Features

Consider native queries only when performance optimization is required or database-specific features are essential.

In team development, it’s a good idea to agree on these criteria up front.

Common Pitfalls

Here are some common pitfalls you may run into when implementing query methods.

PropertyReferenceException

If you specify a property name that does not exist on the entity, a PropertyReferenceException is thrown. Write the name in exact camel case: findByUsername if the entity property is username, and findByUserName if it is userName.

Referencing Properties of Associated Entities

When using a property of an associated entity as a search condition, it is clearer to either separate it with an underscore (findByUser_Name) or use @Query.

Caveats When Using @Modifying

Always add @Transactional when using @Modifying. Without it, a TransactionRequiredException is thrown.

How to Debug

If you want to see what SQL is actually being generated, adding the following to application.properties is helpful.

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

Fetching Only the Data You Need with a DTO

This approach improves performance by fetching only the information you actually 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 generates SQL automatically just by following naming conventions.

The basic rule of thumb is to use query methods for simple search conditions and @Query when you need complex conditions or JOINs. When a method name gets too long or hard to read, that’s your cue to switch to @Query.

More Advanced Query Techniques (What to Learn Next)

Once you have a handle on query methods and @Query, learning the following three topics in order will greatly expand what you can express in practice. They go beyond the scope of this article, so links to dedicated articles are provided for each.

Dynamic Queries (Search Forms with Optional Conditions)

Dynamic queries of the “add only the fields that were filled in as conditions” variety, as in a search form, are hard to write with query methods or @Query alone. Combining JpaSpecificationExecutor with Specification lets you build them in a type-safe way. For details, see How to Implement Dynamic Queries with Spring Data JPA Specification.

The N+1 Problem and Optimizing Associated Entity Fetching

If you fetch a list with findBy... and then reference associated entities inside a loop, N+1 queries are likely to occur. Solutions using @EntityGraph and JOIN FETCH are covered in How to Solve the N+1 Problem in Spring Data JPA.

Projections (Fetching Only the Columns You Need)

In addition to the DTO projection covered in this article, Spring Data JPA also supports interface-based projections. They are effective when you want to minimize responses in read-only APIs, and they require less code than SELECT new ....

The NoSQL Equivalent

MongoDB’s MongoRepository supports nearly the same vocabulary for the findBy* naming conventions. If you are considering expanding beyond an RDBMS, reading How to Use MongoDB with Spring Boot alongside this article will make the conceptual comparison easier to grasp.

Practical @Query Patterns: LIKE Searches, Paging, and Dynamic Sorting

We covered the basics of @Query (named parameters, JOIN, and @Modifying) above, but let’s dig a bit deeper into the patterns people most often search for in practice.

Writing LIKE Searches Safely with Parameters

Embedding wildcards directly in JPQL, as in %:keyword%, is interpreted inconsistently across implementations, so concatenating with CONCAT is more portable and safer.

@Query("SELECT u FROM User u WHERE u.name LIKE CONCAT('%', :keyword, '%')")
List<User> searchByName(@Param("keyword") String keyword);

If user input may contain % or _, also consider escaping with an ESCAPE clause. And for a simple partial match, remember that the query method keyword Containing is often the shorter option.

Positional Parameters (?1) vs. Named Parameters (:name)

@Query also supports positional parameters such as ?1 and ?2, but simply reordering the arguments introduces a bug, so named parameters are recommended.

// 位置パラメータ: 動くが順序依存で壊れやすい
@Query("SELECT u FROM User u WHERE u.name = ?1 AND u.active = ?2")
List<User> findByNameAndActive(String name, boolean active);

// 名前付きパラメータ: 可読性が高く順序に依存しない(推奨)
@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 pass Pageable to @Query for paging as well. For queries that include JOINs, retrieving the total count tends to be inefficient, so the standard practice is to specify countQuery explicitly.

@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 columns fetched in a list API, the interface-based and class-based projections explained in How to Fetch DTOs Directly with Spring Data JPA Projections are effective. And for how to use JOIN FETCH when you want to load associated entities at the same time in a query with JOINs, 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 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);

// 使用例
List<User> users = userRepository.findActiveUsers(true, Sort.by("createdAt").descending());

Unlike embedding OrderBy in the method name, the advantage here is that the caller can switch the sort key. Move up in stages: switch to @Query once you have four or more conditions and the method name becomes hard to read, and switch to Specification once you need variable conditions such as a search form that filters only by the fields that were entered.