Derived Queries & @Query

Reviewed & published by Brayan K

Spring Data turns method names into queries — findByLastName just works. When that is not enough, @Query lets you write explicit JPQL, and a Pageable adds paging and sorting.

Part of the free Java course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

Before You Start

You should have mapped JPA entities and connected them with relationships. Familiarity with the Java Stream API helps read the in-memory illustrations.

What You'll Learn

The Big Idea — Name the Question, Get the Answer

💡 Analogy: A derived query method is like asking a librarian in plain words: "find by last name." You just name what you want and the librarian (Spring Data) writes the catalog search for you. @Query is for when the request is complicated enough that you hand over a written request slip (the JPQL) yourself. And Pageable is saying "give me the third shelf of ten, sorted alphabetically."

You declare intent on an interface; Spring supplies the implementation.

1️⃣ Derived Query Methods

Declare a method whose name describes the query. Spring parses keywords like And, GreaterThan, Containing, and OrderBy to build it.

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.domain.*;
import java.util.*;

// Spring Data parses these METHOD NAMES and generates the queries for you.
public interface CustomerRepository extends JpaRepository<Customer, Long> {

    // WHERE last_name = ?
    List<Customer> findByLastName(String lastName);

    // WHERE first_name = ? AND last_name = ?
    Customer findByFirstNameAndLastName(String first, String last);

    // WHERE age > ?
    List<Customer> findByAgeGreaterThan(int age);

    // WHERE email LIKE ? (containing) ... ORDER BY last_name ASC
    List<Customer> findByEmailContainingOrderByLastNameAsc(String part);

    // null-safe single result
    Optional<Customer> findByEmail(String email);

    // LIMIT 3
    List<Customer> findTop3ByOrderByAgeDesc();

    // paging + sorting
    Page<Customer> findByLastName(String lastName, Pageable pageable);
}
import java.util.*;
import java.util.stream.*;

// PLAIN-JAVA illustration of what "findByAgeGreaterThan + OrderBy" MEANS.
// Spring generates SQL; here we filter and sort an in-memory list the same way.
public class Main {
    record Customer(String name, int age) { }

    public static void main(String[] args) {
        List<Customer> all = List.of(
            new Customer("Ana", 31),
            new Customer("Ben", 22),
            new Customer("Cara", 45),
            new Customer("Dan", 28));

        // findByAgeGreaterThan(25) ORDER BY age ASC
        List<Customer> result = all.stream()
            .filter(c -> c.age() > 25)
            .sorted(Comparator.comparingInt(Customer::age))
            .collect(Collectors.toList());

        result.forEach(c -> System.out.println(c.name() + " (" + c.age() + ")"));
    }
}

2️⃣ Explicit Queries with @Query

When a name would get unwieldy, write the query yourself with @Query. Use JPQL (entity-oriented and portable) by default, bind parameters with @Param, or drop to native SQL with nativeQuery = true.

import org.springframework.data.jpa.repository.*;
import org.springframework.data.repository.query.Param;
import java.util.*;

public interface CustomerRepository extends JpaRepository<Customer, Long> {

    // Explicit JPQL with a NAMED parameter bound via @Param.
    @Query("SELECT c FROM Customer c WHERE c.lastName = :name")
    List<Customer> withLastName(@Param("name") String name);

    // JPQL across an association + ordering.
    @Query("SELECT c FROM Customer c WHERE c.age BETWEEN :lo AND :hi ORDER BY c.age")
    List<Customer> inAgeRange(@Param("lo") int lo, @Param("hi") int hi);

    // A NATIVE SQL query when you need database-specific features.
    @Query(value = "SELECT * FROM customers WHERE email LIKE %:part%", nativeQuery = true)
    List<Customer> emailLike(@Param("part") String part);
}

3️⃣ Paging & Sorting

Add a Pageable parameter and return a Page<T>. The caller builds it with PageRequest.of(page, size, sort), and the result carries total counts. Here is the paging math in plain Java.

