Home Java Spring Data JPA Derived Query Methods: Naming Conventions & Custom Queries
Intermediate 6 min · July 14, 2026

Spring Data JPA Derived Query Methods: Naming Conventions & Custom Queries

Master Spring Data JPA derived query methods with real-world naming conventions, custom queries, and production debugging tips.

N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Drawn from code that ran under real load.

Follow
Production
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
Before you start⏱ 15-20 min read
  • Basic knowledge of Spring Boot and Spring Data JPA
  • Familiarity with JPA entities and repository pattern
  • Understanding of JPQL and SQL basics
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Derived query methods generate queries from method names using prefixes like findBy, countBy, deleteBy.
  • Follow naming conventions: findByProperty, findByPropertyAndOtherProperty, findByPropertyContaining.
  • Use @Query for complex or performance-critical queries.
  • Avoid N+1 queries: use join fetch or @EntityGraph.
  • Test derived queries with large datasets to catch naming mismatches.
✦ Definition~90s read
What is Derived Query Methods in Spring Data JPA?

Spring Data JPA derived query methods allow you to define database queries by simply declaring method names in repository interfaces, following a strict naming convention that Spring parses to generate JPQL automatically.

Think of derived query methods like a smart assistant who understands what you want based on how you phrase it.
Plain-English First

Think of derived query methods like a smart assistant who understands what you want based on how you phrase it. If you say 'find all red cars from 2020', the assistant knows to filter by color and year. But if you mumble or use the wrong words, you get wrong results. Spring Data JPA works the same way: the method name tells it exactly what SQL to generate.

If you've worked with Spring Data JPA for more than a week, you've probably written a repository method like findByLastName(String lastName) and marveled at how it just works. It's almost magical — until it isn't. I've seen production outages caused by a single misspelled property name in a derived query method. The method compiles fine, but at runtime? You get a confusing error or, worse, silent incorrect results.

In this article, I'll walk you through the real-world naming conventions for derived query methods, when to use @Query instead, and what the official docs gloss over. I'll share war stories from a fintech startup where a derived query caused a data leak, and give you a debugging guide that'll save you hours of head-scratching.

By the end, you'll know exactly when to rely on Spring's magic and when to write your own JPQL. Let's dive in.

Understanding Derived Query Methods: The Naming Convention

Derived query methods are the bread and butter of Spring Data JPA. You define a method in your repository interface, and Spring parses the method name to generate a JPQL query automatically. The naming convention is strict but powerful.

The basic pattern is: [action][By][property][comparison][ordering].

  • Action: find, read, get, query, count, delete, remove.
  • By: Required marker that starts the criteria.
  • Property: The entity property name (camelCase).
  • Comparison: Optional keywords like And, Or, Between, LessThan, GreaterThan, Like, Containing, In, IsNull, IsNotNull.
  • Ordering: OrderBy[property][Asc|Desc].

For example: findByLastNameAndAgeBetweenOrderByLastNameAsc(String lastName, int start, int end) generates WHERE last_name = ?1 AND age BETWEEN ?2 AND ?3 ORDER BY last_name ASC.

What the docs don't stress enough: property names must match exactly. I've seen a findByEmailAddress fail because the field was email, not emailAddress. The error message is cryptic: No property emailAddress found for type User. You'll waste an hour if you don't check the entity.

Also, nested properties work via underscore: findByAddress_ZipCode(String zip). But if you have a property named address with a field zipCode, you can also write findByAddressZipCode if the path is unambiguous. However, if there's a property addressZipCode on the entity, Spring will prefer that. Use _ to disambiguate.

Pro tip: Always enable spring.jpa.show-sql=true in development. It shows the generated SQL, so you can verify your method name produces the right query.

UserRepository.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface UserRepository extends JpaRepository<User, Long> {
    // Derived query: findByLastName
    List<User> findByLastName(String lastName);

    // Multiple criteria with AND
    List<User> findByLastNameAndFirstName(String lastName, String firstName);

    // Nested property with underscore
    List<User> findByAddress_ZipCode(String zipCode);

    // With comparison and ordering
    List<User> findByAgeBetweenOrderByLastNameAsc(int start, int end);

    // Distinct
    List<User> findDistinctByLastName(String lastName);

    // Limit results
    List<User> findFirst10ByLastName(String lastName);
}
💡Use IDE Autocomplete
📊 Production Insight
I once worked on a project where a developer used findByCreatedDate but the field was createdAt. The method compiled fine but threw an exception at runtime when the first request hit. Always test your repositories with real data.
🎯 Key Takeaway
Derived query methods are powerful but require exact property name matching. Use IDE support and enable SQL logging to verify generated queries.

