skip to content

As a tech lead, when do you push back on derived query methods, and what are the design tradeoffs versus explicit queries?

level: principalimportance: nice to knowfreq 20%

answer

  1. derivation = convention, not a query language
  2. ceiling: no joins/group-by/functions/nested OR
  3. readability cap ~2-3 predicate parts
  4. positional args => silent order bugs
  5. route: derive / @Query / custom fragment / Specifications

basics

~20 s

Use derivation for simple, readable lookups. Push back when method names get long or need joins, grouping, functions, or complex OR logic — those belong in an explicit @Query or a custom fragment, which are clearer and testable.

solid answer

~40 s

Derived methods are a convenience layer, not a query language. Their strengths: zero boilerplate, fail-fast property validation at startup, and store-agnostic consistency via PartTree. Their limits: the keyword grammar can't express joins/fetches, GROUP BY/HAVING, DB functions, subqueries, or nested boolean grouping; and Or only splits top-level, so anything but flat AND-of-ORs is impossible. As a lead I set a readability ceiling — roughly two or three predicate parts. Beyond that, the name stops communicating intent and becomes error-prone (silent argument-order bugs, brittle refactors when properties rename). I move those to @Query (JPQL/native) for declarative complex reads, or to a custom repository fragment (interface + Impl) for imperative/Criteria/dynamic queries. The default CREATE_IF_NOT_FOUND lets both coexist so we never rewrite the whole repository — we just annotate the outliers.

go deeper

for a junior

Knows derivation is for simple lookups.

for a middle

Can list a few things derivation can't do and reach for @Query.

for a senior

Articulates the full expressiveness ceiling and picks @Query vs fragment deliberately.

for a principal

Sets a team-wide routing/readability policy and governs it in review, leveraging CREATE_IF_NOT_FOUND for low-cost promotion.

**Frame derivation correctly.** Query derivation is a *naming convention that generates a query*, backed by the finite `PartTree` grammar in Spring Data Commons. It is deliberately narrow. A senior/lead's job is to know its ceiling and route work accordingly. **What derivation buys you.** - **No boilerplate**: declare a name, get a query. - **Fail-fast safety**: an unresolvable property is a `PropertyReferenceException` at bootstrap, so typos and renames surface immediately, and IDEs/refactors can often track properties. - **Consistency**: the grammar is identical across stores because it lives in Commons; the same mental model applies to JPA, Mongo, etc. - **Readability for simple cases**: `findByEmail`, `existsByUsername`, `deleteByExpiryBefore` read like English. **Where it breaks down.** - **Expressiveness ceiling**: no joins/`JOIN FETCH`, no `GROUP BY`/`HAVING`, no aggregate/DB functions, no subqueries, no computed projections beyond the built-ins. Boolean logic is flat: `Or` only splits at the top level, so `A AND (B OR C)` is impossible. - **Name explosion**: `findByStatusAndTypeAndCreatedAtBetweenAndOwnerIdOrderByCreatedAtDesc` is technically valid but unreadable and fragile. - **Silent argument-order bugs**: parameters bind positionally; two same-typed params in the wrong order compile and run but return wrong data. - **Refactor coupling**: the query is encoded in the method name, so a property rename that a refactor misses becomes a startup failure (better than silent, but still coupling name to schema). - **Overfetching / N+1**: derived finders can't express fetch strategy, encouraging lazy-loading surprises for complex aggregates. **The routing policy I set.** 1. **Derive** simple equality/range/`In`/null/`Like` lookups with at most ~2-3 predicate parts and a static sort. 2. **`@Query` (JPQL, or native when needed)** for declarative complex reads — joins, aggregation, subqueries — keeping the method name meaningful; under the default `CREATE_IF_NOT_FOUND`, the annotation wins, so no repository restructuring is needed. 3. **Custom repository fragment** (`interface Frag` + `FragImpl`, composed into the repository) for imperative, dynamic, or Criteria/QueryDSL/`Specification`-driven queries where even `@Query` strings are too rigid. 4. **Specifications / QueryDSL** when filters are dynamic/combinatorial — derivation is the wrong tool for optional criteria. **Testing & governance.** Complex `@Query` strings deserve integration tests (they aren't validated as deeply as derivation at startup for native SQL). I also treat a growing derived-name as a code smell in review — it usually signals a missing repository fragment or a query that should be declarative. **Bottom line.** Derivation optimizes for the 80% of trivial lookups; explicit queries and fragments own the 20% that carry real complexity. The value of `CREATE_IF_NOT_FOUND` is that the two live side by side in one interface, so the migration cost of promoting a method from derived to declared is a single annotation.

  • A team keeps growing a derived method to six predicate parts. What review guidance do you give?
    Treat it as a smell: the name no longer communicates intent and positional binding is bug-prone. Promote it to an @Query with a descriptive method name, or to a Specification/custom fragment if the filters are dynamic — and add an integration test.
  • Why might derived finders encourage N+1 problems for complex aggregates?
    Derived queries can't express fetch strategy (no JOIN FETCH), so associations load lazily per element. For aggregates you want an explicit @Query with a fetch join or an entity graph.

saying these in an interview costs you the question

  • Treating derived queries as a full replacement for JPQL/SQL.
  • Defending very long derived names as 'self-documenting'.
  • Claiming derivation can do joins or GROUP BY.
  • Not knowing custom fragments exist for imperative/dynamic queries.

context