skip to content

Across derived queries, @Query, @Aggregation, and MongoTemplate — how do you choose the right query mechanism, and what are the tradeoffs?

level: principalimportance: should knowfreq 45%

answer

  1. ladder: derived -> @Query -> @Aggregation -> MongoTemplate
  2. static vs dynamic is the key axis
  3. indexes + explain() decide perf, not the API
  4. fragment Impl for custom MongoTemplate logic
  5. bind params always; Page counts, Slice doesn't

basics

~10 s

Use derived methods for simple readable finders, @Query for native JSON filters/projections too complex for a name, @Aggregation for grouping and multi-stage pipelines, and MongoTemplate when the query must be built dynamically at runtime.

solid answer

~40 s

Pick by complexity and dynamism. Derived query methods (findByX) win for simple, self-documenting finders — but the name gets unreadable past a few conditions and every field typo is a bootstrap failure. @Query drops to native MongoDB JSON: precise operators, projections (fields), count/exists/delete flags — declarative but static strings, bind params to avoid injection. @Aggregation runs pipelines ($match/$group/$lookup) mapping each output doc to a DTO — great for rollups and joins, but the stages are fixed at compile time. When stages or filters vary at runtime, or you need type-safe construction and options like allowDiskUse, use MongoTemplate's Criteria/Aggregation builders. Cross-cutting: all should be backed by proper indexes; verify with explain(). Choose the simplest mechanism that expresses the query, and escalate only when the current tier can't express it cleanly.

code

java · 20 lines
java
// Custom fragment for a DYNAMIC query the derived/@Query tiers can't express
interface OrderRepositoryCustom {
    List<Order> search(OrderFilter f);
}

class OrderRepositoryCustomImpl implements OrderRepositoryCustom {
    private final MongoTemplate template;
    OrderRepositoryCustomImpl(MongoTemplate template) { this.template = template; }

    public List<Order> search(OrderFilter f) {
        Criteria c = new Criteria();
        if (f.status() != null) c.and("status").is(f.status());
        if (f.minAmount() != null) c.and("amount").gte(f.minAmount());
        return template.find(new Query(c), Order.class);
    }
}

// Callers see ONE repository combining both
interface OrderRepository
        extends MongoRepository<Order, String>, OrderRepositoryCustom { }

go deeper

for a junior

Know the four options exist and derived methods are the simplest.

for a middle

Match each mechanism to a use case and know @Query/@Aggregation are static strings.

for a senior

Explain the static-vs-dynamic axis, fragment composition, and projection/paging costs.

for a principal

Drive decisions from index/explain() analysis, escalation heuristics, injection safety, and when to pre-aggregate vs run live pipelines at scale.

Spring Data MongoDB offers a **ladder of query mechanisms**; the design skill is choosing the lowest rung that expresses the need cleanly. **1. Derived query methods** (`findByLastNameAndAgeGreaterThan`). Pros: zero implementation, self-documenting, compile-time-ish safety (property paths validated at **startup** — a typo fails fast). Cons: names balloon with condition count, can't express `$or` mixes / `$elemMatch` / computed conditions cleanly, and keyword semantics (e.g. `Like`→regex) can surprise. **Use for** simple finders and existence/count/delete-by. **2. `@Query`** — native MongoDB **JSON filter** on the method. Pros: full operator control, **projections** (`fields`), `sort`, `count`/`exists`/`delete` flags, collation. Positional `?0`/SpEL binding keeps it injection-safe. Cons: strings are opaque to the compiler (bad JSON fails at execution), field names aren't refactor-safe, and the filter is **static**. **Use for** complex filters/projections that a method name can't express readably. **3. `@Aggregation`** — a **pipeline** (`pipeline = {stages...}`). Pros: grouping, computed fields, `$lookup` joins, faceting; each output doc maps to a **DTO/projection**; trailing `Sort`/`Pageable` append stages. Cons: pipeline is a **fixed compile-time array** — no conditional stages; output shape must match the return type. **Use for** analytics, rollups, joins, top-N. **4. `MongoTemplate`** — the imperative API with **`Criteria`** and the typed **`Aggregation`** builder (`newAggregation(match(...), group(...))`). Pros: fully **dynamic** — build filters/stages from runtime conditions, type-safe stage construction, access to options (`allowDiskUse`, read preference, bulk ops, `findAndModify`). Cons: more code, less declarative, lives in a repository fragment or service. **Use for** dynamic queries, complex conditional logic, and operations outside the repository abstraction. **Composition:** custom logic integrates via **repository fragments** — declare a custom interface + `Impl` using `MongoTemplate`, and have your `MongoRepository` extend it, so callers see one repository. **Cross-cutting concerns (the principal lens):** - **Indexes decide performance**, not the mechanism — every filter/`$match`/`$near`/`$sort` should be index-backed; validate with `explain()` and watch `COLLSCAN`. - **Injection**: always bind parameters (`?0`/Criteria), never concatenate untrusted input into JSON. - **Projection & payload**: prefer `fields`/DTO projections to cut over-fetching; be aware unselected fields are null on the mapped entity. - **Paging cost**: `Page` runs a count query; `Slice` doesn't. Large aggregations may need `allowDiskUse`. - **Refactor safety**: derived/Criteria track property renames better than raw JSON strings. - **Consistency & scale**: consider read preferences, `$lookup` cost, and whether a materialized/pre-aggregated collection beats an expensive live pipeline. **Decision heuristic:** simplest that works → derived; need native operators/projection → `@Query`; multi-stage transform/rollup → `@Aggregation`; anything **dynamic** or needing engine options → `MongoTemplate`. Escalate one rung only when the current one can't express the query cleanly.

  • How do you add fully dynamic query logic while keeping a single repository interface for callers?
    Use a repository fragment: declare a custom interface (e.g. OrderRepositoryCustom) with an Impl that uses MongoTemplate/Criteria, then have MongoRepository extend both. Spring Data merges the proxy and the fragment so callers see one repository.
  • Two implementations produce identical results; how do you decide which query is actually better?
    Check index usage and cost with explain() — look for COLLSCAN vs IXSCAN, documents examined vs returned, in-memory sorts. The mechanism matters less than whether the filter/sort hits an index; pick the one with the better execution plan.

saying these in an interview costs you the question

  • Claiming @Aggregation can add stages conditionally at runtime
  • Treating the query API choice as the performance driver instead of indexing
  • Reaching for MongoTemplate for simple static finders that a derived method expresses clearly

context