When Derived Queries Fall Short: Enter @Query

Derived query methods are great for simple CRUD, but they have limits. When you need joins, subqueries, aggregation, or complex conditions, @Query is your friend.

With @Query, you write JPQL (or native SQL) directly. This gives you full control. For example, a derived query can't easily do a join across unrelated entities or use database-specific functions.

Consider a scenario: you need to find all orders placed by users in a certain city. With derived queries, you'd need something like findByUserAddressCity(String city). That works, but it implies a join path. If the path is deep or ambiguous, @Query is clearer.

``java @Query("SELECT o FROM Order o JOIN o.user u JOIN u.address a WHERE a.city = :city") List<Order> findByUserCity(@Param("city") String city); ``

Another case: you want to update multiple records in one query. Derived methods only support delete and count for bulk operations. For updates, use @Modifying with @Query.

``java @Modifying @Query("UPDATE User u SET u.active = false WHERE u.lastLogin < :date") int deactivateOldUsers(@Param("date") LocalDate date); ``

Performance tip: For read-only queries, add @QueryHints(@QueryHint(name = org.hibernate.jpa.QueryHints.HINT_READONLY, value = "true")) to avoid dirty checking.

What the docs don't tell you: @Query methods bypass Spring's derived query parsing, so they are slightly faster at startup. But they also bypass some of Spring's automatic count queries for pagination. If you use pagination with @Query, you must also provide a count query: @Query(countQuery = "SELECT COUNT(o) FROM ...").

OrderRepository.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface OrderRepository extends JpaRepository<Order, Long> {
    // Custom JPQL query with join
    @Query("SELECT o FROM Order o JOIN o.user u JOIN u.address a WHERE a.city = :city")
    List<Order> findByUserCity(@Param("city") String city);

    // Bulk update with @Modifying
    @Modifying
    @Query("UPDATE Order o SET o.status = :status WHERE o.createdDate < :date")
    int updateStatusForOldOrders(@Param("status") OrderStatus status, @Param("date") LocalDate date);

    // Pagination with count query
    @Query(value = "SELECT o FROM Order o WHERE o.total > :amount",
           countQuery = "SELECT COUNT(o) FROM Order o WHERE o.total > :amount")
    Page<Order> findExpensiveOrders(@Param("amount") BigDecimal amount, Pageable pageable);
}
⚠ Don't Forget @Modifying for Updates
🎯 Key Takeaway
Use @Query for complex joins, updates, or when you need fine-grained control over the SQL. Always provide a countQuery for pagination.

What the Official Docs Won't Tell You

I've been burned by Spring Data JPA's derived query methods more times than I'd like to admit. Here are the gotchas that the official reference documentation glosses over.

1. Implicit AND vs OR ambiguity

When you write findByLastNameOrFirstName, it's clear: WHERE last_name = ?1 OR first_name = ?2. But what about findByLastNameAndFirstNameOrEmail? It's actually WHERE last_name = ?1 AND (first_name = ?2 OR email = ?3). The AND binds tighter than OR. If you need different grouping, you must use @Query. I've seen bugs where developers assumed left-to-right evaluation.

2. The Distinct keyword is not always honored

findDistinctByLastName generates SELECT DISTINCT .... But if you have a join in the background (e.g., due to @ElementCollection or @OneToMany), the distinct may be applied after the join, leading to unexpected results. Hibernate might also ignore DISTINCT if you use join fetch. Test with your actual data.

3. Case sensitivity of property names

Property names in derived queries are case-sensitive. findByLastname will fail if the field is lastName. The error message is not helpful: it says No property 'lastname' found. The fix is to match the exact camelCase.

4. The Is prefix is optional but can cause confusion

findByActiveTrue() works, but findByActiveIsTrue() also works. Some developers use Is to make the method read more naturally. However, if you have a property named isActive (which is a boolean), the method findByIsActive will look for a property isActive, not active. This is a common pitfall when using Lombok's @Getter with is prefix.

5. Null handling

Derived queries treat null parameters differently depending on the comparison. findByLastName(String lastName) will pass null to the query, which becomes WHERE last_name = NULL (which never matches). If you want to ignore null, you need to use @Query with :lastName IS NULL OR last_name = :lastName. Spring Data JPA 2.x introduced @Nullable and Optional support, but it's not automatic.

6. Performance: derived queries are parsed at startup

Every derived method is parsed when the application context starts. If you have hundreds of repository methods, startup time increases. This is rarely an issue, but in large monoliths, it can add seconds. Consider using @Query for methods that are called infrequently.

