Overview

Spring GraphQL provides support for Spring applications built on GraphQL Java. It is a joint collaboration between both teams. Our shared philosophy is to be less opinionated and more focused on comprehensive and wide-ranging support.

Spring GraphQL is the successor of the GraphQL Java Spring project from the GraphQL Java team. It aims to be the foundation for all Spring, GraphQL applications.

The project is in a milestone phase towards a 1.0 release, currently, and looking for feedback. Please, use our issue tracker to report a problem, discuss a design issue, or request a feature.

To get started, please see the Boot Starter and the Samples sections.

Web Transports

Spring GraphQL supports GraphQL requests over HTTP and over WebSocket.

HTTP

GraphQlHttpHandler handles GraphQL over HTTP requests and delegates to the Web 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 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.

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 Web Endpoints for details or check GraphQlWebMvcAutoConfiguration or GraphQlWebFluxAutoConfiguration for example config.

The Spring GraphQL repository contains a Spring MVC HTTP sample application.

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 Web 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. The Boot starter has options to enable this, see Web Endpoints for details or check GraphQlWebMvcAutoConfiguration or GraphQlWebFluxAutoConfiguration for example config.

The Spring GraphQL repository contains a WebFlux WebSocket sample application.

Web Interception

HTTP and WebSocket transport handlers delegate to a common Web interception chain for request execution. The chain consists of a sequence of WebInterceptor components, followed by a GraphQlService that invokes the GraphQL Java engine.

WebInterceptor is as a common contract to use in both Spring MVC and WebFlux applications. Use it to intercept requests, inspect HTTP request headers, or to register a transformation of the graphql.ExecutionInput:

class MyInterceptor implements WebInterceptor {

    @Override
    public Mono<WebOutput> intercept(WebInput webInput, WebGraphQlHandler next) {
        webInput.configureExecutionInput((executionInput, builder) -> {
            Map<String, Object> map = ... ;
            return builder.extensions(map).build();
        });
        return next.handle(webInput);
    }
}

Use WebInterceptor also to intercept responses, add HTTP response headers, or transform the graphql.ExecutionResult:

class MyInterceptor implements WebInterceptor {

    @Override
    public Mono<WebOutput> intercept(WebInput webInput, WebGraphQlHandler next) {
        return next.handle(webInput)
                .map(webOutput -> {
                    Object data = webOutput.getData();
                    Object updatedData = ... ;
                    return webOutput.transform(builder -> builder.data(updatedData));
                });
    }
}

WebGraphQlHandler provides a builder to initialize the Web interception chain. After you build the chain, you can use the resulting WebGraphQlHandler to initialize the HTTP or WebSocket transport handlers. The Boot starter configures all this, see Web Endpoints for details, or check GraphQlWebMvcAutoConfiguration or GraphQlWebFluxAutoConfiguration for example config.

Query Execution

GraphQlService is the main Spring GraphQL abstraction to call GraphQL Java to execute requests. Underlying transports, such as the Web Transports, delegate to GraphQlService to handle requests.

The main implementation, ExecutionGraphQlService, is a thin facade around the invocation of graphql.GraphQL. It is configured with a GraphQlSource for access to the graphql.GraphQL instance.

GraphQLSource

GraphQlSource is a core Spring GraphQL abstraction for access to the graphql.GraphQL instance to use for request execution. It provides a builder API to initialize GraphQL Java and build a GraphQlSource.

The default GraphQlSource builder, accessible via GraphQlSource.builder(), enables support for Reactive DataFetcher, Context Propagation, and Exception Resolution.

Reactive DataFetcher

The default GraphQlSource builder enables support for a DataFetcher to return Mono or Flux. Both return types are adapted to a CompletableFuture with Flux values aggregated and turned into a List, unless the request is a GraphQL subscription request, in which case the return value remains a Reactive Streams Publisher for streaming GraphQL responses.

A reactive DataFetcher can rely on access to Reactor context propagated from the transport layer, such as from a WebFlux request handling, see WebFlux Context.

Context Propagation

Spring GraphQL provides support to transparently propagate context from the Web Transports, through the GraphQL engine, and to DataFetcher and other components it invokes. This includes both ThreadLocal context from the Spring MVC request handling thread and Reactor Context from the WebFlux processing pipeline.

WebMvc

A DataFetcher and other components invoked by GraphQL Java may not always execute on the same thread as the Spring MVC handler, for example if an asynchronous WebInterceptor or DataFetcher switches to a different thread.

