How does the @Query annotation work on a MongoRepository method, and when would you use it over a derived method?
answer
- JSON filter string, ?0/?1 placeholders
- fields=projection, sort, count/exists/delete=true
- SpEL :#{...} for computed values
- bind params — never concat (injection)
- use when derived name too complex
basics
~20 s@Query lets you write the raw MongoDB query as a JSON filter string on a repository method, with ?0, ?1 placeholders for arguments. Use it when the query is too complex for a readable derived method name.
solid answer
~40 s@Query takes a MongoDB query document as a JSON string, e.g. @Query("{ 'lastName' : ?0 }"). Positional parameters bind with ?0, ?1 (or SpEL :#{...}). It gives precise control the method-name parser can't express — nested paths, $and/$or, $regex, operators. Useful attributes: fields (projection: @Query(fields="{ 'lastName':1 }")), sort, count=true for a count query, exists=true, delete=true to run a delete. String arguments interpolated into ?0 are properly escaped by Spring Data, but you must NOT hand-concatenate untrusted input into the JSON — use placeholders. It's still a repository method, so return types (List, Page, Optional, Stream) and a trailing Pageable/Sort work the same. Reach for @Query when the derived name gets unwieldy, you need operators/projections not expressible by keywords, or you want to reuse a native filter.
code
java · 15 linespublic interface UserRepository extends MongoRepository<User, String> {
@Query("{ 'lastName' : ?0, 'age' : { $gt : ?1 } }")
List<User> findAdultsByLastName(String lastName, int minAge);
// projection: only return lastName, drop _id
@Query(value = "{ 'active' : true }", fields = "{ 'lastName' : 1, '_id' : 0 }")
List<User> findActiveNames();
@Query(value = "{ 'lastName' : ?0 }", count = true)
long countByLastName(String lastName);
@Query(value = "{ 'email' : ?0 }", delete = true)
long deleteByEmail(String email);
}go deeper
Know @Query holds a raw JSON filter with ?0 placeholders.
Know fields/sort/count/exists/delete attributes and safe parameter binding.
Discuss injection safety, projection nullability, SpEL binding, and when @Query beats derived vs aggregation.
Weigh @Query vs MongoTemplate for dynamic queries, index/EXPLAIN implications, and projection-to-DTO strategies at scale.
**`@Query`** (`org.springframework.data.mongodb.repository.Query`) annotates a repository method with an explicit MongoDB **query document** expressed as a JSON string. Instead of deriving the query from the method name, Spring Data uses your JSON verbatim (after binding parameters) as the filter passed to the driver. **Parameter binding:** Use **positional placeholders** `?0`, `?1`, … referring to method arguments by index: `@Query("{ 'lastName' : ?0, 'age' : { $gt : ?1 } }")`. You can also use **SpEL expression placeholders** `:#{...}` and `?#{...}` for computed values, and `#{#paramName}` when `@Param` names arguments. A whole-argument placeholder that is a `String` is inserted as a JSON string with proper quoting/escaping by Spring Data's parameter binder — this is why you pass values as parameters instead of concatenating them, which would risk **query/operator injection**. **Key attributes of `@Query`:** - `value` — the filter JSON (the default attribute). - `fields` — a **projection** document choosing which fields to return: `@Query(value="{ 'lastName' : ?0 }", fields="{ 'lastName' : 1, '_id' : 0 }")`. - `sort` — a sort document applied server-side. - `count = true` — execute as a count and return a numeric type. - `exists = true` — return a boolean existence check. - `delete = true` — execute as a delete; return type is the deleted count or the removed documents. - `collation` — locale-aware string matching. **Return types and paging:** all the usual types work (`List`, `Optional`, `Stream`, `Page`, `Slice`). A trailing `Pageable` applies skip/limit/sort; with `Page` a derived count query is run using the same filter. A trailing `Sort` applies ordering. **When to prefer `@Query` over a derived method:** 1. The derived name would be long/unreadable (many conditions). 2. You need operators or shapes the keyword parser can't express cleanly — `$or`/`$and` mixes, `$elemMatch` on arrays, `$exists`, complex `$regex` with options, querying nested paths. 3. You want a **projection** to reduce payload. 4. You want to keep a hand-tuned native filter close to the code. **Gotchas:** - The JSON must be valid Mongo query syntax; a bad document fails at execution. - `?0` inside a value position vs. inside a key matters — you generally bind values, not field names. - Don't build the JSON by string concatenation of user input — always use `?n`/SpEL placeholders so binding escapes it; concatenation invites injection. - Projections returning partial documents can leave non-selected fields null on the mapped entity — often pair with a DTO/interface projection. - `@Query` does not add indexes; you still need appropriate indexes for performance. **Contrast:** derived methods for simple readable finders, `@Query` for native JSON control, `@Aggregation` for multi-stage pipelines, and `MongoTemplate` for fully programmatic/dynamic queries built at runtime.
- Why bind parameters with ?0 instead of concatenating them into the JSON string?Spring Data's parameter binder escapes/quotes bound values, preventing MongoDB operator/query injection. Hand-concatenating untrusted input lets an attacker inject operators like $where or $ne, changing query semantics.
- How do you return only a subset of fields with @Query?Use the fields attribute with a projection document, e.g. fields = "{ 'lastName' : 1, '_id' : 0 }". Often combine with a DTO or interface-based projection return type so unselected fields aren't left null.
saying these in an interview costs you the question
- Concatenating user input into the @Query JSON string
- Thinking @Query field names can be bound with ?0 the same as values
- Believing @Query automatically creates indexes for its filter