7. The @Param annotation is required for named parameters in @Query

If you use named parameters (e.g., :name), you must annotate the method parameter with @Param("name"). Without it, Spring will throw an IllegalArgumentException. This is documented but often forgotten.

AmbiguousQueryExample.javaJAVA
1
2
3
4
5
6
7
8
// This query: WHERE last_name = ?1 AND (first_name = ?2 OR email = ?3)
List<User> findByLastNameAndFirstNameOrEmail(String lastName, String firstName, String email);

// To get: (last_name = ?1 AND first_name = ?2) OR email = ?3, use @Query
@Query("SELECT u FROM User u WHERE (u.lastName = :lastName AND u.firstName = :firstName) OR u.email = :email")
List<User> findByLastNameAndFirstNameOrEmailGrouped(@Param("lastName") String lastName,
                                                     @Param("firstName") String firstName,
                                                     @Param("email") String email);
🔥Always Test with Real Data
📊 Production Insight
At a previous job, a derived query findByCreatedDateBetween returned no results because the createdDate field was a Timestamp and the parameters were LocalDate. The comparison failed silently because of type mismatch. Always ensure parameter types match the entity field types.
🎯 Key Takeaway
Derived queries have subtle behaviors around operator precedence, null handling, and case sensitivity. Always verify the generated SQL and test with realistic data.

Combining Derived and Custom Queries: Best Practices

In a real application, you'll mix derived methods with @Query. The key is to use each where it shines.

Use derived methods for: - Simple lookups by single fields or combinations of a few fields. - Standard CRUD operations (findAll, findById). - Count and delete operations. - When the query logic is obvious and unlikely to change.

Use @Query for: - Complex joins across multiple entities. - Aggregations (SUM, COUNT, GROUP BY). - Queries that need database-specific functions. - Bulk updates and deletes. - Queries where performance is critical and you need to control the SQL.

Never mix derived and custom in the same method – you can't. It's either one or the other.

Naming convention for custom methods: I recommend prefixing custom query methods with findBy as well, to keep a consistent API. For example: findByUserCity (custom) vs findByLastName (derived). This makes the repository interface easy to read.

Testing: Both derived and custom queries should be tested. Use @DataJpaTest with an embedded database or Testcontainers. Test edge cases: null parameters, empty collections, large datasets.

Performance monitoring: Enable Hibernate statistics (spring.jpa.properties.hibernate.generate_statistics=true) to see how many queries are executed. Derived queries can cause N+1 if you're not careful with associations.

Example of mixing: Suppose you have a UserRepository with a derived method findByEmail and a custom method findByUserCity. Both are List<User> returns. The derived method is simple, the custom one handles a join.

UserRepositoryMixin.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
public interface UserRepository extends JpaRepository<User, Long> {
    // Derived method
    List<User> findByEmail(String email);

    // Custom method with join
    @Query("SELECT DISTINCT u FROM User u JOIN FETCH u.roles WHERE u.email = :email")
    User findByEmailWithRoles(@Param("email") String email);

    // Custom method with aggregation
    @Query("SELECT COUNT(u) FROM User u WHERE u.active = true")
    long countActiveUsers();
}
💡Use JOIN FETCH to Avoid N+1
📊 Production Insight
In a high-traffic system, a derived findAll on an entity with many lazy associations caused thousands of queries per page load. We replaced it with a @Query using JOIN FETCH and reduced the query count to one.
🎯 Key Takeaway
Mix derived and custom queries strategically. Use derived for simplicity, custom for complexity. Always test both types thoroughly.

Advanced Derived Query Techniques: Limiting, Paging, and Sorting

Derived query methods support pagination and sorting out of the box. You can pass Pageable or Sort parameters to any method that returns a collection or Page.

Pagination: Page<User> findByLastName(String lastName, Pageable pageable);

Sorting: List<User> findByLastName(String lastName, Sort sort);

Limiting results: Use First or Top in the method name: findFirstByLastName, findTop10ByLastName. You can also combine with ordering: findFirst3ByLastNameOrderByAgeDesc.

Important: When using Page, Spring automatically executes a count query to determine total pages. For derived queries, this works fine. For @Query, you must provide a countQuery as mentioned earlier.

Streaming results: For large result sets, you can return Stream<User> and use @QueryHints to enable scrolling. This avoids loading all results into memory.

``java @QueryHints(@QueryHint(name = HINT_FETCH_SIZE, value = "100")) Stream<User> findAllByActiveTrue(); ``

