Class 1: Your First Spring GraphQL Service
Duration: 35 minutes | Difficulty: Beginner | Prerequisites: Basic Spring Boot knowledge, Java 21+, IDE of choice
What You'll Learn
By the end of this class, you will:
- Understand what GraphQL is and why it matters
- Create a Spring Boot project with GraphQL support
- Write your first GraphQL schema
- Implement your first query resolver
- Test queries using GraphiQL
What is Spring GraphQL?
Spring GraphQL is the official Spring integration for GraphQL, released on July 06, 2021. in this blog post. It is built on top of GraphQL Java (the most widely used GraphQL implementation on the JVM) and adds the Spring developer experience on top: annotations, auto-configuration, and seamless integration with Spring MVC, WebFlux, WebSocket, and RSocket.
The architecture looks something like this:
GraphQL Java handles the hard parts: parsing queries, validating them against your schema, and executing resolvers. Spring GraphQL wraps that with familiar Spring conventions so you rarely need to touch GraphQL Java directly.
DataFetchers are called Resolvers in the GraphQL specification and in other implementations. Andi Marek, the creator of graphql-java in his book "GraphQL with Java and Spring" says:
I (Andi) named it DataFetcher because I thought it reflected the purpose better. I am not convinced I would make the same decision today, but now it is too late to change.
What is GraphQL?
Before we write code, let's understand what we're building.
GraphQL is a query language for your API. Unlike REST, where the server decides what data to return, GraphQL lets clients ask for exactly what they need.
REST (Multiple endpoints, fixed responses):
GET /movies/1→{ id, title, year, director, actors... }GET /movies/1/actors→[{ id, name, bio, awards... }]GET /directors/5→{ id, name, movies... }
GraphQL (One endpoint, flexible responses):
POST /graphql
query {
movie(id: 1) {
title # Only what you need
director { name } # Nested in one request
}
}
Step 1: Create the Project
Let's create a new Spring Boot project. You have two options:
I won't cover Spring Boot basics here! If you're new to Spring, I recommend watching first few Spring Boot videos by Josh Long or Dan Vega. They work directly for Spring and do amazing tutorials.
Option A: Spring Initializr (Recommended)
-
Go to start.spring.io
-
Configure your project:
- Project: Maven
- Language: Java
- Spring Boot: 4.0.5 (or latest stable)
- Group: com.graphqlguy
- Artifact: moviedb
- Name: moviedb
- Package name: com.graphqlguy.moviedb
- Packaging: Jar
- Java: 21 or 25 (I will always use the latest LTS)
-
Add dependencies:
- Spring Web
- Spring GraphQL
- Lombok - you don't have to use it, if you don't like it
-
Click "Generate", extract the ZIP file, and open the project in an IDE.
Option B: Add to Existing Project
If you have an existing Spring Boot project, add these dependencies to your pom.xml:
<dependencies>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<!-- Spring GraphQL -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
<!-- I use Lombok quite extensively, you obviously don't have to use it if you don't like it -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- For testing (optional but recommended) -->
<dependency>
<groupId>org.springframework.graphql</groupId>
<artifactId>spring-graphql-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Step 2: Enable GraphiQL
GraphiQL is an in-browser IDE for exploring GraphQL APIs. Let's enable it.
Spring Initializr generated src/main/resources/application.properties, but this course uses the YAML format throughout - it handles the nested configuration of later classes much more readably. Rename the file to application.yaml and add:
# application.yaml
spring:
graphql:
graphiql:
enabled: true
Step 3: Create Your First Schema
GraphQL uses a Schema Definition Language (SDL) to define your API. Create this file:
📁 src/main/resources/graphql/schema.graphqls
type Query {
"""A simple greeting to verify the API is running"""
hello: String!
"""Find a movie by its unique identifier"""
movie(id: ID!): Movie
"""List all movies in the database"""
movies: [Movie!]!
}
"""A movie in the database"""
type Movie {
id: ID!
title: String!
releaseYear: Int!
genre: String!
}
Triple-quoted strings ("""...""") are GraphQL's documentation syntax. They appear in GraphiQL's Docs panel and in generated API documentation. Document types and fields when the name alone is not enough, for example value ranges, units, or non-obvious behavior. Skip comments that just restate the field name (e.g., """The title of the movie""" on a title field adds nothing).
Let's break this down:
| Element | Meaning |
|---|---|
type Query | The entry point for read operations |
hello: String! | A field that returns a non-null String |
movie(id: ID!) | A field that takes an ID argument |
[Movie!]! | A non-null list of non-null Movies |
type Movie | A custom object type |
! means non-null. String can be null, String! cannot.
[Movie]: nullable list of nullable movies[Movie!]: nullable list of non-null movies[Movie!]!: non-null list of non-null movies
movie(id: ID!) takes a required argument: an argument is required only when it is non-null (!) and has no default. Give it a default and it becomes optional - movies(first: Int = 20) lets the client omit first and get 20 back. You'll lean on defaults for pagination and filtering in Class 11.
Our movies query returns all movies at once. In a production API, you'd paginate this to avoid returning thousands of records in a single response. We'll cover pagination patterns in Class 11. For now, with only a handful of movies, returning all results is fine for learning.
Step 4: Create the Movie Model
Create a simple Java class to represent a Movie:
📁 src/main/java/com/graphqlguy/moviedb/movie/Movie.java
package com.graphqlguy.moviedb.movie;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Builder;
import lombok.NoArgConstructor;
@Data
@Builder
@NoArgConstructor
@AllArgsConstructor
public class Movie {
private Long id;
private String title;
private int releaseYear;
private String genre;
}
If you're using Java 16+, you can use a record for now, but note you'll have to change it, since we will be saving this to a DB later, and we need mutable entities for that:
public record Movie(Long id, String title, int releaseYear, String genre) {}
Step 5: Create the Query Controller
Now let's implement the resolvers that handle our GraphQL queries:
📁 src/main/java/com/graphqlguy/moviedb/movie/MovieController.java
package com.graphqlguy.moviedb.movie;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;
import java.util.List;
@Controller
public class MovieController {
// Sample data - we'll use a database in later classes
private final List<Movie> movies = List.of(
new Movie(1L, "The Shawshank Redemption", 1994, "Drama"),
new Movie(2L, "The Godfather", 1972, "Crime"),
new Movie(3L, "The Dark Knight", 2008, "Action"),
new Movie(4L, "Pulp Fiction", 1994, "Crime"),
new Movie(5L, "Forrest Gump", 1994, "Drama")
);
@QueryMapping
String hello() {
return "Welcome to the Movie Database GraphQL API!";
}
@QueryMapping
List<Movie> movies() {
return movies;
}
@QueryMapping
Movie movie(@Argument Long id) {
return movies.stream()
.filter(m -> m.getId().equals(id))
.findFirst()
.orElse(null);
}
}
Let's understand the annotations:
| Annotation | Purpose |
|---|---|
@Controller | Marks this as a Spring controller |
@QueryMapping | Maps a method to a GraphQL query field |
@Argument | Injects a GraphQL argument into the method |
-parameters compiler flag@Argument Long id binds by the parameter name (id), which the compiler only records if you build with the -parameters flag. Spring Boot's parent POM turns it on by default, so this usually just works - but if an argument ever binds as null, a missing -parameters is the first thing to check. You can also name the argument explicitly to sidestep it: @Argument("id") Long movieId.
By default, @QueryMapping uses the method name to match the schema field. So movies() maps to Query.movies. You can override this: @QueryMapping("allMovies").
@QueryMapping is actually a specialization of a more general purpose annotation called @SchemaMapping, similar to @GetMapping or @PostMapping in contrast to general purpose @RequestMapping, so we could've written the mapping also @SchemaMapping(typeName = "Query", field = "movies"
Spring for GraphQL ships with a Schema Mapping Inspection Report that compares every field in your schema to the @QueryMapping / @SchemaMapping / @MutationMapping methods you've written. Enable it with one property:
spring:
graphql:
schema:
inspection:
enabled: true
At startup you'll see a report listing any schema field without a resolver and any resolver without a schema field. It's the fastest way to catch "I added tagline: String to the schema and forgot to wire it up, why does the field always return null?" before it hits GraphiQL.
Prefer to log the report yourself? Register a customizer:
@Bean
public GraphQlSourceBuilderCustomizer inspectionCustomizer() {
return builder -> builder.inspectSchemaMappings(report ->
log.info("GraphQL schema mapping inspection:\n{}", report));
}
Add this now. It pays for itself the moment the schema starts growing.
Step 6: Run and Test
Start your application:
./mvnw spring-boot:run
Or run the main class from your IDE.
Open your browser and navigate to: http://localhost:8080/graphiql
You should see the GraphiQL interface!
Your First Query
In the left panel, type:
query {
hello
}
Click the Play button (▶️). You should see:
{
"data": {
"hello": "Welcome to the Movie Database GraphQL API!"
}
}
Query All Movies
query {
movies {
id
title
releaseYear
}
}
Response:
{
"data": {
"movies": [
{
"id": "1",
"title": "The Shawshank Redemption",
"releaseYear": 1994
},
{
"id": "2",
"title": "The Godfather",
"releaseYear": 1972
}
// ... more movies
]
}
}
Query a Single Movie
query {
movie(id: 3) {
title
genre
releaseYear
}
}
Response:
{
"data": {
"movie": {
"title": "The Dark Knight",
"genre": "Action",
"releaseYear": 2008
}
}
}
Try Requesting Different Fields
That's the power of GraphQL! Try this:
query {
movies {
title
}
}
You only get title back, nothing else. No over-fetching!
Understanding the Request/Response
Let's look at what's happening under the hood:
Project Structure So Far
Your project should look like this:
moviedb/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/graphqlguy/moviedb/
│ │ │ ├── MoviedbApplication.java
│ │ │ └── movie/
│ │ │ ├── Movie.java
│ │ │ └── MovieController.java
│ │ └── resources/
│ │ ├── application.yaml
│ │ └── graphql/
│ │ └── schema.graphqls
│ └── test/
├── pom.xml
└── mvnw
Exercises
Before moving to the next class, try these exercises:
Exercise 1: Add a New Field
Add a rating field (type Float) to the Movie type and model. Update your sample data with ratings.
Solution
schema.graphqls:
type Movie {
id: ID!
title: String!
releaseYear: Int!
genre: String!
rating: Float!
}
Movie.java: Add a rating field and update the constructor.
MovieController.java: Update sample data:
new Movie(1L, "The Shawshank Redemption", 1994, "Drama", 9.3)
Exercise 2: Add a New Query
Add a query called moviesByGenre(genre: String!): [Movie!]! that filters movies by genre.
Solution
schema.graphqls:
type Query {
# ... existing queries
moviesByGenre(genre: String!): [Movie!]!
}
MovieController.java:
@QueryMapping
public List<Movie> moviesByGenre(@Argument String genre) {
return movies.stream()
.filter(m -> m.getGenre().equalsIgnoreCase(genre))
.toList();
}
Exercise 3: Explore GraphiQL
Use the "Docs" panel on the right side of GraphiQL to explore your schema. Click on types to see their fields.
Common Issues and Solutions
Issue: Schema file not found
Error: No schema found
Solution: Make sure your schema file is at src/main/resources/graphql/schema.graphqls (note the .graphqls extension)
Issue: Query returns null
Error: Field returns null unexpectedly
Solution: Check that your method name matches the schema field name, or use @QueryMapping("fieldName")
Issue: Port already in use
Error: Port 8080 already in use
Solution: Either stop the other application or change the port in application.yaml:
server:
port: 8081
Summary
In this class, you learned:
- GraphQL is a query language that lets clients request exactly the data they need
- Spring GraphQL integrates seamlessly with Spring Boot
- The schema (
.graphqlsfile) defines your API contract @QueryMappingconnects schema fields to Java methods@Argumentinjects GraphQL arguments into your resolvers- GraphiQL provides an interactive way to test your API
What's Next?
In Class 2: Schema Design & Relationships, we'll dive deeper into:
- Enums for type-safe values (like movie genres)
- Relationships between types (movies have directors)
- JPA & H2 for real data persistence
- The
Personmodel and many-to-many relationships
Your Movie API is about to get a lot more interesting!