Spring GraphQL supports propagating ThreadLocal values from the Servlet container thread to the thread a DataFetcher and other components invoked by the GraphQL engine execute on. To do this, an application needs to create a ThreadLocalAccessor to extract ThreadLocal values of interest:

public class RequestAttributesAccessor implements ThreadLocalAccessor {

    private static final String KEY = RequestAttributesAccessor.class.getName();

    @Override
    public void extractValues(Map<String, Object> container) {
        container.put(KEY, RequestContextHolder.getRequestAttributes());
    }

    @Override
    public void restoreValues(Map<String, Object> values) {
        if (values.containsKey(KEY)) {
            RequestContextHolder.setRequestAttributes((RequestAttributes) values.get(KEY));
        }
    }

    @Override
    public void resetValues(Map<String, Object> values) {
        RequestContextHolder.resetRequestAttributes();
    }

}

A ThreadLocalAccessor can be registered in the WebGraphHandler builder. The Boot starter detects beans of this type and automatically registers them for Spring MVC application, see Web Endpoints.

WebFlux

A Reactive DataFetcher can rely on access to Reactor context that originates from the WebFlux request handling chain. This includes Reactor context added by WebInterceptor components.

Exception Resolution

GraphQL Java applications can register a DataFetcherExceptionHandler to decide how to represent exceptions from the data layer in the "errors" section of the GraphQL response.

Spring GraphQL has a built-in DataFetcherExceptionHandler that is configured for use by the GraphQLSource builder. It enables applications to register one or more Spring DataFetcherExceptionResolver components that are invoked sequentially until one resolves the Exception to a list of graphql.GraphQLError objects.

DataFetcherExceptionResolver is an asynchronous contract. For most implementations, it would be sufficient to extend DataFetcherExceptionResolverAdapter and override one of its resolveToSingleError or resolveToMultipleErrors methods that resolve exceptions synchronously.

A GraphQLError can be assigned an graphql.ErrorClassification. Spring GraphQL defines an ErrorType enum with common, error classification categories:

  • BAD_REQUEST

  • UNAUTHORIZED

  • FORBIDDEN

  • NOT_FOUND

  • INTERNAL_ERROR

Applications can use this to classify errors. If an error remains unresolved, by default it is marked as INTERNAL_ERROR.

Data Integration

Querydsl

Spring GraphQL supports use of Querydsl to fetch data through the Spring Data Querydsl extension. Querydsl provides a flexible yet typesafe approach to express query predicates by generating a meta-model using annotation processors.

For example, declare a repository as QuerydslPredicateExecutor:

public interface AccountRepository extends Repository<Account, Long>,
            QuerydslPredicateExecutor<Account> {
}

Then use it to create a DataFetcher:

// For single result queries
DataFetcher<Account> dataFetcher =
        QuerydslDataFetcher.builder(repository).single();

// For multi-result queries
DataFetcher<Iterable<Account>> dataFetcher =
        QuerydslDataFetcher.builder(repository).many();

The DataFetcher builds a Querydsl Predicate from GraphQL request parameters, and uses it to fetch data. Spring Data supports QuerydslPredicateExecutor for JPA, MongoDB, and LDAP.

If the repository is ReactiveQuerydslPredicateExecutor, the builder returns DataFetcher<Mono<Account>> or DataFetcher<Flux<Account>>. Spring Data supports this variant for MongoDB.

The webmvc-http sample in the Spring GraphQL repository uses Querydsl to fetch artifactRepositories.

Customizations

The Querydsl integration allows customizing the request parameters binding onto a Predicate by accepting a QuerydslBinderCustomizer. Request parameters are bound by default as "is equal to" for each available property in the request.

QuerydslDataFetcher supports interface and DTO projections to transform query results before returning these for further GraphQL processing.

Auto Registration

QuerydslDataFetcher exposes a GraphQLTypeVisitor that finds top-level queries whose return type matches the domain type of one or more Querydsl repositories, and registers a DataFetcher for each matching query. This includes both queries that return a single value and queries that return a list of values.

The repository must be annotated with @GraphQlRepository. By default, the name of the GraphQL type returned by the query must match the simple name of the repository domain type. Of if they don’t match, you can use the typeName attribute of @GraphQlRepository to set the GraphQL type name.

Such repositories are auto-detected in the Boot starter.

Security