import java.util.*;

// PLAIN-JAVA illustration of paging metadata (like Spring's Page<T>).
public class Main {
    public static void main(String[] args) {
        List<String> all = List.of("a","b","c","d","e","f","g");
        int pageSize = 3, pageNumber = 1;       // zero-based: page 1 = second page

        int from = pageNumber * pageSize;
        int to   = Math.min(from + pageSize, all.size());
        List<String> content = all.subList(from, to);

        int totalElements = all.size();
        int totalPages = (int) Math.ceil((double) totalElements / pageSize);

        System.out.println("content: " + content);
        System.out.println("page " + pageNumber + " of " + totalPages);
        System.out.println("totalElements: " + totalElements);
    }
}

🧠 Quick Recall

Predict before you peek.

Answer: WHERE status = ? AND age < ? — And joins the conditions and LessThan maps to <.

Answer: annotate the matching argument with @Param("name").

Answer: paging metadata — total elements, total pages, and the current page — alongside the content slice.

🎯 YOUR TURN — Product derived queries

Add four derived methods to a ProductRepository: by category, below a price, a null-safe by-name, and the five most expensive.

import org.springframework.data.jpa.repository.JpaRepository;
import java.util.*;

// YOUR TURN: add derived query methods to ProductRepository.
//  1) find all products in a given category  -> findByCategory(String c)
//  2) find products priced below an amount    -> findByPriceLessThan(int cents)
//  3) find one product by exact name, null-safe -> Optional<Product> ...
//  4) the 5 most expensive products           -> findTop5ByOrderByPriceDesc()
//
// public interface ProductRepository extends JpaRepository<Product, Long> {
//     ...
// }

🧩 MINI-CHALLENGE — @Query with paging

Write JPQL @Query methods, including one that returns a Page<Product> using a Pageable.

import org.springframework.data.jpa.repository.*;
import org.springframework.data.repository.query.Param;
import org.springframework.data.domain.*;
import java.util.*;

// MINI-CHALLENGE: write @Query methods.
//  1) JPQL: orders placed by a given customer id, newest first.
//       @Query("SELECT o FROM Order o WHERE o.customerId = :cid ORDER BY o.createdAt DESC")
//  2) JPQL with paging: products in a category, returning Page<Product>
//       (add a Pageable parameter; return Page<Product>).
//  3) Use @Param to bind the named parameters.
//
// Think: when is a derived method clearer than @Query, and vice versa?

Common Errors

Pro Tips

📋 Quick Reference

Keyword / ideaMaps toNote
findByXWHERE x = ?Derived query
GreaterThan / LessThanx > ? / x < ?Comparisons
ContainingLIKE %?%Substring match
And / OrAND / ORCombine conditions
OrderByXAscORDER BY x ASCSorting
findTop3 / findFirstLIMIT nLimit rows
@Queryexplicit JPQL/SQLFull control
@Param("n")bind :nNamed parameters
PageableLIMIT/OFFSET + sortReturns Page<T>
Optional<T>single, maybe-emptyNull-safe

📚 Resources / Keep Learning

🎉 Lesson Complete!

You can now let Spring Data generate queries from method names, write explicit JPQL or native SQL with @Query, bind values safely with @Param, and add paging and sorting with Pageable and Page<T>.

Practice quiz

How does Spring Data implement a method named findByLastName(String name)?

  • You must write SQL by hand
  • It parses the method name and generates the query
  • It returns null
  • It throws at runtime

Answer: It parses the method name and generates the query. Spring Data parses derived query method names and generates the query automatically from the property names.

What does findByAgeGreaterThan(int age) translate to?

  • WHERE age = ?
  • WHERE age < ?
  • WHERE age > ?
  • ORDER BY age

Answer: WHERE age > ?. The GreaterThan keyword maps to a > comparison on the age property.

Which keyword combines two conditions in a derived query?

  • AlsoWhere
  • And
  • Plus
  • With

