From spring
Build Spring GraphQL servers with schema resources, annotated query or mutation mappings, batching, transport-aware execution, and `GraphQlTester`-based tests. Use this skill when building Spring GraphQL servers with schema resources, annotated query or mutation mappings, batching, transport-aware execution, and `GraphQlTester`-based tests.
npx claudepluginhub ririnto/sinon --plugin springThis skill uses the workspace's default tool permissions.
Use this skill when building Spring GraphQL servers with schema resources, annotated query or mutation mappings, batching, transport-aware execution, and `GraphQlTester`-based tests.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
Use this skill when building Spring GraphQL servers with schema resources, annotated query or mutation mappings, batching, transport-aware execution, and GraphQlTester-based tests.
Use spring-graphql for GraphQL schema design, query and mutation handlers, field mappings, batching, transport-aware execution, and GraphQL-specific tests.
The ordinary Spring GraphQL job is:
@QueryMapping or @MutationMapping and nested fields with @SchemaMapping or @BatchMapping only when needed.@BatchMapping or DataLoader when the resolver shape would otherwise cause N+1 access patterns.GraphQlTester test that proves the query shape, response path, and error behavior.| Surface | Start here when | Open a reference when |
|---|---|---|
| Ordinary schema-first query and mutation API | one service owns the schema and HTTP transport is enough | stay in SKILL.md |
| Subscription or alternate transports | subscriptions, WebSocket, or RSocket are the blocker | open references/transports-and-subscriptions.md |
| Custom batching lifecycle | @BatchMapping is not enough | open references/advanced-dataloader.md |
| Test-slice or integration-tester choice | the blocker is choosing the right GraphQL test surface | open references/testing-graphql.md |
| Security context or explicit error shaping | request metadata, authorization, or stable error categories are the blocker | open references/security-context-and-errors.md |
| Federated graph ownership | more than one service contributes to the graph | open references/federation.md |
Use the Boot starter for application code and the GraphQL test module for focused tests.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.graphql</groupId>
<artifactId>spring-graphql-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
| Need | Artifact |
|---|---|
| Ordinary GraphQL controllers, schema resources, and HTTP endpoint | spring-boot-starter-graphql |
| GraphQL-focused test slices and tester support | spring-graphql-test |
| Subscription, WebSocket, or RSocket transport support | spring-boot-starter-graphql with transport-specific configuration |
./mvnw test -Dtest=BookGraphQlControllerTests
./gradlew test --tests BookGraphQlControllerTests
src/main/resources/graphql/schema.graphqls
spring:
graphql:
path: /graphql
Start with a single schema resource set and one HTTP endpoint. Add WebSocket or RSocket transports only when the API really needs them.
./mvnw spring-boot:run
./gradlew bootRun
Run the server on the ordinary HTTP path first. Add WebSocket or RSocket verification only after the base schema and resolver path is stable.
| Situation | Use |
|---|---|
| Root query or mutation field | @QueryMapping or @MutationMapping |
| Nested field resolved from one parent object | @SchemaMapping |
| Nested field resolved for many parents at once | @BatchMapping |
| Custom loader options, several loader keys, or shared batch lifecycle | dedicated DataLoader registration |
Prefer @BatchMapping when one nested field is the obvious batching boundary. Keep manual DataLoader registration for cases where batching must be shared across several resolvers or keyed differently from the schema field itself.
@BatchMapping first, then reach for explicit DataLoader registration when batching needs custom loader composition.type Query {
bookById(id: ID!): Book
}
type Mutation {
addBook(input: BookInput!): Book!
}
type Book {
id: ID!
title: String!
author: String!
}
input BookInput {
title: String!
author: String!
}
@Controller
class BookGraphQlController {
private final BookService service;
BookGraphQlController(BookService service) {
this.service = service;
}
@QueryMapping
Book bookById(@Argument Long id) {
return service.find(id);
}
@MutationMapping
Book addBook(@Argument BookInput input) {
return service.add(input);
}
@SchemaMapping(typeName = "Book", field = "viewerCanEdit")
boolean viewerCanEdit(Book book, GraphQLContext context) {
return context.getOrDefault("role", "viewer").equals("editor");
}
}
@BatchMapping(typeName = "Book", field = "reviews")
Map<Book, List<Review>> reviews(List<Book> books) {
return reviewService.findByBooks(books);
}
@SchemaMapping(typeName = "Book", field = "reviews")
CompletableFuture<List<Review>> reviews(Book book, DataLoader<Long, List<Review>> reviewsLoader) {
return reviewsLoader.load(book.id());
}
Use @BatchMapping for the ordinary nested-field batching path. Keep direct DataLoader access for advanced loader registration or reuse across several resolvers.
graphQlTester.document("mutation { addBook(input: { title: \"\", author: \"A\" }) { id } }")
.execute()
.errors()
.satisfy(errors -> assertFalse(errors.isEmpty()));
@Bean
WebGraphQlInterceptor roleInterceptor() {
return (request, chain) -> {
String role = request.getHeaders().getFirst("X-Role");
if (role != null) {
request.configureExecutionInput((input, builder) -> builder.graphQLContext(Map.of("role", role)).build());
}
return chain.next(request);
};
}
Keep context values explicit and small. Read them at resolver boundaries only when the field behavior actually depends on request metadata.
@GraphQlTest(BookGraphQlController.class)
class BookGraphQlControllerTests {
@Autowired
GraphQlTester graphQlTester;
@Test
void query() {
graphQlTester.document("query { bookById(id: 1) { title } }")
.execute()
.path("bookById.title")
.entity(String.class)
.isEqualTo("Spring");
}
}
| Situation | Use |
|---|---|
| Controller-focused GraphQL mapping test | @GraphQlTest + GraphQlTester |
| Server integration with interceptor chain | WebGraphQlTester |
| Subscription verification | GraphQlTester + reactive assertions |
Open the testing reference when transport choice, subscription testing, or slice-versus-integration setup becomes the blocker.
bookById.title
/graphql
@SchemaMapping(typeName = "Book", field = "reviews")
@BatchMapping(typeName = "Book", field = "reviews")
Return:
@BatchMapping or explicit DataLoader registration is used