Starter for building GraphQL applications with Spring GraphQL
—
Spring Boot GraphQL Starter provides automatic integration with Spring Data repositories, enabling seamless database access through GraphQL without manual DataFetcher configuration. The integration supports Query by Example, Querydsl, and pagination patterns.
Automatic integration with Spring Data repositories that implement QueryByExampleExecutor.
@AutoConfiguration(after = GraphQlAutoConfiguration.class)
@ConditionalOnClass({ GraphQL.class, QueryByExampleDataFetcher.class, QueryByExampleExecutor.class })
@ConditionalOnBean(GraphQlSource.class)
public class GraphQlQueryByExampleAutoConfiguration {
@Bean
GraphQlSourceBuilderCustomizer queryByExampleDataFetcherCustomizer(
ObjectProvider<QueryByExampleExecutor<?>> executors,
ObjectProvider<ReactiveQueryByExampleExecutor<?>> reactiveExecutors
) {
return QueryByExampleDataFetcher.autoRegistrationConfigurer(
executors.orderedStream().toList(),
reactiveExecutors.orderedStream().toList()
);
}
}// Repository with Query by Example support
public interface BookRepository extends JpaRepository<Book, String>,
QueryByExampleExecutor<Book> {
// Spring Data will automatically generate DataFetchers
}
// Reactive repository support
public interface ReactiveBookRepository extends ReactiveCrudRepository<Book, String>,
ReactiveQueryByExampleExecutor<Book> {
// Reactive DataFetchers automatically generated
}type Query {
# Automatically mapped to QueryByExampleExecutor.findAll(Example)
books(where: BookFilter): [Book]
# Automatically mapped to QueryByExampleExecutor.findOne(Example)
book(where: BookFilter): Book
}
input BookFilter {
title: String
author: String
isbn: String
publishedYear: Int
}@Controller
public class BookController {
// No need to implement - auto-configured by Query by Example
// GraphQL query: books(where: {author: "Jane Doe"}) automatically
// becomes: bookRepository.findAll(Example.of(Book.builder().author("Jane Doe").build()))
}Automatic integration with Querydsl-enabled repositories for type-safe dynamic queries.
@AutoConfiguration(after = GraphQlAutoConfiguration.class)
@ConditionalOnClass({ GraphQL.class, QuerydslDataFetcher.class, QuerydslPredicateExecutor.class })
@ConditionalOnBean(GraphQlSource.class)
public class GraphQlQuerydslAutoConfiguration {
@Bean
GraphQlSourceBuilderCustomizer querydslDataFetcherCustomizer(
ObjectProvider<QuerydslPredicateExecutor<?>> executors,
ObjectProvider<ReactiveQuerydslPredicateExecutor<?>> reactiveExecutors
) {
return QuerydslDataFetcher.autoRegistrationConfigurer(
executors.orderedStream().toList(),
reactiveExecutors.orderedStream().toList()
);
}
}// Querydsl-enabled repository
public interface AuthorRepository extends JpaRepository<Author, String>,
QuerydslPredicateExecutor<Author> {
// Type-safe query generation
}
// Reactive Querydsl support
public interface ReactiveAuthorRepository extends ReactiveCrudRepository<Author, String>,
ReactiveQuerydslPredicateExecutor<Author> {
}// Generated Querydsl Q-types are automatically used
// GraphQL query: authors(where: {name: {like: "John%"}})
// becomes: authorRepository.findAll(QAuthor.author.name.like("John%"))Built-in pagination support using Spring Data's pagination abstractions.
@ConditionalOnClass(ScrollPosition.class)
@Configuration(proxyBeanMethods = false)
static class GraphQlDataAutoConfiguration {
@Bean
@ConditionalOnMissingBean
EncodingCursorStrategy<ScrollPosition> cursorStrategy() {
return CursorStrategy.withEncoder(
new ScrollPositionCursorStrategy(),
CursorEncoder.base64()
);
}
@Bean
GraphQlSourceBuilderCustomizer cursorStrategyCustomizer(CursorStrategy<?> cursorStrategy) {
ConnectionFieldTypeVisitor visitor = ConnectionFieldTypeVisitor.create(List.of(
new WindowConnectionAdapter(scrollCursorStrategy),
new SliceConnectionAdapter(scrollCursorStrategy)
));
return builder -> builder.typeVisitors(List.of(visitor));
}
}type Query {
books(first: Int, after: String, last: Int, before: String): BookConnection
}
type BookConnection {
edges: [BookEdge]
pageInfo: PageInfo!
}
type BookEdge {
node: Book!
cursor: String!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}public interface BookRepository extends JpaRepository<Book, String> {
// Automatically supports GraphQL Connection pagination
Page<Book> findAll(Pageable pageable);
// Slice-based pagination for large datasets
Slice<Book> findByAuthor(String author, Pageable pageable);
// Window-based pagination with ScrollPosition
Window<Book> findByPublishedYearAfter(int year, ScrollPosition position);
}Manual DataFetcher registration alongside automatic repository integration.
@Component
public class CustomDataFetcherConfigurer implements RuntimeWiringConfigurer {
@Override
public void configure(RuntimeWiring.Builder builder) {
builder
.type("Query", typeWiring -> typeWiring
.dataFetcher("searchBooks", searchBooksDataFetcher())
.dataFetcher("recommendedBooks", recommendedBooksDataFetcher())
)
.type("Book", typeWiring -> typeWiring
.dataFetcher("reviews", bookReviewsDataFetcher())
);
}
private DataFetcher<List<Book>> searchBooksDataFetcher() {
return environment -> {
String query = environment.getArgument("query");
return searchService.searchBooks(query);
};
}
}Integration with DataLoader for efficient batch loading.
@Component
public class BookDataLoaders {
@Autowired
private BatchLoaderRegistry batchLoaderRegistry;
@PostConstruct
public void registerDataLoaders() {
batchLoaderRegistry.forName("authorLoader")
.registerBatchLoader((keys, environment) -> {
// Batch load authors by IDs
return authorService.findAllById(keys);
});
}
}@Controller
public class BookController {
@SchemaMapping(typeName = "Book", field = "author")
public CompletableFuture<Author> author(Book book, DataLoader<String, Author> authorLoader) {
return authorLoader.load(book.getAuthorId());
}
@BatchMapping(typeName = "Book", field = "reviews")
public Mono<Map<Book, List<Review>>> reviews(List<Book> books) {
List<String> bookIds = books.stream().map(Book::getId).toList();
return reviewService.findByBookIds(bookIds)
.collectMultimap(review -> findBookById(books, review.getBookId()));
}
}Automatic transaction management for GraphQL operations.
@Controller
@Transactional(readOnly = true) // Default read-only transactions
public class BookController {
@QueryMapping
public List<Book> books() {
return bookRepository.findAll(); // Read-only transaction
}
@MutationMapping
@Transactional // Read-write transaction for mutations
public Book addBook(@Argument BookInput input) {
Book book = new Book(input.getTitle(), input.getAuthor());
return bookRepository.save(book);
}
@MutationMapping
@Transactional(rollbackFor = Exception.class)
public Book updateBook(@Argument String id, @Argument BookInput input) {
Book book = bookRepository.findById(id)
.orElseThrow(() -> new BookNotFoundException(id));
book.setTitle(input.getTitle());
book.setAuthor(input.getAuthor());
return bookRepository.save(book);
}
}Integration with Bean Validation for input validation.
// Input validation with Bean Validation
public class BookInput {
@NotBlank
@Size(min = 1, max = 255)
private String title;
@NotBlank
@Size(min = 1, max = 100)
private String author;
@Pattern(regexp = "\\d{3}-\\d{10}")
private String isbn;
// Getters and setters
}
@Controller
public class BookController {
@MutationMapping
public Book addBook(@Argument @Valid BookInput input) {
// Validation automatically applied
return bookService.create(input);
}
}Data access error handling and mapping to GraphQL errors.
@Component
public class DataAccessExceptionResolver implements DataFetcherExceptionResolver {
@Override
public Mono<List<GraphQLError>> resolveException(DataFetcherExceptionResolverEnvironment env) {
Throwable exception = env.getException();
if (exception instanceof EntityNotFoundException) {
return Mono.just(List.of(
GraphqlErrorBuilder.newError(env)
.message("Entity not found: " + exception.getMessage())
.errorType(ErrorType.DataFetchingException)
.build()
));
}
if (exception instanceof DataIntegrityViolationException) {
return Mono.just(List.of(
GraphqlErrorBuilder.newError(env)
.message("Data integrity violation")
.errorType(ErrorType.ValidationError)
.build()
));
}
return Mono.empty();
}
}@Controller
public class BookController {
@QueryMapping
public List<Book> books(DataFetchingEnvironment environment) {
// Use GraphQL selection set for JPA projections
Set<String> fields = getSelectedFields(environment);
if (fields.contains("author")) {
return bookRepository.findAllWithAuthor(); // Join fetch
} else {
return bookRepository.findAll(); // Simple query
}
}
}// Custom repository methods for optimized queries
public interface BookRepository extends JpaRepository<Book, String> {
@Query("SELECT b FROM Book b JOIN FETCH b.author WHERE b.publishedYear > :year")
List<Book> findRecentBooksWithAuthor(@Param("year") int year);
@Query("SELECT b.id, b.title, a.name FROM Book b JOIN b.author a")
List<BookSummary> findBookSummaries();
}Install with Tessl CLI
npx tessl i tessl/maven-org-springframework-boot--spring-boot-starter-graphql