Answer: And. And (and Or) join multiple property conditions, e.g. findByFirstNameAndLastName.

What does the @Query annotation let you provide?

  • A bean name
  • A cache key
  • A validation rule
  • An explicit JPQL or native query

Answer: An explicit JPQL or native query. @Query lets you specify an explicit JPQL (or native) query instead of relying on the method name.

In @Query, how are named parameters bound to method arguments?

  • By position only
  • With @Param matching the :name placeholder
  • Automatically by type
  • They cannot be bound

Answer: With @Param matching the :name placeholder. @Param("name") binds a method argument to the :name placeholder in the JPQL.

What does a derived query method's OrderBy keyword do?

  • Filters rows
  • Limits the count
  • Sorts the result by a property
  • Joins tables

Answer: Sorts the result by a property. OrderBy<Property>Asc/Desc sorts the results by that property without a separate Sort argument.

Which return type makes a single-result derived query null-safe?

  • String
  • Optional<T>
  • int
  • void

Answer: Optional<T>. Returning Optional<T> expresses that a single-result query may find nothing, avoiding null surprises.

What does a Pageable parameter add to a repository method?

  • Caching
  • Transactions
  • Validation
  • Pagination and sorting

Answer: Pagination and sorting. Passing a Pageable enables paging (page number and size) and sorting, returning a Page<T>.

What extra information does a Page<T> provide over a List<T>?

  • Nothing
  • Total elements and total pages metadata
  • The SQL text
  • The entity class

Answer: Total elements and total pages metadata. Page<T> includes total element count, total pages, and current page info in addition to the content.

Which keyword in a derived method limits results to the first match?

  • findTop / findFirst
  • findOnly
  • findLimit
  • findSingle

Answer: findTop / findFirst. findFirst and findTop (e.g. findTop3By...) limit the number of returned rows.

Continue this course

Frequently asked questions

How do derived query methods work?

Spring Data parses the method name into a query at startup. A name like findByLastNameAndAgeGreaterThan is split into the action (find), the properties (lastName, age), and keywords (And, GreaterThan), which Spring turns into a JPQL/SQL query against the entity. You never write the query body — just declare the method on an interface extending JpaRepository, and Spring provides the implementation. This is ideal for straightforward lookups where the method name stays readable.

When should I use @Query instead of a derived method?

Use @Query when the query is too complex to express clearly in a method name — joins across associations, custom projections, aggregate functions, conditional logic, or database-specific SQL. A method called findByFirstNameAndLastNameAndAgeBetweenAndStatusOrderByCreatedAtDesc is a sign you have outgrown derived queries. @Query lets you write explicit JPQL (portable, entity-oriented) or, with nativeQuery = true, raw SQL. It keeps the repository readable while giving you full control over the query.

What is the difference between JPQL and native SQL in @Query?

JPQL (the default for @Query) queries entities and their fields rather than tables and columns, so it is database-portable and refactor-friendly — SELECT c FROM Customer c references the entity, not a table. Native SQL (nativeQuery = true) is plain SQL against actual tables and columns, which you use when you need database-specific features, vendor functions, or performance tuning JPQL cannot express. The trade-off is that native queries tie you to a particular database dialect.

How do @Param and named parameters work?

In a JPQL string you reference parameters by name with a colon, like :name. You then bind each one to a method argument by annotating that argument with @Param("name"). The names must match. Named parameters are clearer and safer than positional ?1, ?2 binding, especially as queries grow, and they always use parameter binding under the hood — never string concatenation — which protects against SQL injection.

How does paging and sorting work with Pageable?

Add a Pageable parameter to a repository method and return a Page<T>. The caller builds a Pageable with PageRequest.of(pageNumber, pageSize, sort), and Spring applies the limit, offset, and ORDER BY automatically. The returned Page<T> carries not just the slice of content but also metadata: total element count, total number of pages, and whether more pages exist. If you only need the slice without the count query, you can return a Slice<T> instead, which is cheaper.

Related lessons