GraphQL with Spring Boot
GraphQL lets clients request exactly the data they need — no over-fetching, no under-fetching. Spring Boot 3 has first-class GraphQL support.
Setup
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
spring:
graphql:
graphiql:
enabled: true # GraphiQL UI at /graphiql
path: /graphql
schema:
locations: classpath:graphql/
GraphQL Schema
src/main/resources/graphql/schema.graphqls:
type Query {
books: [Book!]!
book(id: ID!): Book
booksByAuthor(author: String!): [Book!]!
}
type Mutation {
createBook(input: CreateBookInput!): Book!
updateBook(id: ID!, input: UpdateBookInput!): Book!
deleteBook(id: ID!): Boolean!
}
type Book {
id: ID!
title: String!
author: Author!
isbn: String
price: Float
reviews: [Review!]!
}
type Author {
id: ID!
name: String!
books: [Book!]!
}
type Review {
id: ID!
rating: Int!
comment: String
reviewer: String!
}
input CreateBookInput {
title: String!
authorId: ID!
isbn: String
price: Float
}
input UpdateBookInput {
title: String
price: Float
}
Controllers
@Controller
public class BookGraphQLController {
private final BookService bookService;
@QueryMapping
public List<Book> books() {
return bookService.findAll();
}
@QueryMapping
public Book book(@Argument Long id) {
return bookService.findById(id);
}
@QueryMapping
public List<Book> booksByAuthor(@Argument String author) {
return bookService.findByAuthor(author);
}
@MutationMapping
public Book createBook(@Argument CreateBookInput input) {
return bookService.create(input);
}
@MutationMapping
public Book updateBook(@Argument Long id, @Argument UpdateBookInput input) {
return bookService.update(id, input);
}
@MutationMapping
public boolean deleteBook(@Argument Long id) {
bookService.delete(id);
return true;
}
}
Nested Resolvers
@Controller
public class BookGraphQLController {
// Resolve the 'author' field on Book
@SchemaMapping(typeName = "Book", field = "author")
public Author author(Book book) {
return authorService.findById(book.getAuthorId());
}
// Resolve 'reviews' field on Book
@SchemaMapping(typeName = "Book", field = "reviews")
public List<Review> reviews(Book book) {
return reviewService.findByBookId(book.getId());
}
}
DataLoader — Solving N+1
@Controller
public class BookGraphQLController {
@BatchMapping
public Map<Book, Author> author(List<Book> books) {
// Single batch query instead of N queries
List<Long> authorIds = books.stream()
.map(Book::getAuthorId).distinct().toList();
Map<Long, Author> authorsById = authorService.findByIds(authorIds).stream()
.collect(Collectors.toMap(Author::getId, a -> a));
return books.stream()
.collect(Collectors.toMap(book -> book, book -> authorsById.get(book.getAuthorId())));
}
}
GraphQL Queries
# Get only what you need
query {
books {
title
price
}
}
# Get book with author and reviews
query {
book(id: 1) {
title
author {
name
}
reviews {
rating
comment
}
}
}
# Create a book
mutation {
createBook(input: {
title: "Clean Code"
authorId: 1
isbn: "978-0132350884"
price: 39.99
}) {
id
title
}
}
GraphQL vs REST
| Feature | REST | GraphQL |
|---|---|---|
| Endpoints | Multiple (/books, /authors) |
Single (/graphql) |
| Over-fetching | Common | ❌ Client picks fields |
| Under-fetching | Multiple requests | ❌ Single query |
| Caching | Simple (HTTP caching) | Complex |
| File upload | Simple | Complex |
| Best for | CRUD, simple APIs | Complex, nested data |
Error Handling
@Controller
public class BookGraphQLController {
@QueryMapping
public Book book(@Argument Long id) {
return bookService.findById(id); // throws ResourceNotFoundException
}
}
@Component
public class GraphQLExceptionResolver extends DataFetcherExceptionResolverAdapter {
@Override
protected GraphQLError resolveToSingleError(Throwable ex, DataFetchingEnvironment env) {
if (ex instanceof ResourceNotFoundException) {
return GraphqlErrorBuilder.newError(env)
.message(ex.getMessage())
.errorType(ErrorType.NOT_FOUND)
.build();
}
return null; // default handling
}
}
Key Takeaways
- Spring Boot 3 has native GraphQL support — schema-first approach.
@QueryMappingfor queries,@MutationMappingfor mutations.@SchemaMappingresolves nested fields;@BatchMappingprevents N+1.- GraphQL excels at complex, nested data — REST is simpler for CRUD.
- Use GraphiQL (
/graphiql) for interactive query testing.
Module Complete!
🎉 You’ve completed the Spring Boot module — from project setup to GraphQL. You now know how to build production-ready APIs with Spring Boot, including REST, databases, security, testing, Docker, and more.