Server Transports

Spring for GraphQL supports server handling of GraphQL requests over HTTP, WebSocket, and RSocket.

HTTP

GraphQlHttpHandler handles GraphQL over HTTP requests and delegates to the Interception chain for request execution. There are two variants, one for Spring MVC and one for Spring WebFlux. Both handle requests asynchronously and have equivalent functionality, but rely on blocking vs non-blocking I/O respectively for writing the HTTP response.

Requests must use HTTP POST with "application/json" as content type and GraphQL request details included as JSON in the request body, as defined in the proposed GraphQL over HTTP specification. Once the JSON body has been successfully decoded, the HTTP response status is always 200 (OK), and any errors from GraphQL request execution appear in the "errors" section of the GraphQL response. The default and preferred choice of media type is "application/graphql-response+json", but "application/json" is also supported, as described in the specification.

GraphQlHttpHandler can be exposed as an HTTP endpoint by declaring a RouterFunction bean and using the RouterFunctions from Spring MVC or WebFlux to create the route. The Boot Starter does this, see the Web Endpoints section for details, or check GraphQlWebMvcAutoConfiguration or GraphQlWebFluxAutoConfiguration it contains, for the actual config.

By default, the GraphQlHttpHandler will serialize and deserialize JSON payloads using the HttpMessageConverter (Spring MVC) and the DecoderHttpMessageReader/EncoderHttpMessageWriter (WebFlux) configured in the web framework. In some cases, the application will configure the JSON codec for the HTTP endpoint in a way that is not compatible with the GraphQL payloads. Applications can instantiate GraphQlHttpHandler with a custom JSON codec that will be used for GraphQL payloads.

The 1.0.x branch of this repository contains a Spring MVC HTTP sample application.

Server-Sent Events

GraphQlSseHandler is very similar to the HTTP handler listed above, but this time handling GraphQL requests over HTTP using the Server-Sent Events protocol. With this transport, clients must send HTTP POST requests to the endpoint with "application/json" as content type and GraphQL request details included as JSON in the request body; the only difference with the vanilla HTTP variant is that the client must send "text/event-stream" as the "Accept" request header. The response will be sent as one or more Server-Sent Event(s).

This is also defined in the proposed GraphQL over HTTP specification. Spring for GraphQL only implements the "Distinct connections mode", so applications must consider scalability concerns and whether adopting HTTP/2 as the underlying transport would help.

The main use case for GraphQlSseHandler is an alternative to the WebSocket transport, receiving a stream of items as a response to a subscription operation. Other types of operations, like queries and mutations, are not supported here and should be using the plain JSON over HTTP transport variant.

File Upload

As a protocol GraphQL focuses on the exchange of textual data. This doesn’t include binary data such as images, but there is a separate, informal graphql-multipart-request-spec that allows file uploads with GraphQL over HTTP.

Spring for GraphQL does not support the graphql-multipart-request-spec directly. While the spec does provide the benefit of a unified GraphQL API, the actual experince has led to a number of issues, and best practice recommendations have evolved, see Apollo Server File Upload Best Practices for a more detailed discussion.

If you would like to use graphql-multipart-request-spec in your application, you can do so through the library multipart-spring-graphql.

WebSocket

GraphQlWebSocketHandler handles GraphQL over WebSocket requests based on the protocol defined in the graphql-ws library. The main reason to use GraphQL over WebSocket is subscriptions which allow sending a stream of GraphQL responses, but it can also be used for regular queries with a single response. The handler delegates every request to the Interception chain for further request execution.

GraphQL Over WebSocket Protocols

There are two such protocols, one in the subscriptions-transport-ws library and another in the graphql-ws library. The former is not active and succeeded by the latter. Read this blog post for the history.

There are two variants of GraphQlWebSocketHandler, one for Spring MVC and one for Spring WebFlux. Both handle requests asynchronously and have equivalent functionality. The WebFlux handler also uses non-blocking I/O and back pressure to stream messages, which works well since in GraphQL Java a subscription response is a Reactive Streams Publisher.

The graphql-ws project lists a number of recipes for client use.

GraphQlWebSocketHandler can be exposed as a WebSocket endpoint by declaring a SimpleUrlHandlerMapping bean and using it to map the handler to a URL path. By default, the Boot Starter does not expose a GraphQL over WebSocket endpoint, but you can add a property for the endpoint path to enable it. Please, review Web Endpoints in the Boot reference documentation, and the list of supported spring.graphql.websocket properties. You can also look at GraphQlWebMvcAutoConfiguration or GraphQlWebFluxAutoConfiguration for the actual Boot autoconfig details.

The 1.0.x branch of this repository contains a WebFlux WebSocket sample application.

RSocket

GraphQlRSocketHandler handles GraphQL over RSocket requests. Queries and mutations are expected and handled as an RSocket request-response interaction while subscriptions are handled as request-stream.

GraphQlRSocketHandler can be used a delegate from an @Controller that is mapped to the route for GraphQL requests. For example:

import java.util.Map;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import org.springframework.graphql.server.GraphQlRSocketHandler;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.stereotype.Controller;

@Controller
public class GraphQlRSocketController {

	private final GraphQlRSocketHandler handler;

	GraphQlRSocketController(GraphQlRSocketHandler handler) {
		this.handler = handler;
	}