Production pitfall: If you use Stream, you must close the stream in a try-with-resources block, or you'll leak database connections. Spring Data JPA does not automatically close the stream.

What the docs don't tell you: The Page object returned by a derived method with Pageable includes the count query. If the count query is expensive (e.g., on a large table without an index), it can slow down your application. Consider using Slice instead of Page if you don't need the total count. Slice only checks if there is a next page, which is cheaper.

``java Slice<User> findByLastName(String lastName, Pageable pageable); ``

PagingAndSortingRepository.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface UserRepository extends JpaRepository<User, Long> {
    // Pagination with Page
    Page<User> findByLastName(String lastName, Pageable pageable);

    // Sorting
    List<User> findByLastName(String lastName, Sort sort);

    // Limiting with Top
    List<User> findTop5ByLastNameOrderByAgeDesc(String lastName);

    // Streaming
    @QueryHints(@QueryHint(name = HINT_FETCH_SIZE, value = "100"))
    Stream<User> findAllByActiveTrue();

    // Slice (no count query)
    Slice<User> findByCity(String city, Pageable pageable);
}
⚠ Close Streams Properly
📊 Production Insight
I once saw a production incident where a Page query on a table with 10 million rows took 30 seconds because the count query did a full table scan. We switched to Slice and added an index, reducing response time to 100ms.
🎯 Key Takeaway
Use Pageable and Sort for flexible querying. Prefer Slice over Page when you don't need the total count. Always close streams.

Custom Queries with Specifications and QueryDSL

For dynamic queries where the criteria change at runtime (e.g., search filters), derived methods and static @Query won't cut it. You need Specification (from Spring Data JPA) or QueryDSL.

Specifications allow you to build queries programmatically using a fluent API. They are based on the JPA Criteria API but are more readable.

``java public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> { } ``

```java public class UserSpecifications { public static Specification<User> hasLastName(String lastName) { return (root, query, cb) -> cb.equal(root.get("lastName"), lastName); }

public static Specification<User> ageGreaterThan(int age) { return (root, query, cb) -> cb.greaterThan(root.get("age"), age); } } ```

``java List<User> users = userRepository.findAll( Specification.where(UserSpecifications.hasLastName("Smith")) .and(UserSpecifications.ageGreaterThan(25)) ); ``

QueryDSL is another option, but it requires code generation. It's more powerful but adds build complexity. I recommend Specifications for most cases.

What the docs don't tell you: Specifications can lead to complex and hard-to-debug queries if overused. Also, they don't support joins as naturally as JPQL. For complex joins, stick with @Query.

Production insight: At a SaaS company, we used Specifications to build a dynamic report filter. The query became so complex that it caused a database deadlock. We had to introduce query timeouts and limit the number of predicates.

UserSpecifications.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class UserSpecifications {
    public static Specification<User> hasLastName(String lastName) {
        return (root, query, cb) -> cb.equal(root.get("lastName"), lastName);
    }

    public static Specification<User> ageGreaterThan(int age) {
        return (root, query, cb) -> cb.greaterThan(root.get("age"), age);
    }

    public static Specification<User> emailLike(String emailPattern) {
        return (root, query, cb) -> cb.like(root.get("email"), emailPattern);
    }
}

// Usage
List<User> users = userRepository.findAll(
    Specification.where(UserSpecifications.hasLastName("Smith"))
                 .and(UserSpecifications.ageGreaterThan(25))
                 .and(UserSpecifications.emailLike("%@example.com"))
);
🔥Keep Specifications Simple
🎯 Key Takeaway
Use Specifications for dynamic queries with varying criteria. They are cleaner than building Criteria API manually, but beware of complexity.
● Production incidentPOST-MORTEMseverity: high

The Silent Data Leak: How a Derived Query Exposed Customer Records

Symptom
Users saw other users' transaction histories when searching by email. The UI displayed random records intermittently.
Assumption
The developer assumed findByEmail would return a unique result, but the database had multiple records with the same email due to a legacy migration.
Root cause
The derived method findByEmail generated SELECT * FROM transactions WHERE email = ? instead of WHERE email = ? AND user_id = ?, because the repository was on the Transaction entity, not the User entity. The method didn't scope to the current user.
Fix
Changed the method to findByUserEmailAndUserId or used a @Query with explicit join and user filtering. Added a unique constraint on email per user.
Key lesson
  • Always think about the entity context: a derived query operates on the repository's entity type only.
  • Use @Query for multi-table joins or security-scoped queries; don't trust naming alone.
  • Add database constraints (unique, check) to enforce data integrity beyond JPA.
  • Test derived queries with data that has duplicates or nulls.
