Während REST-APIs für viele Anwendungsfälle die richtige Wahl bleiben, stoßen sie bei datenintensiven Client-Anwendungen an ihre Grenzen: Overfetching (der Client erhält mehr Daten als benötigt) und Underfetching (der Client muss mehrere Endpoints nacheinander aufrufen) sind inhärente Probleme. GraphQL löst diese, indem der Client exakt die benötigten Felder anfordert — nicht mehr und nicht weniger.

Spring for GraphQL

Spring for GraphQL ist das offizielle Spring-Modul für die Integration von GraphQL in Spring-Boot-Anwendungen. Es baut auf der etablierten Java-Bibliothek GraphQL Java auf und fügt Spring-typische Konzepte wie Dependency Injection und deklarative Konfiguration hinzu.

<em><!-- Maven-Dependency für Spring Boot 4.1.x --></em>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
Code-Sprache: HTML, XML (xml)

Schema-first-Entwicklung

GraphQL-APIs werden schema-first definiert. Das Schema beschreibt die verfügbaren Typen, Queries und Mutationen:

<em># src/main/resources/graphql/schema.graphqls</em>
type Book {
    id: ID!
    title: String!
    author: Author!
    pages: Int
}

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

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

type Mutation {
    createBook(title: String!, authorId: ID!, pages: Int): Book!
}
Code-Sprache: HTML, XML (xml)

Controller als Data Fetcher

In Spring for GraphQL werden keine REST-Endpoints definiert, sondern Data Fetcher, die das Schema mit Daten versorgen. Der Ansatz ist gewohnt spring-ig:

import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.data.method.annotation.SchemaMapping;
import org.springframework.stereotype.Controller;

@Controller
public class BookController {

    private final BookService bookService;

    public BookController(BookService bookService) {
        this.bookService = bookService;
    }

    @QueryMapping
    public Book bookById(@Argument Long id) {
        return bookService.findById(id);
    }

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

    @SchemaMapping(typeName = "Author", field = "books")
    public List<Book> booksForAuthor(Author author) {
        return bookService.findByAuthorId(author.id());
    }
}
Code-Sprache: PHP (php)

Entscheidend: Der Client bestimmt, welche Felder er haben möchte. Eine Query wie { bookById(id: 1) { title author { name } } } liefert nur Titel und Autorname — keine unnötigen Daten.

Testen mit GraphQlTester

Spring for GraphQL bringt exzellente Test-Werkzeuge mit. Mit HttpGraphQlTester können GraphQL-Requests über HTTP gegen einen laufenden Server getestet werden:

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.graphql.test.tester.HttpGraphQlTester;
import org.springframework.test.web.reactive.server.WebTestClient;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class BookControllerTest {

    @Autowired
    private WebTestClient.Builder clientBuilder;

    @Test
    void shouldReturnBookById() {
        HttpGraphQlTester tester = HttpGraphQlTester.builder(clientBuilder).build();

        tester.document("""
            query {
                bookById(id: 1) {
                    title
                    author { name }
                }
            }
            """)
            .execute()
            .path("bookById.title")
            .entity(String.class)
            .isEqualTo("Der Prozess");
    }
}

Alternativ kann ein HttpGraphQlTester-Bean über @AutoConfigureGraphQlTester automatisch konfiguriert und per @Autowired injiziert werden. Für Tests ohne laufenden Server steht ExecutionGraphQlServiceTester zur Verfügung.

N+1-Problem und @BatchMapping

Ein häufiges Problem bei GraphQL ist das N+1-Problem: Wird für jeden Autor dessen Bücherliste einzeln geladen, entstehen Hunderte unnötiger Datenbankabfragen. Spring for GraphQL bietet dafür @BatchMapping:

@BatchMapping(typeName = "Author", field = "books")
public Mono<Map<Author, List<Book>>> booksForAuthors(List<Author> authors) {
    return bookService.findByAuthorIds(authors.stream()
        .map(Author::id).toList());
}
Code-Sprache: CSS (css)

Statt N einzelner Queries wird eine einzige Batch-Query ausgeführt — und das Framework verteilt die Ergebnisse korrekt auf die Autoren. Die Methode kann auch imperativ ein Map<Author, List<Book>> zurückgeben; Mono ist vor allem bei rein asynchronen Aufrufen sinnvoll.

Fazit

Spring for GraphQL macht die Arbeit mit GraphQL in Spring Boot zugänglich und entwicklerfreundlich. Die Kombination aus Schema-first-Ansatz, vertrauten Controller-Konventionen und mächtigen Test-Tools senkt die Einstiegshürde erheblich. Für Projekte, die flexible, client-gesteuerte APIs benötigen, ist GraphQL eine ausgezeichnete Ergänzung zum klassischen REST-Stack.