The path to a Web GraphQL endpoint can be secured with HTTP URL security to ensure that only authenticated users can access it. This does not, however, differentiate among different GraphQL requests on such a shared endpoint on a single URL.

To apply more fine-grained security, add Spring Security annotations such as @PreAuthorize or @Secured to service methods involved in fetching specific parts of the GraphQL response. This should work due to Context Propagation that aims to make Security, and other context, available at the data fetching level.

The Spring GraphQL repository contains samples for Spring MVC and for WebFlux.

Testing

You can test GraphQL requests using Spring’s WebTestClient, just send and receive JSON, but a number of GraphQL specific details make this approach more cumbersome than it should be.

GraphQlTester

GraphQlTester defines a workflow to test GraphQL requests with the following benefits:

  • Verify GraphQL responses are 200 (OK).

  • Verify no unexpected errors under the "errors" key in the response.

  • Decode under the "data" key in the response.

  • Use JsonPath to decode different parts of the response.

  • Test subscriptions.

To create GraphQlTester, you only need a GraphQlService, and no transport:

GraphQlSource graphQlSource = GraphQlSource.builder()
        .schemaResources(...)
        .runtimeWiring(...)
        .build();

GraphQlService graphQlService = new ExecutionGraphQlService(graphQlSource);

GraphQlTester graphQlTester = GraphQlTester.builder(graphQlService).build();

WebGraphQlTester

WebGraphQlTester extends GraphQlTester to add a workflow and configuration specific to Web Transports. You need one of the following inputs to create it:

  • WebTestClient — perform requests as an HTTP client, either against HTTP handlers without a server, or against a live server.

  • WebGraphQlHandler — perform requests through the Web Interception chain used by both HTTP and WebSocket handlers, which in effect is testing without a Web framework. One reason to use this is for Subscriptions.

For Spring WebFlux without a server, you can point to your Spring configuration:

ApplicationContext context = ... ;

WebTestClient client =
        WebTestClient.bindToApplicationContext(context)
                .configureClient()
                .baseUrl("/graphql")
                .build();

WebGraphQlTester tester = WebGraphQlTester.builder(client).build();

For Spring MVC without a server, use the MockMvcWebTestClient builder:

WebApplicationContext context = ... ;

WebTestClient client =
        MockMvcWebTestClient.bindToApplicationContext(context)
                .configureClient()
                .baseUrl("/graphql")
                .build();

WebGraphQlTester tester = WebGraphQlTester.builder(client).build();

For tests against a live, running server:

WebTestClient client =
        WebTestClient.bindToServer()
                .baseUrl("http://localhost:8080/graphql")
                .build();

WebGraphQlTester tester = WebGraphQlTester.builder(client).build();

Queries

Below is an example query test using JsonPath to extract all release versions in the GraphQL response.

String query = "{" +
        "  project(slug:\"spring-framework\") {" +
        "    releases {" +
        "      version" +
        "    }"+
        "  }" +
        "}";

graphQlTester.query(query)
        .execute()
        .path("project.releases[*].version")
        .entityList(String.class)
        .hasSizeGreaterThan(1);

The JsonPath is relative to the "data" section of the response.

Errors

Tests cannot use verify data, if there are errors under the "errors" key in the response has errors. If necessary to ignore an error, use an error filter Predicate:

graphQlTester.query(query)
        .execute()
        .errors()
        .filter(error -> ...)
        .verify()
        .path("project.releases[*].version")
        .entityList(String.class)
        .hasSizeGreaterThan(1);

An error filter can be registered globally and apply to all tests:

WebGraphQlTester graphQlTester = WebGraphQlTester.builder(client)
        .errorFilter(error -> ...)
        .build();

Or inspect all errors directly and that also marks them as filtered:

graphQlTester.query(query)
        .execute()
        .errors()
        .satisfy(errors -> {
            // ...
        });

If a request does not have any response data (e.g. mutation), use executeAndVerify instead of execute to verify there are no errors in the response:

graphQlTester.query(query).executeAndVerify();

Subscriptions

The executeSubscription method defines a workflow specific to subscriptions which return a stream of responses instead of a single response.

To test subscriptions, you can create GraphQlTester with a GraphQlService, which calls graphql.GraphQL directly and that returns a stream of responses:

GraphQlService service = ... ;

GraphQlTester graphQlTester = GraphQlTester.builder(service).build();

Flux<String> result = graphQlTester.query("subscription { greetings }")
    .executeSubscription()
    .toFlux("greetings", String.class);  // decode each response