	@MessageMapping("graphql")
	public Mono<Map<String, Object>> handle(Map<String, Object> payload) {
		return this.handler.handle(payload);
	}

	@MessageMapping("graphql")
	public Flux<Map<String, Object>> handleSubscription(Map<String, Object> payload) {
		return this.handler.handleSubscription(payload);
	}
}

Interception

Server transports allow intercepting requests before and after the GraphQL Java engine is called to process a request.

WebGraphQlInterceptor

HTTP and WebSocket transports invoke a chain of 0 or more WebGraphQlInterceptor, followed by an ExecutionGraphQlService that calls the GraphQL Java engine. Interceptors allow applications to intercept incoming requests in order to:

  • Check HTTP request details

  • Customize the graphql.ExecutionInput

  • Add HTTP response headers

  • Customize the graphql.ExecutionResult

  • and more

For example, an interceptor can pass an HTTP request header to a DataFetcher:

import java.util.Collections;

import reactor.core.publisher.Mono;

import org.springframework.graphql.data.method.annotation.ContextValue;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.server.WebGraphQlInterceptor;
import org.springframework.graphql.server.WebGraphQlRequest;
import org.springframework.graphql.server.WebGraphQlResponse;
import org.springframework.stereotype.Controller;

class RequestHeaderInterceptor implements WebGraphQlInterceptor { (1)

	@Override
	public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, Chain chain) {
		String value = request.getHeaders().getFirst("myHeader");
		request.configureExecutionInput((executionInput, builder) ->
				builder.graphQLContext(Collections.singletonMap("myHeader", value)).build());
		return chain.next(request);
	}
}

@Controller
class MyContextValueController { (2)

	@QueryMapping
	Person person(@ContextValue String myHeader) {
		...
	}
}
1 Interceptor adds HTTP request header value into GraphQLContext
2 Data controller method accesses the value

Reversely, an interceptor can access values added to the GraphQLContext by a controller:

import graphql.GraphQLContext;
import reactor.core.publisher.Mono;

import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.server.WebGraphQlInterceptor;
import org.springframework.graphql.server.WebGraphQlRequest;
import org.springframework.graphql.server.WebGraphQlResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.stereotype.Controller;

// Subsequent access from a WebGraphQlInterceptor

class ResponseHeaderInterceptor implements WebGraphQlInterceptor {

	@Override
	public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, Chain chain) { (2)
		return chain.next(request).doOnNext((response) -> {
			String value = response.getExecutionInput().getGraphQLContext().get("cookieName");
			ResponseCookie cookie = ResponseCookie.from("cookieName", value).build();
			response.getResponseHeaders().add(HttpHeaders.SET_COOKIE, cookie.toString());
		});
	}
}

@Controller
class MyCookieController {

	@QueryMapping
	Person person(GraphQLContext context) { (1)
		context.put("cookieName", "123");
		...
	}
}
1 Controller adds value to the GraphQLContext
2 Interceptor uses the value to add an HTTP response header

WebGraphQlHandler can modify the ExecutionResult, for example, to inspect and modify request validation errors that are raised before execution begins and which cannot be handled with a DataFetcherExceptionResolver:

import java.util.List;

import graphql.GraphQLError;
import graphql.GraphqlErrorBuilder;
import reactor.core.publisher.Mono;

import org.springframework.graphql.server.WebGraphQlInterceptor;
import org.springframework.graphql.server.WebGraphQlRequest;
import org.springframework.graphql.server.WebGraphQlResponse;

class RequestErrorInterceptor implements WebGraphQlInterceptor {

	@Override
	public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, Chain chain) {
		return chain.next(request).map((response) -> {
			if (response.isValid()) {
				return response; (1)
			}

			List<GraphQLError> errors = response.getErrors().stream() (2)
					.map((error) -> {
						GraphqlErrorBuilder<?> builder = GraphqlErrorBuilder.newError();
						// ...
						return builder.build();
					})
					.toList();

			return response.transform((builder) -> builder.errors(errors).build()); (3)
		});
	}
}
1 Return the same if ExecutionResult has a "data" key with non-null value
2 Check and transform the GraphQL errors
3 Update the ExecutionResult with the modified errors

Use WebGraphQlHandler to configure the WebGraphQlInterceptor chain. This is supported by the Boot Starter, see Web Endpoints.

WebSocketGraphQlInterceptor

WebSocketGraphQlInterceptor extends WebGraphQlInterceptor with additional callbacks to handle the start and end of a WebSocket connection, in addition to client-side cancellation of subscriptions. The same also intercepts every GraphQL request on the WebSocket connection.

Use WebGraphQlHandler to configure the WebGraphQlInterceptor chain. This is supported by the Boot Starter, see Web Endpoints. There can be at most one WebSocketGraphQlInterceptor in a chain of interceptors.

There are two built-in WebSocket interceptors called AuthenticationWebSocketInterceptor, one for the WebMVC and one for the WebFlux transports. These help to extract authentication details from the payload of a "connection_init" GraphQL over WebSocket message, authenticate, and then propagate the SecurityContext to subsequent requests on the WebSocket connection.

There is a websocket-authentication sample in spring-graphql-examples.

RSocketQlInterceptor

Similar to WebGraphQlInterceptor, an RSocketQlInterceptor allows intercepting GraphQL over RSocket requests before and after GraphQL Java engine execution. You can use this to customize the graphql.ExecutionInput and the graphql.ExecutionResult.