Production debug guideSymptom to Action4 entries
Symptom · 01
Method compiles but throws InvalidDataAccessApiUsageException at runtime with 'No property [xyz] found for type [Entity]'
Fix
Check the method name spelling and property names. Use IDE autocomplete or CriteriaBuilder to verify property paths. Enable SQL logging (spring.jpa.show-sql=true) to see the generated query.
Symptom · 02
Query returns unexpected results (too many or too few rows)
Fix
Add @Query with explicit JPQL and test it directly in the database. Ensure your method signature matches the expected parameters and return type. Check for implicit AND vs OR logic.
Symptom · 03
N+1 queries: derived query triggers many SQL statements
Fix
Use @EntityGraph or JOIN FETCH in @Query. Avoid findAll on entities with lazy associations. Enable Hibernate statistics to monitor query count.
Symptom · 04
Queries are slow despite simple derived method
Fix
Check if the derived method generates a full table scan due to missing index. Use @QueryHint or @Index annotations. Analyze the generated SQL with EXPLAIN.
★ Quick Debug Cheat SheetQuick reference for common derived query issues.
No property found for type
Immediate action
Verify property name in entity
Commands
spring.jpa.show-sql=true
logging.level.org.hibernate.SQL=DEBUG
Fix now
Correct the method name or add missing property
Unexpected results (too many rows)+
Immediate action
Check if method uses `Distinct` or `First`
Commands
Add `Distinct` or `Top`/`First` to method name
Use `@Query` with explicit JPQL
Fix now
Refine method naming or switch to @Query
N+1 queries+
Immediate action
Identify lazy-loaded associations
Commands
Add `@EntityGraph` or `JOIN FETCH`
Set `spring.jpa.open-in-view=false`
Fix now
Use @EntityGraph(attributePaths = {...})
FeatureDerived Query@Query
Query generationAutomatic from method nameManual JPQL/SQL
Complex joinsLimited (nested properties)Full support
AggregationsNoYes
Bulk updatesOnly deleteYes with @Modifying
Dynamic filteringNot supportedVia Specifications or manual logic
Performance at startupParsed at startupParsed at startup
ReadabilityConcise for simple queriesExplicit but verbose
⚙ Quick Reference
6 commands from this guide
FileCommand / CodePurpose
UserRepository.javapublic interface UserRepository extends JpaRepository {Understanding Derived Query Methods
OrderRepository.javapublic interface OrderRepository extends JpaRepository {When Derived Queries Fall Short
AmbiguousQueryExample.javaList findByLastNameAndFirstNameOrEmail(String lastName, String firstName, ...What the Official Docs Won't Tell You
UserRepositoryMixin.javapublic interface UserRepository extends JpaRepository {Combining Derived and Custom Queries
PagingAndSortingRepository.javapublic interface UserRepository extends JpaRepository {Advanced Derived Query Techniques
UserSpecifications.javapublic class UserSpecifications {Custom Queries with Specifications and QueryDSL

Key takeaways

1
Derived query methods are concise but require exact property name matching and have limitations with joins and aggregations.
2
Use @Query for complex queries, bulk updates, and when you need control over the SQL.
3
Always enable SQL logging in development to verify generated queries.
4
Test derived queries with edge cases
nulls, duplicates, and large datasets.
5
Prefer Slice over Page when you don't need the total count to avoid expensive count queries.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
Explain how Spring Data JPA derived query methods work. What is the nami...
Q02SENIOR
How would you implement a query that returns users with orders placed in...
Q03SENIOR
What are the limitations of derived query methods, and how do you overco...
Q01 of 03JUNIOR

Explain how Spring Data JPA derived query methods work. What is the naming convention?

ANSWER
Derived query methods parse the method name to generate JPQL automatically. The pattern is [action][By][property][comparison][ordering]. For example, findByLastNameAndAgeBetweenOrderByLastNameAsc generates WHERE last_name = ?1 AND age BETWEEN ?2 AND ?3 ORDER BY last_name ASC. The method name must match entity property names exactly.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
Can I use derived query methods with native SQL?
02
How do I handle null parameters in derived queries?
03
What is the difference between `findBy` and `findAllBy`?
04
Why is my derived query returning an empty list even though data exists?
N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Drawn from code that ran under real load.

Follow
Verified
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
🔥

That's Hibernate & JPA. Mark it forged?

6 min read · try the examples if you haven't

Previous
Spring Data JPA @Query: Custom JPQL, Native Queries, and Named Queries
18 / 28 · Hibernate & JPA
Next
Calling Stored Procedures from Spring Data JPA Repositories