The StepVerifier from Project Reactor is useful to verify a stream:

Flux<String> result = graphQlTester.query("subscription { greetings }")
    .executeSubscription()
    .toFlux("greetings", String.class);

StepVerifier.create(result)
        .expectNext("Hi")
        .expectNext("Bonjour")
        .expectNext("Hola")
        .verifyComplete();

To test with the Web Interception chain, you can create WebGraphQlTester with a WebGraphQlHandler:

GraphQlService service = ... ;

WebGraphQlHandler handler = WebGraphQlHandler.builder(service)
    .interceptor((input, next) -> next.handle(input))
    .build();

WebGraphQlTester graphQlTester = WebGraphQlTester.builder(handler).build();

Currently, Spring GraphQL does not support testing with a WebSocket client, and it cannot be used for integration test of GraphQL over WebSocket requests.

Boot Starter

This projects builds on Boot 2.5.x, but it should be compatible with the latest Boot 2.4.x.

Project Setup

To create a project, go to https://start.spring.io and select starter(s) for the GraphQL transports you want to use:

Starter Transport Implementation

spring-boot-starter-web

HTTP

Spring MVC

spring-boot-starter-websocket

WebSocket

WebSocket for Servlet apps

spring-boot-starter-webflux

HTTP, WebSocket

Spring WebFlux

In the generated project, add graphql-spring-boot-starter manually:

Gradle
dependencies {
    // Spring GraphQL Boot starter
    implementation 'org.springframework.experimental:graphql-spring-boot-starter:1.0.0-SNAPSHOT'

    // ...
}

repositories {
    mavenCentral()
    maven { url 'https://repo.spring.io/milestone' }  // Spring milestones
    maven { url 'https://repo.spring.io/snapshot' }   // Spring snapshots
}
Maven
<dependencies>

    // Spring GraphQL Boot starter
    <dependency>
        <groupId>org.springframework.experimental</groupId>
        <artifactId>graphql-spring-boot-starter</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </dependency>

    <!-- ... -->

</dependencies>

<!-- For Spring project milestones or snapshot releases -->
<repositories>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
    </repository>
    <repository>
        <id>spring-snapshots</id>
        <name>Spring Snapshots</name>
        <url>https://repo.spring.io/snapshot</url>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </repository>
</repositories>
Boot Starter Group Id

The Boot starter will move from the Spring GraphQL repository to the Spring Boot repository, after Spring Boot 2.6 is released. The group id for the starter will then change from org.springframework.experimental to org.springframework.boot and will be released in Spring Boot 2.7.

Schema

By default, GraphQL schema files are expected to be in src/main/resources/graphql and have the extension ".graphqls", ".graphql", ".gql", or ".gqls". You can customize the schema locations to check as follows:

spring.graphql.schema.locations=classpath:graphql/

The GraphQL schema can be viewed over HTTP at "/graphql/schema". This is not enabled by default:

spring.graphql.schema.printer.enabled=false

DataFetcher Registration

You can declare RuntimeWiringBuilderCustomizer beans in your Spring config and use those to register data fetchers, type resolvers, and more with the GraphQL engine:

@Component
public class PersonDataWiring implements RuntimeWiringBuilderCustomizer {

    private final PersonService service;

    public PersonDataWiring(PersonService service) {
        this.service = service;
    }

    @Override
    public void customize(RuntimeWiring.Builder builder) {
        builder.type("Query", wiring ->
                wiring.dataFetcher("people", env -> this.service.findAll()));
    }
}

Querydsl Repositories

Spring Data repositories that extend QuerydslPredicateExecutor or ReactiveQuerydslPredicateExecutor and are annotated with @GraphQlRepository are detected and considered as candidates for DataFetcher auto registration for matching top-level queries.

Web Endpoints

The GraphQL HTTP endpoint is at HTTP POST "/graphql" by default. The path can be customized:

spring.graphql.path=/graphql

The GraphQL WebSocket endpoint supports WebSocket handshakes at "/graphql" by default. The below shows the properties that apply for WebSocket handling:

spring.graphql.websocket.path=/graphql

# Time within which a "CONNECTION_INIT" message must be received from the client
spring.graphql.websocket.connection-init-timeout=60s

The GraphQL WebSocket endpoint is off by default. To enable it:

  • For a Servlet application, add the WebSocket starter spring-boot-starter-websocket.

  • For a WebFlux application, set the spring.graphql.websocket.path application property.

