As a tech lead, when do you push back on derived query methods, and what are the design tradeoffs versus explicit queries?
answer
- derivation = convention, not a query language
- ceiling: no joins/group-by/functions/nested OR
- readability cap ~2-3 predicate parts
- positional args => silent order bugs
- route: derive / @Query / custom fragment / Specifications
basics
~20 sUse 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 sDerived 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
Knows derivation is for simple lookups.
Can list a few things derivation can't do and reach for @Query.
Articulates the full expressiveness ceiling and picks @Query vs fragment deliberately.
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.