Database · Lesson 57 of 95
Derived Query Methods
Derived query methods in Spring Data JPA: write findBy, And, Between, OrderBy and Top method names and get queries with no SQL, using a bookshop example.
Picture a bookshop assistant. A customer says, "Show me cooking books under 400 rupees, cheapest first." The assistant does not need a long form. The sentence itself is enough, and she walks to the right shelf. Derived query methods work the same way. You write a method name that reads like a sentence, and Spring Data JPA turns the name into a database query for you.
In this topic you will learn how to name methods so that Spring understands them, which keywords you can use, and where the limits are. We will build a small bookshop search that uses eight different methods and no SQL at all.
What are derived query methods?
A derived query method is a method you declare in a repository interface. Spring Data reads its name, splits it into parts, and builds the query at startup. You never write a method body.
The name follows a pattern: a start word, then the property names of your entity, joined by keywords.
javaList<Book> findByAuthor(String author); List<Book> findByPriceLessThan(int price);
The first method means "find books where author equals the argument". The second means "find books where price is less than the argument". The property names must match the fields in your entity, with a capital letter at the start of each word.
Why are they used?
Most searches in an app are simple: find by email, find by status, find the newest five. Writing SQL or JPQL for each one is slow and easy to get wrong. A derived method takes a single line, and it is checked when the app starts. If you misspell a field, the app refuses to start, so you find the mistake at once and not in front of a customer.
They also keep the code readable. A method name that spells out the genre and the stock flag tells any teammate exactly what it does. When a search grows too complex for a name, you switch to a written query, which is the next topic.
How it works
Spring Data splits the name and builds a query from the parts.
textfindByGenreAndPriceLessThan | | | | | +-- operator | +-- property (genre) +-- start word: find
The start word findBy says "select rows". Then come the property names and keywords. And joins two conditions, and LessThan is an operator applied to the property before it. Spring Data turns this into a where genre = ? and price < ? query and runs it through Hibernate.
Here is the journey from your call to the result.
textCall: findByGenre("COOKING") | v Spring parses the method name | v Builds query: where genre = ? | v Hibernate runs the SQL | v Rows become a List of entities
The parsing happens once at startup, so each call afterwards is fast. If a name cannot be matched to your entity, Spring stops the app with a clear error.
Real-Life Example
Think about how you search on a shopping app. You tick "Cooking", set a price limit and choose "cheapest first". You never write a query. You pick from a menu of words: category, less than, sort by. Derived query methods are that menu for developers. The keywords And, LessThan and OrderBy are the tick boxes. The app turns your choices into the real database search behind the scenes.
Code Example
Let's build the search for a shop called Turning Pages. The Book entity has a title, an author, a genre, a price and an in-stock flag. The repository has eight derived methods.
textbookshop/ ├─ pom.xml └─ src/main/ ├─ java/com/turningpages/shop/ │ ├─ ShopApplication.java │ ├─ Book.java │ ├─ BookRepository.java │ └─ SearchRunner.java └─ resources/ └─ application.properties
File: pom.xml
xml<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>4.1.1</version> <relativePath/> </parent> <groupId>com.turningpages</groupId> <artifactId>shop</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <java.version>21</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
File: ShopApplication.java in package com.turningpages.shop
javapackage com.turningpages.shop; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ShopApplication { public static void main(String[] args) { SpringApplication.run(ShopApplication.class, args); } }
File: Book.java in package com.turningpages.shop
javapackage com.turningpages.shop; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @Entity public class Book { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String title; private String author; private String genre; private int price; private boolean inStock; protected Book() { } public Book(String title, String author, String genre, int price, boolean inStock) { this.title = title; this.author = author; this.genre = genre; this.price = price; this.inStock = inStock; } public String getTitle() { return title; } @Override public String toString() { return title + " (Rs" + price + ")"; } }
File: BookRepository.java in package com.turningpages.shop
javapackage com.turningpages.shop; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; public interface BookRepository extends JpaRepository<Book, Long> { List<Book> findByAuthor(String author); List<Book> findByPriceLessThan(int price); List<Book> findByPriceBetween(int low, int high); List<Book> findByGenreAndInStockTrue(String genre); List<Book> findByTitleContainingIgnoreCase(String text); List<Book> findByGenreOrderByPriceAsc(String genre); List<Book> findTop2ByOrderByPriceDesc(); long countByGenre(String genre); boolean existsByTitle(String title); }
File: SearchRunner.java in package com.turningpages.shop
javapackage com.turningpages.shop; import java.util.List; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component public class SearchRunner implements CommandLineRunner { private final BookRepository repository; public SearchRunner(BookRepository repository) { this.repository = repository; } @Override public void run(String... args) { repository.saveAll(List.of( new Book("River of Stories", "Anita Rao", "FICTION", 350, true), new Book("Coastal Winds", "Dev Menon", "FICTION", 275, false), new Book("Kitchen Basics", "Meera Iyer", "COOKING", 420, true), new Book("Night Train to Pune", "Anita Rao", "FICTION", 199, true), new Book("Spice Trails", "Meera Iyer", "COOKING", 310, true), new Book("Garden Notes", "Dev Menon", "GARDENING", 150, true))); show("By author", repository.findByAuthor("Anita Rao")); show("Under 200", repository.findByPriceLessThan(200)); show("Between 250 and 350", repository.findByPriceBetween(250, 350)); show("Fiction in stock", repository.findByGenreAndInStockTrue("FICTION")); show("Title has 'TRA'", repository.findByTitleContainingIgnoreCase("TRA")); show("Cooking, cheapest first", repository.findByGenreOrderByPriceAsc("COOKING")); show("Top 2 by price", repository.findTop2ByOrderByPriceDesc()); System.out.println("Fiction count: " + repository.countByGenre("FICTION")); System.out.println("Has Spice Trails? " + repository.existsByTitle("Spice Trails")); } private void show(String label, List<Book> books) { System.out.println(label + ":"); books.forEach(b -> System.out.println(" " + b.getTitle())); } }
File: application.properties in src/main/resources
propertiesspring.application.name=bookshop
Run it with mvn spring-boot:run.
Output:
textBy author: River of Stories Night Train to Pune Under 200: Night Train to Pune Garden Notes Between 250 and 350: River of Stories Coastal Winds Spice Trails Fiction in stock: River of Stories Night Train to Pune Title has 'TRA': Night Train to Pune Spice Trails Cooking, cheapest first: Spice Trails Kitchen Basics Top 2 by price: Kitchen Basics River of Stories Fiction count: 3 Has Spice Trails? true
Code Explained
findByAuthorcomparesauthorwith the argument, so it finds all books by that writer.findByPriceLessThanbuildsprice < ?, andfindByPriceBetweentakes two arguments for the low and high limits, both included.findByGenreAndInStockTruejoins two conditions withAnd. TheTruekeyword tests a boolean field, so it needs no argument.- The title search uses
ContainingandIgnoreCase, so it matches part of the title and ignores capital letters. It wraps the text as%text%for you. OrderByPriceAscsorts the result. WriteDescfor the other direction.findTop2By...limits the result to the first two rows. Here it gives the two most expensive books.countByGenrereturns a number, andexistsByTitlereturns a boolean. Neither loads any book objects.- The method arguments follow the same order as the words in the name.
Common Keywords
| Keyword | Example | Meaning |
|---|---|---|
And, Or | findByGenreAndAuthor | Combine conditions |
LessThan, GreaterThan | findByPriceLessThan | Compare numbers |
Between | findByPriceBetween | Value inside a range |
Containing, StartingWith | findByTitleContaining | Match part of text |
IgnoreCase | findByGenreIgnoreCase | Ignore capital letters |
OrderBy...Asc/Desc | findByGenreOrderByPriceAsc | Sort the result |
Top, First | findTop3By... | Limit the row count |
In | findByGenreIn | Match any value in a list |
Common Mistakes
- Using column names in the method. Write
findByInStock, notfindByIn_stock. Use the Java field names of the entity. - Wrong number of arguments.
findByPriceBetweenneeds two values. Each condition needs the matching number of arguments, in order. - Returning the wrong type. A search that can return many rows should return a
List. If it returns a single object and many rows match, Spring throws an error. - Very long names. They are hard to read and easy to break. Switch to a written query when the name gets long.
Interview Questions
What is a derived query method?
Ans:It is a repository method whose query Spring Data builds from its name, such as findByAuthor. You write no SQL and no method body.
When does Spring check the method names?
Ans:At application startup. A name that does not match an entity property makes startup fail, so mistakes show up early.
What are the limits of derived queries?
Ans:Long names become unreadable, and complex joins, groupings and custom SQL functions do not fit a name. For those, use a @Query written in JPQL or native SQL.
Key Points to Remember
- Spring Data builds the query from the repository method name.
- The pattern is a start word such as
findBy, then property names joined by keywords such asAndorLessThan. - Property names must match the entity field names, with capital letters between words.
- Misspelled names stop the app at startup, which helps you find errors early.
- For complex searches, use a written query instead of a long name.
Frequently Asked Questions
How do derived query methods find rows without SQL?
Spring Data splits the method name into parts and builds a query from them at startup. Hibernate then turns that query into SQL for your database.
Can I search by a field in a related entity?
Yes. You can write findByAuthorName to reach into a related object. Spring walks the property path, and if the name is unclear, use an underscore such as findByAuthor_Name.
Do derived query methods work with sorting and paging?
Yes. Add a Sort or a Pageable as the last argument, and the result is sorted or paged. Use Page<Book> as the return type for paging.
Should I test derived query methods?
Yes. A small test with test data confirms that your method name means what you think, especially for And and Or combinations.
Related Topics
- JpaRepository: the base methods every repository already has.
- Query with JPQL and Native SQL: write your own query when a name is not enough.
- Pagination and Sorting: return searches page by page.
- Data JPA Test: test your repository methods quickly.
Practice Problems
Try each problem on your own first. Each project has its own pom.xml, shown below.
Easy: Gym Member Search
CityFit Gym stores members with a name and a plan (MONTHLY or YEARLY). Add three derived methods to the repository: one that finds members by plan, one that counts members on a plan, and one that finds members whose name starts with a given text. Save five members and print the results of all three.
Use these members: Aarav (MONTHLY), Anaya (YEARLY), Kabir (MONTHLY), Aditi (YEARLY), Meera (MONTHLY).
Show answerHide answer
StartingWith adds a like 'A%' search, so findByNameStartingWith("A") returns Aarav, Anaya and Aditi.File: pom.xml
xml<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>4.1.1</version> <relativePath/> </parent> <groupId>com.cityfit</groupId> <artifactId>gym</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <java.version>21</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
File: GymApplication.java in package com.cityfit.gym
javapackage com.cityfit.gym; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class GymApplication { public static void main(String[] args) { SpringApplication.run(GymApplication.class, args); } }
File: Member.java in package com.cityfit.gym
javapackage com.cityfit.gym; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @Entity public class Member { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String plan; protected Member() { } public Member(String name, String plan) { this.name = name; this.plan = plan; } public String getName() { return name; } }
File: MemberRepository.java in package com.cityfit.gym
javapackage com.cityfit.gym; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; public interface MemberRepository extends JpaRepository<Member, Long> { List<Member> findByPlan(String plan); long countByPlan(String plan); List<Member> findByNameStartingWith(String prefix); }
File: MemberRunner.java in package com.cityfit.gym
javapackage com.cityfit.gym; import java.util.List; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component public class MemberRunner implements CommandLineRunner { private final MemberRepository repository; public MemberRunner(MemberRepository repository) { this.repository = repository; } @Override public void run(String... args) { repository.saveAll(List.of( new Member("Aarav", "MONTHLY"), new Member("Anaya", "YEARLY"), new Member("Kabir", "MONTHLY"), new Member("Aditi", "YEARLY"), new Member("Meera", "MONTHLY"))); System.out.println("Monthly members:"); repository.findByPlan("MONTHLY").forEach(m -> System.out.println(" " + m.getName())); System.out.println("Yearly count: " + repository.countByPlan("YEARLY")); System.out.println("Names starting with A:"); repository.findByNameStartingWith("A").forEach(m -> System.out.println(" " + m.getName())); } }
The output is:
textMonthly members: Aarav Kabir Meera Yearly count: 2 Names starting with A: Aarav Anaya Aditi
Medium: Car Rental Filters
DriveEasy rents cars in several cities. Each car has a model, a city, a daily rate in rupees and an available flag. Write four derived methods:
- Available cars in a city, cheapest first.
- Cars with a daily rate between two values.
- The single most expensive car in a city (as an
Optional). - Cars in any of several cities.
Save six cars and print the model names returned by each method.
Use these cars: Swift (Pune, 1200, available), City (Pune, 1800, available), Creta (Pune, 2500, not available), Innova (Mumbai, 3000, available), Polo (Mumbai, 1500, available), Nexon (Goa, 2200, available).
Show answerHide answer
findTop... returns one row, so Optional<Car> is the right return type.File: pom.xml
xml<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>4.1.1</version> <relativePath/> </parent> <groupId>com.driveeasy</groupId> <artifactId>rental</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <java.version>21</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
File: RentalApplication.java in package com.driveeasy.rental
javapackage com.driveeasy.rental; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class RentalApplication { public static void main(String[] args) { SpringApplication.run(RentalApplication.class, args); } }
File: Car.java in package com.driveeasy.rental
javapackage com.driveeasy.rental; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @Entity public class Car { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String model; private String city; private int dailyRate; private boolean available; protected Car() { } public Car(String model, String city, int dailyRate, boolean available) { this.model = model; this.city = city; this.dailyRate = dailyRate; this.available = available; } public String getModel() { return model; } }
File: CarRepository.java in package com.driveeasy.rental
javapackage com.driveeasy.rental; import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; public interface CarRepository extends JpaRepository<Car, Long> { List<Car> findByCityAndAvailableTrueOrderByDailyRateAsc(String city); List<Car> findByDailyRateBetween(int low, int high); Optional<Car> findTopByCityOrderByDailyRateDesc(String city); List<Car> findByCityIn(List<String> cities); }
File: CarRunner.java in package com.driveeasy.rental
javapackage com.driveeasy.rental; import java.util.List; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component public class CarRunner implements CommandLineRunner { private final CarRepository repository; public CarRunner(CarRepository repository) { this.repository = repository; } @Override public void run(String... args) { repository.saveAll(List.of( new Car("Swift", "Pune", 1200, true), new Car("City", "Pune", 1800, true), new Car("Creta", "Pune", 2500, false), new Car("Innova", "Mumbai", 3000, true), new Car("Polo", "Mumbai", 1500, true), new Car("Nexon", "Goa", 2200, true))); print("Pune, available, cheapest first", repository.findByCityAndAvailableTrueOrderByDailyRateAsc("Pune")); print("Rate 1500 to 2200", repository.findByDailyRateBetween(1500, 2200)); System.out.println("Priciest in Mumbai: " + repository.findTopByCityOrderByDailyRateDesc("Mumbai").orElseThrow().getModel()); print("Goa or Mumbai", repository.findByCityIn(List.of("Goa", "Mumbai"))); } private void print(String label, List<Car> cars) { System.out.println(label + ":"); cars.forEach(c -> System.out.println(" " + c.getModel())); } }
The output is:
textPune, available, cheapest first: Swift City Rate 1500 to 2200: City Polo Nexon Priciest in Mumbai: Innova Goa or Mumbai: Innova Polo Nexon