What does @SchemaMapping do for nested/field-level resolution, and how does the parent (source) object get injected?
answer
- @SchemaMapping = field-level DataFetcher
- typeName from source param, field from method name
- parent object injected as argument
- list field resolver -> N+1
- @BatchMapping + DataLoader -> one call, ordered/Map
basics
~20 s@SchemaMapping resolves a field on a non-root type — for example the author field of a Book. Spring injects the parent object (the Book) as a method parameter so you can compute the child value.
solid answer
~40 s@SchemaMapping resolves a specific field on any object type, typically a nested/association field like Book.author. You declare `@SchemaMapping(typeName="Book", field="author")` — but both can be inferred: typeName from the source/parent parameter's Java type, and field from the method name. Spring injects the parent object (the source) as a method parameter (the object returned by the parent field's resolver). This is how you lazily resolve associations only when the client selects them. The pitfall is the N+1 problem: for a list of N books, an author field resolver runs N times. The fix is @BatchMapping (backed by a DataLoader), which loads all authors in one call and returns a Map<Book, Author> or Flux/Mono. @QueryMapping/@MutationMapping/@SubscriptionMapping are just @SchemaMapping with the typeName fixed to a root type.
code
java · 22 lines@Controller
public class BookController {
@QueryMapping
public List<Book> books() { return bookService.findAll(); }
// Field resolver: typeName inferred from Book param, field from method name
// Runs ONCE PER book -> N+1 risk
@SchemaMapping
public Author author(Book book) {
return authorService.findById(book.authorId());
}
}
// Batched fix: invoked ONCE with all books in a dispatch
@Controller
class BatchedBookController {
@BatchMapping // typeName Book, field author (both inferred)
public Map<Book, Author> author(List<Book> books) {
return authorService.findByBooks(books); // single round-trip
}
}go deeper
Know it resolves a field on a non-root type and receives the parent object.
Explain typeName/field inference, lazy resolution, and recognize the N+1 problem with the @BatchMapping fix.
Detail DataLoader mechanics, Map vs ordered-Flux returns, and when a getter suffices vs a resolver.
Reason about batching strategy, per-request DataLoader scoping, and the schema-design tradeoffs of expensive nested fields.
GraphQL resolves a query field-by-field. When a field returns an object type, that object's own fields each get resolved by a `DataFetcher`. `@SchemaMapping` is how you supply a field-level `DataFetcher` in an annotated controller. **Declaration & inference** `@SchemaMapping(typeName = "Book", field = "author")` binds the method to the `author` field of the `Book` type. Both attributes are optional: - **field** defaults to the method name. - **typeName** is inferred from the **source parameter** — the first non-annotated parameter whose type is the parent object. So a method `Author author(Book book)` binds to `Book.author` with no attributes at all. **The source (parent) object** During execution, the parent field's resolver produces an object (e.g. `bookById` returns a `Book`). When GraphQL then needs `Book.author`, Spring calls your `@SchemaMapping` method and passes that `Book` instance as the source argument. You use it to fetch/compute the child. This enables **lazy resolution**: `author` is only fetched when the client actually selects it in the query. **Why not just a getter?** If `Book` already has a populated `author`, graphql-java's default `PropertyDataFetcher` returns it — no resolver needed. You add `@SchemaMapping` when the association is expensive, remote, or must be loaded on demand (e.g. author lives in another service/table). **The N+1 problem**: for a query returning N books each selecting `author`, the field resolver fires N times → N author lookups. This is the classic GraphQL N+1. **The fix — @BatchMapping / DataLoader**: `@BatchMapping` declares a batched field resolver. Spring registers a `DataLoader` and collects all parent objects for one execution, invoking your method **once** with the full `List<Book>` (or `Collection`); you return a `Map<Book, Author>` (or `Flux<Author>` in parent order, or `Mono<Map<...>>`). graphql-java's DataLoader machinery batches the keys within a single dispatch, collapsing N calls into 1. ```java @BatchMapping public Map<Book, Author> author(List<Book> books) { return authorService.findByBooks(books); // one query } ``` **Injectable parameters for @SchemaMapping** are the same set as other handlers: the source object, `@Argument`, `DataFetchingEnvironment`, `@ContextValue`, a `DataLoader<K,V>` (to batch manually), `GraphQLContext`, `Principal`. **Gotchas** - If typeName can't be inferred (no source parameter of the parent type), you must set it explicitly. - `@BatchMapping` return ordering: when returning a `Flux`/`List` (not a Map), the results must be in the **same order** as the input parents. - A field resolver's method name must match the field (or override), else it won't bind. - Field resolvers can also take `@Argument` — GraphQL fields on nested types can have arguments too. **When to use**: `@SchemaMapping` for on-demand/expensive nested fields; upgrade to `@BatchMapping` the moment a resolver can run inside a list to avoid N+1.
- How does @BatchMapping actually collapse N calls into one?It registers a DataLoader keyed by parent. graphql-java queues the keys during the execution and dispatches them together, invoking your batch method once with all parents; you return a Map<Parent,Value> or ordered Flux/List.
- If you return a Flux/List from @BatchMapping instead of a Map, what constraint applies?The returned values must be in the same order as the input parent list, so Spring can pair each result to its parent by index.
saying these in an interview costs you the question
- Believing @SchemaMapping avoids N+1 by itself (it doesn't; @BatchMapping/DataLoader does)
- Thinking you must always write a field resolver even when the parent object already has the property populated
- Returning an unordered list from @BatchMapping and expecting correct pairing