What is @ProjectedPayload in Spring for GraphQL, and when would you use it instead of binding an @Argument to a POJO?
answer
- @ProjectedPayload on an INTERFACE
- proxy over raw argument map (Spring Data projections)
- needs spring-data-commons on classpath
- getX -> key x; @Value SpEL to compute/rename
- read-only, nested projections; alternative to POJO binding
basics
~20 s@ProjectedPayload marks an interface used to read GraphQL input arguments. Instead of binding to a concrete class, Spring creates a proxy over the raw argument map, and you access values through getter-style accessor methods on the interface.
solid answer
~50 s@ProjectedPayload is an annotation on an *interface* that lets a controller method receive GraphQL input arguments as an interface-based projection rather than a concrete POJO. Spring (using Spring Data's projection support, so spring-data-commons must be on the classpath) creates a dynamic proxy backed by the raw arguments map. You declare accessor methods like `String getTitle()` and Spring maps them to argument keys; nested input types can be projected via nested @ProjectedPayload interfaces, and @Value with SpEL can compute or rename fields. It's handy when you want a read-only, flexible view over input without a dedicated class, or to expose only a subset of fields, or to project a subset of a large input. Compared to POJO binding — which instantiates and populates a concrete object via constructor/setters — projections avoid boilerplate DTOs and tolerate loosely-structured input.
code
java · 21 lines// Interface projection over the input arguments (no concrete DTO)
@ProjectedPayload
public interface BookProjection {
String getTitle();
AuthorProjection getAuthor(); // nested projection
@Value("#{target.title + ' (' + target.year + ')'}")
String getDisplayLabel(); // computed via SpEL
}
@ProjectedPayload
interface AuthorProjection { String getName(); }
@Controller
class BookController {
@MutationMapping
public Book addBook(BookProjection book) { // proxy over arg map
return service.create(book.getTitle(), book.getAuthor().getName());
}
}
// input BookInput { title: String!, year: Int, author: AuthorInput! }go deeper
Recognize it lets you read input arguments through an interface.
Explain it's an interface proxy over the argument map and contrast with POJO binding.
Detail Spring Data projection backing, nested projections, @Value SpEL, and the spring-data-commons requirement.
Judge when read-only projections beat concrete DTOs, validation tradeoffs, and API-evolution/loose-coupling implications.
When a GraphQL field has arguments (especially an `input` type), Spring for GraphQL can bind them to a Java target. There are two styles: 1. **POJO binding** — `@Argument BookInput input` creates a concrete `BookInput` and populates it via constructor or setters through the `GraphQlArgumentBinder`. 2. **Interface projection** — `@ProjectedPayload` lets you receive an **interface** instead, backed by a proxy over the raw argument map. **How @ProjectedPayload works** - You define an interface annotated `@ProjectedPayload` with accessor (getter) methods: `String getTitle();`, `Integer getPages();`. - Spring uses **Spring Data's projection machinery** (`spring-data-commons` must be on the classpath) to create a dynamic proxy. The proxy resolves each accessor against the underlying arguments `Map` by property name (`getTitle` → key `title`). - Use it as a controller parameter; you may or may not add `@Argument`. Spring recognizes a parameter whose type is a `@ProjectedPayload` interface. **Extra capabilities** - **Nested projections**: an accessor can itself return another `@ProjectedPayload` interface for nested input objects. - **@Value + SpEL**: you can annotate an accessor with `@Value("#{target.firstName + ' ' + target.lastName}")` to compute or rename, mirroring Spring Data's open projections. - **Read-only**: projections are for *reading* input; they don't construct domain objects. **When to use vs POJO** - Use a **projection** when you want a lightweight, read-only, possibly partial view of input arguments without authoring a full DTO/record, or when the input is loosely structured and you only need a few fields. - Use **POJO/record binding** when you want a strongly-typed, validated (`@Valid`) domain/command object you'll pass deeper into the app, or when you need the concrete instance. **Gotchas** - Requires `spring-data-commons` on the classpath; without it the projection support isn't available. - Accessor names must match argument keys (JavaBean `getX` → `x`) or use `@Value` to remap. - Projections are proxies, not real objects — you can't pass them somewhere expecting a concrete type, and identity/equality behave like proxies. - Bean Validation on a projection interface isn't the same as validating a populated POJO; prefer POJO binding when you need `@Valid` constraint checking. **Summary**: `@ProjectedPayload` is a convenience for interface-based, read-only access to GraphQL input arguments, powered by Spring Data projections — a lighter alternative to declaring concrete input classes.
- What dependency must be present for @ProjectedPayload to work?spring-data-commons, because the projection proxies reuse Spring Data's projection factory/machinery.
- When would you prefer a POJO/record over a projection for input?When you want a strongly-typed, concrete command object to pass deeper, or when you need Bean Validation (@Valid) on a populated instance — projections are read-only proxies, not real objects.
saying these in an interview costs you the question
- Saying @ProjectedPayload goes on a class rather than an interface
- Claiming it constructs a real domain object (it's a read-only proxy)
- Forgetting it depends on spring-data-commons
- Thinking accessor names are arbitrary rather than mapped to argument keys