[[controllers]] = Annotated Controllers Spring for GraphQL provides an annotation-based programming model where `@Controller` components use annotations to declare handler methods with flexible method signatures to fetch the data for specific GraphQL fields. For example: [source,java,indent=0,subs="verbatim,quotes"] ---- @Controller public class GreetingController { @QueryMapping // <1> public String hello() { // <2> return "Hello, world!"; } } ---- <1> Bind this method to a query, i.e. a field under the Query type. <2> Determine the query from the method name if not declared on the annotation. Spring for GraphQL uses `RuntimeWiring.Builder` to register the above handler method as a `graphql.schema.DataFetcher` for the query named "hello". [[controllers-declaration]] == Declaration You can define `@Controller` beans as standard Spring bean definitions. The `@Controller` stereotype allows for auto-detection, aligned with Spring general support for detecting `@Controller` and `@Component` classes on the classpath and auto-registering bean definitions for them. It also acts as a stereotype for the annotated class, indicating its role as a data fetching component in a GraphQL application. `AnnotatedControllerConfigurer` detects `@Controller` beans and registers their annotated handler methods as ``DataFetcher``s via `RuntimeWiring.Builder`. It is an implementation of `RuntimeWiringConfigurer` which can be added to `GraphQlSource.Builder`. The xref:boot-starter.adoc[Boot Starter] automatically declares `AnnotatedControllerConfigurer` as a bean and adds all `RuntimeWiringConfigurer` beans to `GraphQlSource.Builder` and that enables support for annotated ``DataFetcher``s, see the {spring-boot-ref-docs}/web.html#web.graphql.runtimewiring[GraphQL RuntimeWiring] section in the Boot starter documentation. [[controllers.schema-mapping]] == `@SchemaMapping` The `@SchemaMapping` annotation maps a handler method to a field in the GraphQL schema and declares it to be the `DataFetcher` for that field. The annotation can specify the parent type name, and the field name: [source,java,indent=0,subs="verbatim,quotes"] ---- @Controller public class BookController { @SchemaMapping(typeName="Book", field="author") public Author getAuthor(Book book) { // ... } } ---- The `@SchemaMapping` annotation can also leave out those attributes, in which case the field name defaults to the method name, while the type name defaults to the simple class name of the source/parent object injected into the method. For example, the below defaults to type "Book" and field "author": [source,java,indent=0,subs="verbatim,quotes"] ---- @Controller public class BookController { @SchemaMapping public Author author(Book book) { // ... } } ---- The `@SchemaMapping` annotation can be declared at the class level to specify a default type name for all handler methods in the class. [source,java,indent=0,subs="verbatim,quotes"] ---- @Controller @SchemaMapping(typeName="Book") public class BookController { // @SchemaMapping methods for fields of the "Book" type } ---- `@QueryMapping`, `@MutationMapping`, and `@SubscriptionMapping` are meta annotations that are themselves annotated with `@SchemaMapping` and have the typeName preset to `Query`, `Mutation`, or `Subscription` respectively. Effectively, these are shortcut annotations for fields under the Query, Mutation, and Subscription types respectively. For example: [source,java,indent=0,subs="verbatim,quotes"] ---- @Controller public class BookController { @QueryMapping public Book bookById(@Argument Long id) { // ... } @MutationMapping public Book addBook(@Argument BookInput bookInput) { // ... } @SubscriptionMapping public Flux newPublications() { // ... } } ---- `@SchemaMapping` handler methods have flexible signatures and can choose from a range of method arguments and return values.. [[controllers.schema-mapping.signature]] === Method Signature Schema mapping handler methods can have any of the following method arguments: [cols="1,2"] |=== | Method Argument | Description | `@Argument` | For access to a named field argument bound to a higher-level, typed Object. See xref:controllers.adoc#controllers.schema-mapping.argument[`@Argument`]. | `@Argument Map` | For access to the raw argument value. See xref:controllers.adoc#controllers.schema-mapping.argument[`@Argument`]. | `ArgumentValue` | For access to a named field argument bound to a higher-level, typed Object along with a flag to indicate if the input argument was omitted vs set to `null`. See xref:controllers.adoc#controllers.schema-mapping.argument-value[`ArgumentValue`]. | `@Arguments` | For access to all field arguments bound to a higher-level, typed Object. See xref:controllers.adoc#controllers.schema-mapping.arguments[`@Arguments`]. | `@Arguments Map` | For access to the raw map of arguments. | `@ProjectedPayload` Interface | For access to field arguments through a project interface. See xref:controllers.adoc#controllers.schema-mapping.projectedpayload.argument[`@ProjectedPayload` Interface]. | "Source" | For access to the source (i.e. parent/container) instance of the field. See xref:controllers.adoc#controllers.schema-mapping.source[Source]. | `Subrange` and `ScrollSubrange` | For access to pagination arguments. See xref:request-execution.adoc#execution.pagination[Pagination], xref:data.adoc#data.pagination.scroll[Scroll], xref:controllers.adoc#controllers.schema-mapping.subrange[`Subrange`]. | `Sort` | For access to sort details. See xref:request-execution.adoc#execution.pagination[Pagination], xref:controllers.adoc#controllers.schema-mapping.sort[`Sort`]. | `DataLoader` | For access to a `DataLoader` in the `DataLoaderRegistry`. See xref:controllers.adoc#controllers.schema-mapping.data-loader[`DataLoader`]. | `@ContextValue` | For access to an attribute from the main `GraphQLContext` in `DataFetchingEnvironment`. | `@LocalContextValue` | For access to an attribute from the local `GraphQLContext` in `DataFetchingEnvironment`. | `GraphQLContext` | For access to the context from the `DataFetchingEnvironment`. | `java.security.Principal` | Obtained from the Spring Security context, if available. | `@AuthenticationPrincipal` | For access to `Authentication#getPrincipal()` from the Spring Security context. | `DataFetchingFieldSelectionSet` | For access to the selection set for the query through the `DataFetchingEnvironment`. | `Locale`, `Optional` | For access to the `Locale` from the `DataFetchingEnvironment`. | `DataFetchingEnvironment` | For direct access to the underlying `DataFetchingEnvironment`. |=== Schema mapping handler methods can return: - A resolved value of any type. - `Mono` and `Flux` for asynchronous value(s). Supported for controller methods and for any `DataFetcher` as described in xref:request-execution.adoc#execution.reactive-datafetcher[Reactive `DataFetcher`]. - `java.util.concurrent.Callable` to have the value(s) produced asynchronously. For this to work, `AnnotatedControllerConfigurer` must be configured with an `Executor`. [[controllers.schema-mapping.argument]] === `@Argument` In GraphQL Java, `DataFetchingEnvironment` provides access to a map of field-specific argument values. The values can be simple scalar values (e.g. String, Long), a `Map` of values for more complex input, or a `List` of values. Use the `@Argument` annotation to have an argument bound to a target object and injected into the handler method. Binding is performed by mapping argument values to a primary data constructor of the expected method parameter type, or by using a default constructor to create the object and then map argument values to its properties. This is repeated recursively, using all nested argument values and creating nested target objects accordingly. For example: [source,java,indent=0,subs="verbatim,quotes"] ---- @Controller public class BookController { @QueryMapping public Book bookById(@Argument Long id) { // ... } @MutationMapping public Book addBook(@Argument BookInput bookInput) { // ... } } ---- TIP: If the target object doesn't have setters, and you can't change that, you can use a property on `AnnotatedControllerConfigurer` to allow falling back on binding via direct field access. By default, if the method parameter name is available (requires the `-parameters` compiler flag with Java 8+ or debugging info from the compiler), it is used to look up the argument. If needed, you can customize the name through the annotation, e.g. `@Argument("bookInput")`. TIP: The `@Argument` annotation does not have a "required" flag, nor the option to specify a default value. Both of these can be specified at the GraphQL schema level and are enforced by GraphQL Java. If binding fails, a `BindException` is raised with binding issues accumulated as field errors where the `field` of each error is the argument path where the issue occurred. You can use `@Argument` with a `Map` argument, to obtain the raw value of the argument. For example: [source,java,indent=0,subs="verbatim,quotes"] ---- @Controller public class BookController { @MutationMapping public Book addBook(@Argument Map bookInput) { // ... } } ---- NOTE: Prior to 1.2, `@Argument Map` returned the full arguments map if the annotation did not specify a name. After 1.2, `@Argument` with `Map` always returns the raw argument value, matching either to the name specified in the annotation, or to the parameter name. For access to the full arguments map, please use xref:controllers.adoc#controllers.schema-mapping.arguments[`@Arguments`] instead. [[controllers.schema-mapping.argument-value]] === `ArgumentValue` By default, input arguments in GraphQL are nullable and optional, which means an argument can be set to the `null` literal, or not provided at all. This distinction is useful for partial updates with a mutation where the underlying data may also be, either set to `null` or not changed at all accordingly. When using xref:controllers.adoc#controllers.schema-mapping.argument[`@Argument`] there is no way to make such a distinction, because you would get `null` or an empty `Optional` in both cases. If you want to know not whether a value was not provided at all, you can declare an `ArgumentValue` method parameter, which is a simple container for the resulting value, along with a flag to indicate whether the input argument was omitted altogether. You can use this instead of `@Argument`, in which case the argument name is determined from the method parameter name, or together with `@Argument` to specify the argument name. For example: [source,java,indent=0,subs="verbatim,quotes"] ---- @Controller public class BookController { @MutationMapping public void addBook(ArgumentValue bookInput) { if (!bookInput.isOmitted()) { BookInput value = bookInput.value(); // ... } } } ---- `ArgumentValue` is also supported as a field within the object structure of an `@Argument` method parameter, either initialized via a constructor argument or via a setter, including as a field of an object nested at any level below the top level object. [[controllers.schema-mapping.arguments]] === `@Arguments` Use the `@Arguments` annotation, if you want to bind the full arguments map onto a single target Object, in contrast to `@Argument`, which binds a specific, named argument. For example, `@Argument BookInput bookInput` uses the value of the argument "bookInput" to initialize `BookInput`, while `@Arguments` uses the full arguments map and in that case, top-level arguments are bound to `BookInput` properties. You can use `@Arguments` with a `Map` argument, to obtain the raw map of all argument values. [[controllers.schema-mapping.projectedpayload.argument]] === `@ProjectedPayload` Interface As an alternative to using complete Objects with xref:controllers.adoc#controllers.schema-mapping.argument[`@Argument`], you can also use a projection interface to access GraphQL request arguments through a well-defined, minimal interface. Argument projections are provided by https://docs.spring.io/spring-data/commons/docs/current/reference/html/#projections.interfaces[Spring Data's Interface projections] when Spring Data is on the class path. To make use of this, create an interface annotated with `@ProjectedPayload` and declare it as a controller method parameter. If the parameter is annotated with `@Argument`, it applies to an individual argument within the `DataFetchingEnvironment.getArguments()` map. When declared without `@Argument`, the projection works on top-level arguments in the complete arguments map. For example: [source,java,indent=0,subs="verbatim,quotes"] ---- @Controller public class BookController { @QueryMapping public Book bookById(BookIdProjection bookId) { // ... } @MutationMapping public Book addBook(@Argument BookInputProjection bookInput) { // ... } } @ProjectedPayload interface BookIdProjection { Long getId(); } @ProjectedPayload interface BookInputProjection { String getName(); @Value("#{target.author + ' ' + target.name}") String getAuthorAndName(); } ---- [[controllers.schema-mapping.source]] === Source In GraphQL Java, the `DataFetchingEnvironment` provides access to the source (i.e. parent/container) instance of the field. To access this, simply declare a method parameter of the expected target type. [source,java,indent=0,subs="verbatim,quotes"] ---- @Controller public class BookController { @SchemaMapping public Author author(Book book) { // ... } } ---- The source method argument also helps to determine the type name for the mapping. If the simple name of the Java class matches the GraphQL type, then there is no need to explicitly specify the type name in the `@SchemaMapping` annotation. [TIP] ==== A xref:controllers.adoc#controllers.batch-mapping[`@BatchMapping`] handler method can batch load all authors for a query, given a list of source/parent books objects. ==== [[controllers.schema-mapping.subrange]] === `Subrange` When there is a xref:request-execution.adoc#execution.pagination.cursor.strategy[`CursorStrategy`] bean in Spring configuration, controller methods support a `Subrange

` argument where `

` is a relative position converted from a cursor. For Spring Data, `ScrollSubrange` exposes `ScrollPosition`. For example: [source,java,indent=0,subs="verbatim,quotes"] ---- @Controller public class BookController { @QueryMapping public Window books(ScrollSubrange subrange) { ScrollPosition position = subrange.position().orElse(ScrollPosition.offset()); int count = subrange.count().orElse(20); // ... } } ---- See xref:request-execution.adoc#execution.pagination[Pagination] for an overview of pagination and of built-in mechanisms. [[controllers.schema-mapping.sort]] === `Sort` When there is a xref:data.adoc#data.pagination.scroll[SortStrategy] bean in Spring configuration, controller methods support `Sort` as a method argument. For example: [source,java,indent=0,subs="verbatim,quotes"] ---- @Controller public class BookController { @QueryMapping public Window books(Optional optionalSort) { Sort sort = optionalSort.orElse(Sort.by(..)); } } ---- [[controllers.schema-mapping.data-loader]] === `DataLoader` When you register a batch loading function for an entity, as explained in xref:request-execution.adoc#execution.batching[Batch Loading], you can access the `DataLoader` for the entity by declaring a method argument of type `DataLoader` and use it to load the entity: [source,java,indent=0,subs="verbatim,quotes"] ---- @Controller public class BookController { public BookController(BatchLoaderRegistry registry) { registry.forTypePair(Long.class, Author.class).registerMappedBatchLoader((authorIds, env) -> { // return Map }); } @SchemaMapping public CompletableFuture author(Book book, DataLoader loader) { return loader.load(book.getAuthorId()); } } ---- By default, `BatchLoaderRegistry` uses the full class name of the value type (e.g. the class name for `Author`) for the key of the registration, and therefore simply declaring the `DataLoader` method argument with generic types provides enough information to locate it in the `DataLoaderRegistry`. As a fallback, the `DataLoader` method argument resolver will also try the method argument name as the key but typically that should not be necessary. Note that for many cases with loading related entities, where the `@SchemaMapping` simply delegates to a `DataLoader`, you can reduce boilerplate by using a xref:controllers.adoc#controllers.batch-mapping[@BatchMapping] method as described in the next section. [[controllers.schema-mapping.validation]] === Validation When a `javax.validation.Validator` bean is found, `AnnotatedControllerConfigurer` enables support for {spring-framework-ref-docs}/core/validation/beanvalidation.html#validation-beanvalidation-overview[Bean Validation] on annotated controller methods. Typically, the bean is of type `LocalValidatorFactoryBean`. Bean validation lets you declare constraints on types: [source,java,indent=0,subs="verbatim,quotes"] ---- public class BookInput { @NotNull private String title; @NotNull @Size(max=13) private String isbn; } ---- You can then annotate a controller method parameter with `@Valid` to validate it before method invocation: [source,java,indent=0,subs="verbatim,quotes"] ---- @Controller public class BookController { @MutationMapping public Book addBook(@Argument @Valid BookInput bookInput) { // ... } } ---- If an error occurs during validation, a `ConstraintViolationException` is raised. You can use the xref:request-execution.adoc#execution.exceptions[Exceptions] chain to decide how to present that to clients by turning it into an error to include in the GraphQL response. TIP: In addition to `@Valid`, you can also use Spring's `@Validated` that allows specifying validation groups. Bean validation is useful for xref:controllers.adoc#controllers.schema-mapping.argument[`@Argument`], xref:controllers.adoc#controllers.schema-mapping.arguments[`@Arguments`], and xref:controllers.adoc#controllers.schema-mapping.projectedpayload.argument[@ProjectedPayload] method parameters, but applies more generally to any method parameter. [WARNING] .Validation and Kotlin Coroutines ==== Hibernate Validator is not compatible with Kotlin Coroutine methods and fails when introspecting their method parameters. Please see https://github.com/spring-projects/spring-graphql/issues/344#issuecomment-1082814093[spring-projects/spring-graphql#344 (comment)] for links to relevant issues and a suggested workaround. ==== [[controllers.batch-mapping]] == `@BatchMapping` xref:request-execution.adoc#execution.batching[Batch Loading] addresses the N+1 select problem through the use of an `org.dataloader.DataLoader` to defer the loading of individual entity instances, so they can be loaded together. For example: [source,java,indent=0,subs="verbatim,quotes"] ---- @Controller public class BookController { public BookController(BatchLoaderRegistry registry) { registry.forTypePair(Long.class, Author.class).registerMappedBatchLoader((authorIds, env) -> { // return Map }); } @SchemaMapping public CompletableFuture author(Book book, DataLoader loader) { return loader.load(book.getAuthorId()); } } ---- For the straight-forward case of loading an associated entity, shown above, the `@SchemaMapping` method does nothing more than delegate to the `DataLoader`. This is boilerplate that can be avoided with a `@BatchMapping` method. For example: [source,java,indent=0,subs="verbatim,quotes"] ---- @Controller public class BookController { @BatchMapping public Mono> author(List books) { // ... } } ---- The above becomes a batch loading function in the `BatchLoaderRegistry` where keys are `Book` instances and the loaded values their authors. In addition, a `DataFetcher` is also transparently bound to the `author` field of the type `Book`, which simply delegates to the `DataLoader` for authors, given its source/parent `Book` instance. [TIP] ==== To be used as a unique key, `Book` must implement `hashcode` and `equals`. ==== By default, the field name defaults to the method name, while the type name defaults to the simple class name of the input `List` element type. Both can be customized through annotation attributes. The type name can also be inherited from a class level `@SchemaMapping`. [[controllers.batch-mapping.signature]] === Method Signature Batch mapping methods support the following arguments: [cols="1,2"] |=== | Method Argument | Description | `List` | The source/parent objects. | `java.security.Principal` | Obtained from Spring Security context, if available. | `@ContextValue` | For access to a value from the `GraphQLContext` of `BatchLoaderEnvironment`, which is the same context as the one from the `DataFetchingEnvironment`. | `GraphQLContext` | For access to the context from the `BatchLoaderEnvironment`, which is the same context as the one from the `DataFetchingEnvironment`. | `BatchLoaderEnvironment` | The environment that is available in GraphQL Java to a `org.dataloader.BatchLoaderWithContext`. |=== Batch mapping methods can return: [cols="1,2"] |=== | Return Type | Description | `Mono>` | A map with parent objects as keys, and batch loaded objects as values. | `Flux` | A sequence of batch loaded objects that must be in the same order as the source/parent objects passed into the method. | `Map`, `Collection` | Imperative variants, e.g. without remote calls to make. | `Callable>`, `Callable>` | Imperative variants to be invoked asynchronously. For this to work, `AnnotatedControllerConfigurer` must be configured with an `Executor`. |=== [[controllers.exception-handler]] == `@GraphQlExceptionHandler` Use `@GraphQlExceptionHandler` methods to handle exceptions from data fetching with a flexible xref:controllers.adoc#controllers.exception-handler.signature[method signature]. When declared in a controller, exception handler methods apply to exceptions from the same controller: [source,java,indent=0,subs="verbatim,quotes"] ---- @Controller public class BookController { @QueryMapping public Book bookById(@Argument Long id) { // ... } @GraphQlExceptionHandler public GraphQLError handle(BindException ex) { return GraphQLError.newError().errorType(ErrorType.BAD_REQUEST).message("...").build(); } } ---- When declared in an `@ControllerAdvice`, exception handler methods apply across controllers: [source,java,indent=0,subs="verbatim,quotes"] ---- @ControllerAdvice public class GlobalExceptionHandler { @GraphQlExceptionHandler public GraphQLError handle(BindException ex) { return GraphQLError.newError().errorType(ErrorType.BAD_REQUEST).message("...").build(); } } ---- Exception handling via `@GraphQlExceptionHandler` methods is applied automatically to controller invocations. To handle exceptions from other `graphql.schema.DataFetcher` implementations, not based on controller methods, obtain a `DataFetcherExceptionResolver` from `AnnotatedControllerConfigurer`, and register it in `GraphQlSource.Builder` as a xref:request-execution.adoc#execution.exceptions[DataFetcherExceptionResolver]. [[controllers.exception-handler.signature]] === Method Signature Exception handler methods support a flexible method signature with method arguments resolved from a `DataFetchingEnvironment,` and matching to those of xref:controllers.adoc#controllers.schema-mapping.arguments[@SchemaMapping methods]. Supported return types are listed below: [cols="1,2"] |=== | Return Type | Description | `graphql.GraphQLError` | Resolve the exception to a single field error. | `Collection` | Resolve the exception to multiple field errors. | `void` | Resolve the exception without response errors. | `Object` | Resolve the exception to a single error, to multiple errors, or none. The return value must be `GraphQLError`, `Collection`, or `null`. | `Mono` | For asynchronous resolution where `` is one of the supported, synchronous, return types. |=== [[controllers.namespacing]] == Namespacing At the schema level, query and mutation operations are defined directly under the `Query` and `Mutation` types. Rich GraphQL APIs can define dozens of operation sunder those types, making it harder to explore the API and separate concerns. You can choose to https://www.apollographql.com/docs/technotes/TN0012-namespacing-by-separation-of-concern/[define Namespaces in your GraphQL schema]. While there are some caveats with this approach, you can implement this pattern with Spring for GraphQL annotated controllers. With namespacing, your GraphQL schema can, for example, nest query operations under top-level types, instead of listing them directly under `Query`. Here, we will define `MusicQueries` and `UserQueries` types and make them available under `Query`: [source,json,subs="verbatim,quotes"] ---- include::ROOT:{include-resources}/controllers/namespaces.graphqls[] ---- A GraphQL client would use the `album` query like this: [source,graphql,subs="verbatim,quotes"] ---- { music { album(id: 42) { id title } } } ---- And get the following response: [source,json,subs="verbatim,quotes"] ---- { "data": { "music": { "album": { "id": "42", "title": "Spring for GraphQL" } } } } ---- This can be implemented in a `@Controller` with the following pattern: include-code::MusicController[] <1> Annotate the controller with `@SchemaMapping` and a `typeName` attribute, to avoid repeating it on methods <2> Define a `@QueryMapping` for the "music" namespace <3> The "music" query returns an "empty" record, but could also return an empty map <4> Queries are now declared as fields under the "MusicQueries" type Instead of declaring wrapping types ("MusicQueries", "UserQueries") explicitly in controllers, you can choose to configure them with the runtime wiring using a `GraphQlSourceBuilderCustomizer` with Spring Boot: include-code::NamespaceConfiguration[] <1> List all the wrapper types for the "Query" type <2> Manually declare data fetchers for each of them, returning an empty Map