Skip to main content

Getting Started with Spring for GraphQL - A Complete Guide

· 6 min read
GraphQL Guy

Spring GraphQL Banner

Spring for GraphQL is the official Spring project for building GraphQL applications. Let's build your first GraphQL API from scratch.

Why Spring for GraphQL?

If you're a Java developer, you've probably wondered how to bring GraphQL into your Spring ecosystem. While libraries like graphql-java have existed for years, Spring for GraphQL (released in 2022) provides first-class integration with Spring Boot, making GraphQL development feel native to the Spring way of doing things.

Key Benefits

  • Annotation-driven development - Use familiar Spring patterns like @Controller and @Autowired
  • Automatic schema mapping - Spring maps your schema to Java types automatically
  • Built-in testing support - Test utilities designed for GraphQL
  • WebSocket subscriptions - Real-time data out of the box
  • Security integration - Seamless Spring Security support

Setting Up Your Project

Let's create a new Spring Boot project with GraphQL support. You can use Spring Initializr or add dependencies manually.

Dependencies

Add these to your pom.xml:

<dependencies>
<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>
</dependencies>

Or for Gradle (build.gradle):

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-graphql'
implementation 'org.springframework.boot:spring-boot-starter-web'
}

Enable GraphiQL (Development Tool)

GraphiQL is an in-browser IDE for exploring GraphQL APIs. Enable it in application.yml:

spring:
graphql:
graphiql:
enabled: true
schema:
printer:
enabled: true

Now you can access GraphiQL at http://localhost:8080/graphiql.

Creating Your First Schema

GraphQL is schema-first by nature. Create your schema file at src/main/resources/graphql/schema.graphqls:

type Query {
books: [Book!]!
bookById(id: ID!): Book
}

type Book {
id: ID!
title: String!
author: Author!
publishedYear: Int
genre: String
}

type Author {
id: ID!
name: String!
books: [Book!]!
}

This schema defines:

  • A Query type with two entry points
  • A Book type with fields including a relationship to Author
  • An Author type with a back-reference to their books

Creating Java Domain Classes

Now let's create corresponding Java classes:

public record Book(
String id,
String title,
String authorId,
Integer publishedYear,
String genre
) {}

public record Author(
String id,
String name
) {}

Note: We use authorId in the Java class but author in GraphQL. Spring GraphQL will resolve this through a DataFetcher.

Building the Controller

Here's where Spring for GraphQL shines. Create a controller using familiar Spring patterns:

@Controller
public class BookController {

private final BookRepository bookRepository;
private final AuthorRepository authorRepository;

public BookController(BookRepository bookRepository,
AuthorRepository authorRepository) {
this.bookRepository = bookRepository;
this.authorRepository = authorRepository;
}

@QueryMapping
public List<Book> books() {
return bookRepository.findAll();
}

@QueryMapping
public Book bookById(@Argument String id) {
return bookRepository.findById(id).orElse(null);
}

@SchemaMapping(typeName = "Book", field = "author")
public Author author(Book book) {
return authorRepository.findById(book.authorId()).orElse(null);
}

@SchemaMapping(typeName = "Author", field = "books")
public List<Book> booksByAuthor(Author author) {
return bookRepository.findByAuthorId(author.id());
}
}

Understanding the Annotations

AnnotationPurpose
@QueryMappingMaps to fields in the Query type
@MutationMappingMaps to fields in the Mutation type
@SchemaMappingMaps to fields on any type
@ArgumentExtracts GraphQL arguments

Creating a Simple Repository

For this example, let's use an in-memory repository:

@Repository
public class BookRepository {

private final Map<String, Book> books = new ConcurrentHashMap<>();

@PostConstruct
public void init() {
books.put("1", new Book("1", "The Great Gatsby", "1", 1925, "Fiction"));
books.put("2", new Book("2", "To Kill a Mockingbird", "2", 1960, "Fiction"));
books.put("3", new Book("3", "1984", "3", 1949, "Dystopian"));
books.put("4", new Book("4", "Animal Farm", "3", 1945, "Political Satire"));
}

public List<Book> findAll() {
return new ArrayList<>(books.values());
}

public Optional<Book> findById(String id) {
return Optional.ofNullable(books.get(id));
}

public List<Book> findByAuthorId(String authorId) {
return books.values().stream()
.filter(book -> book.authorId().equals(authorId))
.toList();
}
}
@Repository
public class AuthorRepository {

private final Map<String, Author> authors = new ConcurrentHashMap<>();

@PostConstruct
public void init() {
authors.put("1", new Author("1", "F. Scott Fitzgerald"));
authors.put("2", new Author("2", "Harper Lee"));
authors.put("3", new Author("3", "George Orwell"));
}

public Optional<Author> findById(String id) {
return Optional.ofNullable(authors.get(id));
}
}

Testing Your API

Start your application and open GraphiQL at http://localhost:8080/graphiql. Try this query:

query {
books {
id
title
publishedYear
author {
name
}
}
}

You should see a response like:

{
"data": {
"books": [
{
"id": "1",
"title": "The Great Gatsby",
"publishedYear": 1925,
"author": {
"name": "F. Scott Fitzgerald"
}
},
{
"id": "2",
"title": "To Kill a Mockingbird",
"publishedYear": 1960,
"author": {
"name": "Harper Lee"
}
}
]
}
}

Project Structure

Your project should look like this:

src/
├── main/
│ ├── java/
│ │ └── com/example/demo/
│ │ ├── DemoApplication.java
│ │ ├── controller/
│ │ │ └── BookController.java
│ │ ├── model/
│ │ │ ├── Book.java
│ │ │ └── Author.java
│ │ └── repository/
│ │ ├── BookRepository.java
│ │ └── AuthorRepository.java
│ └── resources/
│ ├── application.yml
│ └── graphql/
│ └── schema.graphqls

Understanding the Request Flow

When a GraphQL query arrives at your Spring application, here's what happens:

  1. HTTP Request arrives at /graphql endpoint
  2. Query Parsing - Spring GraphQL parses the GraphQL query
  3. Validation - Query is validated against the schema
  4. Execution - DataFetchers (your @QueryMapping methods) are called
  5. Field Resolution - Nested fields trigger @SchemaMapping methods
  6. Response - Results are serialized to JSON
┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│ Client │────▶│ /graphql │────▶│ Schema │
│ Query │ │ Endpoint │ │ Validation │
└─────────────┘ └─────────────┘ └──────┬──────┘

┌─────────────┐ ┌──────▼──────┐
│ JSON │◀────│ Data │
│ Response │ │ Fetchers │
└─────────────┘ └─────────────┘

Common Pitfalls

1. Schema Location

Spring GraphQL looks for schemas in src/main/resources/graphql/. If you put them elsewhere, configure the location:

spring:
graphql:
schema:
locations: classpath:graphql/
file-extensions: .graphqls, .gqls

2. Null Safety

GraphQL distinguishes between nullable and non-nullable fields. String! means non-null. Make sure your Java code respects this:

// Schema: title: String!
// This will cause an error if title is null
@QueryMapping
public Book bookById(@Argument String id) {
return bookRepository.findById(id)
.orElseThrow(() -> new BookNotFoundException(id));
}

3. Case Sensitivity

GraphQL field names are case-sensitive. If your schema has publishedYear but your Java method returns publishedyear, Spring won't map them automatically.

Next Steps

Congratulations! You've built your first Spring GraphQL application. In upcoming posts, we'll explore:

  • Mutations - Creating, updating, and deleting data
  • Error Handling - Graceful error responses
  • DataLoader - Solving the N+1 problem
  • Security - Authentication and authorization
  • Testing - Unit and integration testing strategies

Quick Reference

TaskAnnotation/Config
Query field@QueryMapping
Mutation field@MutationMapping
Type field@SchemaMapping(typeName="...", field="...")
Get argument@Argument
Enable GraphiQLspring.graphql.graphiql.enabled=true
Schema locationsrc/main/resources/graphql/

Spring for GraphQL makes building GraphQL APIs feel natural for Spring developers. The annotation-driven approach, combined with automatic schema mapping, lets you focus on business logic rather than boilerplate.

Happy coding!