Starter for building GraphQL applications with Spring GraphQL
—
Spring Boot GraphQL Starter provides seamless integration with both Spring MVC (servlet-based) and Spring WebFlux (reactive) web stacks. The integration includes HTTP endpoints, WebSocket support, Server-Sent Events, and comprehensive transport layer configuration.
Auto-configuration for servlet-based applications using Spring MVC.
@AutoConfiguration(after = GraphQlAutoConfiguration.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass({ GraphQL.class, GraphQlHttpHandler.class })
@ConditionalOnBean(ExecutionGraphQlService.class)
@EnableConfigurationProperties(GraphQlCorsProperties.class)
public class GraphQlWebMvcAutoConfiguration {
// MVC-specific GraphQL configuration
}Handles HTTP POST requests to the GraphQL endpoint.
@Bean
@ConditionalOnMissingBean
public GraphQlHttpHandler graphQlHttpHandler(WebGraphQlHandler webGraphQlHandler) {
return new GraphQlHttpHandler(webGraphQlHandler);
}Handles Server-Sent Events for GraphQL subscriptions over HTTP.
@Bean
@ConditionalOnMissingBean
public GraphQlSseHandler graphQlSseHandler(
WebGraphQlHandler webGraphQlHandler,
GraphQlProperties properties
) {
return new GraphQlSseHandler(
webGraphQlHandler,
properties.getHttp().getSse().getTimeout(),
properties.getHttp().getSse().getKeepAlive()
);
}Web-specific GraphQL handler with interceptor support.
@Bean
@ConditionalOnMissingBean
public WebGraphQlHandler webGraphQlHandler(
ExecutionGraphQlService service,
ObjectProvider<WebGraphQlInterceptor> interceptors
) {
return WebGraphQlHandler.builder(service)
.interceptors(interceptors.orderedStream().toList())
.build();
}Configures routing for GraphQL endpoints using Spring's functional routing.
@Bean
@Order(0)
public RouterFunction<ServerResponse> graphQlRouterFunction(
GraphQlHttpHandler httpHandler,
GraphQlSseHandler sseHandler,
ObjectProvider<GraphQlSource> graphQlSourceProvider,
GraphQlProperties properties
) {
String path = properties.getHttp().getPath();
RouterFunctions.Builder builder = RouterFunctions.route();
// Main GraphQL endpoint
builder.route(GraphQlRequestPredicates.graphQlHttp(path), httpHandler::handleRequest);
// Server-Sent Events endpoint
builder.route(GraphQlRequestPredicates.graphQlSse(path), sseHandler::handleRequest);
// Error handling
builder.POST(path, this::unsupportedMediaType);
builder.GET(path, this::onlyAllowPost);
// Optional GraphiQL endpoint
if (properties.getGraphiql().isEnabled()) {
GraphiQlHandler graphiQLHandler = new GraphiQlHandler(path, properties.getWebsocket().getPath());
builder.GET(properties.getGraphiql().getPath(), graphiQLHandler::handleRequest);
}
// Optional schema printer endpoint
if (properties.getSchema().getPrinter().isEnabled()) {
SchemaHandler schemaHandler = new SchemaHandler(graphQlSource);
builder.GET(path + "/schema", schemaHandler::handleRequest);
}
return builder.build();
}WebSocket configuration for GraphQL subscriptions in servlet applications.
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ ServerContainer.class, WebSocketHandler.class })
@ConditionalOnProperty("spring.graphql.websocket.path")
public static class WebSocketConfiguration {
@Bean
@ConditionalOnMissingBean
public GraphQlWebSocketHandler graphQlWebSocketHandler(
WebGraphQlHandler webGraphQlHandler,
GraphQlProperties properties,
HttpMessageConverters converters
) {
return new GraphQlWebSocketHandler(
webGraphQlHandler,
getJsonConverter(converters),
properties.getWebsocket().getConnectionInitTimeout(),
properties.getWebsocket().getKeepAlive()
);
}
@Bean
public HandlerMapping graphQlWebSocketMapping(
GraphQlWebSocketHandler handler,
GraphQlProperties properties
) {
String path = properties.getWebsocket().getPath();
WebSocketHandlerMapping mapping = new WebSocketHandlerMapping();
mapping.setWebSocketUpgradeMatch(true);
mapping.setUrlMap(Collections.singletonMap(path,
handler.initWebSocketHttpRequestHandler(new DefaultHandshakeHandler())));
mapping.setOrder(-2); // Higher priority than HTTP endpoint
return mapping;
}
}Auto-configuration for reactive applications using Spring WebFlux.
@AutoConfiguration(after = GraphQlAutoConfiguration.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnClass({ GraphQL.class, org.springframework.graphql.server.webflux.GraphQlHttpHandler.class })
@ConditionalOnBean(ExecutionGraphQlService.class)
@EnableConfigurationProperties(GraphQlCorsProperties.class)
public class GraphQlWebFluxAutoConfiguration {
// WebFlux-specific GraphQL configuration
}Handles reactive HTTP requests using WebFlux.
@Bean
@ConditionalOnMissingBean
public org.springframework.graphql.server.webflux.GraphQlHttpHandler graphQlHttpHandler(
WebGraphQlHandler webGraphQlHandler
) {
return new org.springframework.graphql.server.webflux.GraphQlHttpHandler(webGraphQlHandler);
}Handles reactive Server-Sent Events for subscriptions.
@Bean
@ConditionalOnMissingBean
public org.springframework.graphql.server.webflux.GraphQlSseHandler graphQlSseHandler(
WebGraphQlHandler webGraphQlHandler,
GraphQlProperties properties
) {
return new org.springframework.graphql.server.webflux.GraphQlSseHandler(
webGraphQlHandler,
properties.getHttp().getSse().getTimeout(),
properties.getHttp().getSse().getKeepAlive()
);
}@Bean
@Order(0)
public RouterFunction<ServerResponse> graphQlRouterFunction(
org.springframework.graphql.server.webflux.GraphQlHttpHandler httpHandler,
org.springframework.graphql.server.webflux.GraphQlSseHandler sseHandler,
ObjectProvider<GraphQlSource> graphQlSourceProvider,
GraphQlProperties properties
) {
// Similar structure to MVC but using reactive handlers
return RouterFunctions.route()
.POST(path, accept(MediaType.APPLICATION_JSON), httpHandler::handleRequest)
.GET(path, accept(MediaType.TEXT_EVENT_STREAM), sseHandler::handleRequest)
// Additional routing configuration
.build();
}Cross-Origin Resource Sharing configuration for GraphQL endpoints.
@Configuration(proxyBeanMethods = false)
public static class GraphQlEndpointCorsConfiguration implements WebMvcConfigurer {
private final GraphQlProperties graphQlProperties;
private final GraphQlCorsProperties corsProperties;
@Override
public void addCorsMappings(CorsRegistry registry) {
CorsConfiguration configuration = this.corsProperties.toCorsConfiguration();
if (configuration != null) {
registry.addMapping(this.graphQlProperties.getHttp().getPath())
.combine(configuration);
}
}
}// Custom CORS configuration
@Configuration
public class GraphQlCorsConfig {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(List.of("https://*.example.com"));
configuration.setAllowedMethods(List.of("GET", "POST"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/graphql", configuration);
return source;
}
}GraphQL-specific request matching predicates.
public final class GraphQlRequestPredicates {
// Match GraphQL HTTP requests (POST with application/json)
public static RequestPredicate graphQlHttp(String path) {
return RequestPredicates.POST(path)
.and(RequestPredicates.accept(MediaType.APPLICATION_JSON))
.and(RequestPredicates.contentType(MediaType.APPLICATION_JSON));
}
// Match GraphQL SSE requests (GET with text/event-stream)
public static RequestPredicate graphQlSse(String path) {
return RequestPredicates.GET(path)
.and(RequestPredicates.accept(MediaType.TEXT_EVENT_STREAM));
}
}Built-in error responses for unsupported requests.
// Unsupported media type response
private ServerResponse unsupportedMediaType(ServerRequest request) {
return ServerResponse.status(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
.headers(headers -> headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)))
.build();
}
// Method not allowed response
private ServerResponse onlyAllowPost(ServerRequest request) {
return ServerResponse.status(HttpStatus.METHOD_NOT_ALLOWED)
.headers(headers -> headers.setAllow(Collections.singleton(HttpMethod.POST)))
.build();
}Implement cross-cutting concerns for web GraphQL requests.
public interface WebGraphQlInterceptor {
Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, WebGraphQlInterceptorChain chain);
}@Component
public class AuthenticationInterceptor implements WebGraphQlInterceptor {
@Override
public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, WebGraphQlInterceptorChain chain) {
// Extract authentication from request headers
String token = extractAuthToken(request);
if (token == null) {
return Mono.error(new GraphQlAuthenticationException("Authentication required"));
}
// Validate token and set context
return validateToken(token)
.flatMap(user -> {
request.configureExecutionInput((executionInput, builder) ->
builder.graphQLContext(context -> context.put("currentUser", user))
);
return chain.next(request);
});
}
}Standard HTTP POST requests with JSON payload:
// Example HTTP request
POST /graphql
Content-Type: application/json
{
"query": "query GetBooks { books { id title author } }",
"variables": {},
"operationName": "GetBooks"
}For GraphQL subscriptions over HTTP:
// JavaScript SSE client example
const eventSource = new EventSource('/graphql?query=subscription{bookAdded{id title}}');
eventSource.onmessage = function(event) {
const data = JSON.parse(event.data);
console.log('Subscription update:', data);
};GraphQL over WebSocket using graphql-ws protocol:
// WebSocket client example
const client = new GraphQLWsClient({
url: 'ws://localhost:8080/graphql-ws',
connectionParams: {
authToken: 'Bearer ...'
}
});
client.subscribe({
query: 'subscription { bookAdded { id title } }'
}, {
next: (data) => console.log('Subscription data:', data),
error: (err) => console.error('Subscription error:', err),
complete: () => console.log('Subscription complete')
});Built-in GraphQL IDE for development:
# Enable GraphiQL
spring.graphql.graphiql.enabled=true
spring.graphql.graphiql.path=/graphiqlGraphiQL provides:
Development endpoint to view the complete GraphQL schema:
# Enable schema printer endpoint
spring.graphql.schema.printer.enabled=trueAccess schema at: GET /graphql/schema
WebSocket connections are pooled and managed automatically:
# WebSocket connection timeout
spring.graphql.websocket.connection-init-timeout=30s
# Keep-alive for idle connections
spring.graphql.websocket.keep-alive=15sHTTP endpoint supports GraphQL request batching:
// Batch multiple operations in single HTTP request
POST /graphql
Content-Type: application/json
[
{"query": "query GetBooks { books { id title } }"},
{"query": "query GetAuthors { authors { id name } }"}
]Automatic HTTP caching headers for non-subscription queries:
Cache-Control: public, max-age=300
ETag: "schema-hash-query-hash"Install with Tessl CLI
npx tessl i tessl/maven-org-springframework-boot--spring-boot-starter-graphql