Hook: Interviewers love this because it shows whether you can let Spring Data write the query for you without losing control of the database behavior.
Question: What are derived query methods in Spring Data JPA?
Answer: Derived query methods are repository methods whose names Spring Data JPA reads and turns into database queries. For example, findByLastName, countByActiveTrue, or findTop3ByOrderByCreatedAtDesc all work without writing SQL or JPQL. They are great for simple lookups because they are quick to write and easy to read, and invalid property names usually fail fast when the repository starts.
Interview-Ready Answer: In Spring Data JPA, derived query methods are repository methods where the method name itself describes the query. I can write something like findByEmail or findByLastNameAndActiveTrue, and Spring parses that name, validates the entity fields, and generates the query automatically. I use them for simple, readable queries, but if the logic gets complex or I need more control, I switch to @Query or Specifications.
Derived queries are also called query derivation: you express the intent in the repository method name, and Spring Data builds the query for you. The name is made of a verb-like prefix such as find, count, exists, or delete, followed by By, then the filter conditions.
PartTree. That parser breaks the name into parts like And, Or, Between, IgnoreCase, OrderBy, Top, and First.findByEmaiil but the entity field is email, the application usually fails fast with a PropertyReferenceException.List, Optional, boolean, or Page.Use derived queries when the query is short, stable, and maps directly to entity fields. Good examples are lookup-by-email, filter-by-status, existence checks, count queries, and simple ordered lists. If the method starts reading like a paragraph, that is your cue to switch to @Query or Specifications.
| Option | Best for | Trade-off |
|---|---|---|
| Derived query | Simple lookups | Very readable until it gets long |
@Query | Complex joins | You write the query yourself |
| Specifications | Dynamic filters | More code, more flexible |
The parsing cost is tiny and happens at startup or repository creation time; think O(n) in method-name length, usually microseconds per method. After that, the important cost is the database round trip, which is often 1-10 ms on a healthy system, so derived queries are usually not slower than handwritten JPQL in any meaningful way.
Optional<Customer> or a single entity and the database returns two rows, Spring throws IncorrectResultSizeDataAccessException.findTopBy... or findFirstBy... without OrderBy is not deterministic; the database can pick any matching row.IgnoreCase is for string comparisons. It does not magically make every type case-insensitive.Real-World Example: Imagine a checkout service in an e-commerce app. The team uses findByEmail to load the customer account and findTop3ByActiveTrueOrderByCreatedAtDesc to show recent active customers in an admin dashboard. Everything is simple and fast to read, which is exactly why derived queries are popular in production.
Now the bug: an old data migration accidentally created two rows with the same email address. The repository method still compiled and the app still started, but the first request that called findByEmail crashed with IncorrectResultSizeDataAccessException. Users saw 500 errors during login or checkout, and the logs showed a message like query did not return a unique result.
That is the key lesson: derived queries do not replace data rules. If a method is supposed to return one row, the database should also enforce uniqueness with a unique constraint, otherwise Spring can only detect the problem at runtime.
package com.example.demo;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.jpa.repository.JpaRepository;
@SpringBootApplication
public class DerivedQueryMethodsApplication {
public static void main(String[] args) {
SpringApplication.run(DerivedQueryMethodsApplication.class, args);
}
@Bean
CommandLineRunner demo(CustomerRepository repo) {
return args -> {
repo.saveAllAndFlush(List.of(
new Customer("Ava", "Patel", "ava@example.com", true,
LocalDateTime.now().minusDays(2), new Address("Boston", "1 Main St")),
new Customer("Noah", "Patel", "noah@example.com", false,
LocalDateTime.now().minusDays(1), new Address("Boston", "2 Main St")),
new Customer("Mia", "Chen", "mia@example.com", true,
LocalDateTime.now().minusHours(5), new Address("Austin", "3 Main St")),
new Customer("Ivy", "Chen", "dup@example.com", true,
LocalDateTime.now().minusHours(2), new Address("Austin", "4 Main St")),
new Customer("Ethan", "Stone", "dup@example.com", false,
LocalDateTime.now().minusHours(1), new Address("Dallas", "5 Main St"))
));
System.out.println("=== derived queries ===");
System.out.println("Patel customers: " + repo.findByLastNameIgnoreCase("patel"));
System.out.println("Austin customers: " + repo.findByAddressCity("Austin"));
System.out.println("Active Patels: " + repo.findByLastNameAndActiveTrueOrderByCreatedAtDesc("Patel"));
System.out.println("Top active customers: " + repo.findTop3ByActiveTrueOrderByCreatedAtDesc());
System.out.println("Active count: " + repo.countByActiveTrue());
System.out.println("Ava exists? " + repo.existsByEmail("ava@example.com"));
System.out.println("Missing email Optional: " + repo.findByEmail("missing@example.com"));
try {
// This fails because the method expects one row, but the database has two matches.
System.out.println("Duplicate email lookup: " + repo.findByEmail("dup@example.com"));
} catch (IncorrectResultSizeDataAccessException ex) {
String message = ex.getMostSpecificCause() != null ? ex.getMostSpecificCause().getMessage() : ex.getMessage();
System.out.println("Expected failure: " + ex.getClass().getSimpleName());
System.out.println("Message: " + message);
}
};
}
}
@Entity
@Table(name = "customers")
class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private String email;
private boolean active;
private LocalDateTime createdAt;
@Embedded
private Address address;
protected Customer() {
}
Customer(String firstName, String lastName, String email, boolean active, LocalDateTime createdAt, Address address) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.active = active;
this.createdAt = createdAt;
this.address = address;
}
@Override
public String toString() {
return firstName + " " + lastName + " <" + email + "> " + address + " active=" + active;
}
}
@Embeddable
class Address {
private String city;
private String street;
protected Address() {
}
Address(String city, String street) {
this.city = city;
this.street = street;
}
@Override
public String toString() {
return city + ", " + street;
}
}
interface CustomerRepository extends JpaRepository<Customer, Long> {
List<Customer> findByLastNameIgnoreCase(String lastName);
List<Customer> findByAddressCity(String city);
List<Customer> findByLastNameAndActiveTrueOrderByCreatedAtDesc(String lastName);
List<Customer> findTop3ByActiveTrueOrderByCreatedAtDesc();
long countByActiveTrue();
boolean existsByEmail(String email);
Optional<Customer> findByEmail(String email);
}Follow-up & Tricky Questions:
@Query? By default, Spring Data first looks for an explicitly declared query, then a named query, and then it falls back to query derivation. That means an annotated query wins when you need exact control.findByAddressCity for an embedded object or association. Once the path becomes a real join-heavy report, readability usually drops and @Query becomes cleaner.List, Optional, a single entity, Page, Slice, long, and boolean. The return type should match the meaning of the query, not just whatever compiles.OrderBy inside the method name, or pass a Sort / Pageable parameter. If you need user-driven sorting, prefer parameters instead of baking every order into the method name.deleteByStatus are supported, but they are destructive, so you should be careful with transactions and business rules.findTopBy... mean the newest row? Not by itself. Without an explicit OrderBy, the database can return any matching row, so the result is not stable.existsBy just countBy(...) > 0? Conceptually, yes, but existsBy is clearer and may let the provider produce a more efficient query.Common Mistakes:
@Query or Specifications.List or add a database unique constraint.OrderBy or use Sort/Pageable when the row order matters.Memory Hook: Think of the method name like a recipe card: Spring reads the words, turns them into a query, and serves the result. If the recipe card becomes a novel, it is time to stop using it.
Cheat Sheet:
findBy... returns rows that match a property filter.countBy... returns a count.existsBy... returns a yes/no answer.Top and First limit the result size, usually to 1 if no number is written.And, Or, IgnoreCase, and OrderBy are common building blocks.@Query or Specifications when logic gets complex.Practice Tasks:
findByFirstNameStartingWith and print the result for names starting with A.Page<Customer> method and try paging the results.