Declare a WebInterceptor bean to have it registered in the Web Interception for GraphQL over HTTP and WebSocket requests.

Declare a ThreadLocalAccessor bean to assist with the propagation of ThreadLocal values of interest in Spring MVC.

GraphiQL

The Spring Boot starter includes a GraphiQL page that is exposed at "/graphiql" by default. You can configure this as follows:

spring.graphql.graphiql.enabled=true
spring.graphql.graphiql.path=/graphiql

Metrics

When the starter spring-boot-starter-actuator is present on the classpath, metrics for GraphQL requests are collected. You can disable metrics collection as follows:

management.metrics.graphql.autotime.enabled=false

Metrics can be exposed with an Actuator web endpoint. The following sections assume that its exposure is enabled in your application configuration, as follows:

management.endpoints.web.exposure.include=health,metrics,info

Request Timer

A Request metric timer is available at /actuator/metrics/graphql.request.

Tag Description Sample values

outcome

Request outcome

"SUCCESS", "ERROR"

DataFetcher Timer

A DataFetcher metric timer is available at /actuator/metrics/graphql.datafetcher.

Tag Description Sample values

path

data fetcher path

"Query.project"

outcome

data fetching outcome

"SUCCESS", "ERROR"

Error Counter

A GraphQL error metric counter is available at /actuator/metrics/graphql.error.

Tag Description Sample values

errorType

error type

"DataFetchingException"

errorPath

error JSON Path

"$.project"

Testing

For Spring GraphQL testing support, add the below to your classpath and that will make a WebGraphQlTester available for injection into tests:

Gradle
dependencies {
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.graphql:spring-graphql-test:1.0.0-SNAPSHOT'

    // Also add this, unless spring-boot-starter-webflux is also present
    testImplementation 'org.springframework:spring-webflux'

    // ...
}

repositories {
    mavenCentral()
    maven { url 'https://repo.spring.io/milestone' }  // Spring milestones
    maven { url 'https://repo.spring.io/snapshot' }   // Spring snapshots
}
Maven
<dependencies>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.graphql</groupId>
        <artifactId>spring-graphql-test</artifactId>
        <version>1.0.0-SNAPSHOT</version>
        <scope>test</scope>
    </dependency>

    <!-- Also add this, unless "spring-boot-starter-webflux" is also present -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webflux</artifactId>
        <scope>test</scope>
    </dependency>

    <!-- ... -->

</dependencies>

<!-- For Spring project milestones or snapshot releases -->
<repositories>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
    </repository>
    <repository>
        <id>spring-snapshots</id>
        <name>Spring Snapshots</name>
        <url>https://repo.spring.io/snapshot</url>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </repository>
</repositories>

For GraphQL over HTTP with Spring MVC, using MockMvc as the server:

@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureGraphQlTester
public class MockMvcGraphQlTests {

    @Autowired
    private WebGraphQlTester graphQlTester;

}

For GraphQL over HTTP with Spring WebFlux, using a mock server:

@SpringBootTest
@AutoConfigureWebTestClient
@AutoConfigureGraphQlTester
public class MockMvcGraphQlTests {

    @Autowired
    private WebGraphQlTester graphQlTester;

}

For GraphQL over HTTP with a running server:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureGraphQlTester
public class MockMvcGraphQlTests {

    @Autowired
    private WebGraphQlTester graphQlTester;

}

Subscriptions can be tested without WebSocket as shown below:

@SpringBootTest
@AutoConfigureGraphQlTester
public class MockMvcGraphQlTests {

    @Autowired
    private WebGraphQlTester graphQlTester;

    @Test
    void subscription() {
        Flux<String> result = this.graphQlTester.query("subscription { greetings }")
                .executeSubscription()
                .toFlux("greetings", String.class);

        // Use StepVerifier from "reactor-test" to verify the stream...
        StepVerifier.create(result)
                .expectNext("Hi")
                .expectNext("Bonjour")
                .expectNext("Hola")
                .verifyComplete();
    }

}

The above subscription test is performed directly against the WebGraphQlHandler that both HTTP and WebSocket transports delegate to. It passes through the WebInterceptor chain and then calls GraphQL Java which returns a Reactive Streams Publisher.

Samples

This Spring GraphQL repository contains sample applications for various scenarios.

You can run those by cloning this repository and running main application classes from your IDE or by typing the following on the command line:

$ ./gradlew :samples:{sample-directory-name}:bootRun