java
49 lines · 8 steps
Batching GraphQL queries with DataLoaders in Spring
Register batch loaders that collapse per-item GraphQL fetches into single bulk queries, killing the N+1 problem.
Explained by
highlit
1@Configuration
2public class DataLoaderConfig {
3
4 @Bean
5 public BatchLoaderRegistry batchLoaderRegistry() {
6 return new DefaultBatchLoaderRegistry();
7 }
8
9 @Bean
10 public ApplicationRunner registerLoaders(BatchLoaderRegistry registry,
11 AuthorRepository authorRepository,
12 CommentRepository commentRepository) {
13 return args -> {
14 registry.forTypePair(Long.class, Author.class)
15 .registerMappedBatchLoader((authorIds, env) ->
16 Mono.fromSupplier(() ->
17 authorRepository.findAllById(authorIds).stream()
18 .collect(Collectors.toMap(Author::getId, Function.identity())))
19 .subscribeOn(Schedulers.boundedElastic()));
20
21 registry.forName("commentsByPostLoader")
22 .registerMappedBatchLoader((postIds, env) ->
23 Mono.fromSupplier(() ->
24 commentRepository.findByPostIdIn(postIds).stream()
25 .collect(Collectors.groupingBy(Comment::getPostId)))
26 .subscribeOn(Schedulers.boundedElastic()));
27 };
28 }
29}
30
31@Controller
32class PostController {
33
34 @SchemaMapping(typeName = "Post", field = "author")
35 public CompletableFuture<Author> author(Post post, DataLoader<Long, Author> loader) {
36 return loader.load(post.getAuthorId());
37 }
38
39 @SchemaMapping(typeName = "Post", field = "comments")
40 public CompletableFuture<List<Comment>> comments(
41 Post post,
42 @Argument int limit,
43 DataLoader<Long, List<Comment>> commentsByPostLoader) {
44 return commentsByPostLoader.load(post.getId())
45 .thenApply(comments -> comments == null
46 ? List.of()
47 : comments.stream().limit(limit).toList());
48 }
49}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1A BatchLoaderRegistry lets you fetch related entities in one bulk query instead of one per parent, eliminating N+1 database hits.
- 2Wrapping blocking repository calls in Mono.fromSupplier on boundedElastic keeps the reactive pipeline non-blocking.
- 3Field resolvers return CompletableFuture from a loader so Spring GraphQL can defer and batch the actual data access.
Related explainers
java
@Component @Converter public class EncryptedStringConverter implements AttributeConverter<String, String> {
Transparent column encryption in Spring & JPA
encryption
aes-gcm
jpa-converter
Advanced
10 steps
java
package com.acme.billing.config; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties;
Feature-flagged beans with Spring @ConditionalOnProperty
feature-flags
conditional-beans
strategy-pattern
Intermediate
5 steps
java
public static Map<String, String> parseCookieHeader(String header) { Map<String, String> cookies = new LinkedHashMap<>(); if (header == null || header.isBlank()) { return cookies;
Parsing an HTTP Cookie header in Java
string-parsing
http
url-decoding
Intermediate
6 steps
java
public class TimedSocketReader { private static final int READ_TIMEOUT_MS = 5_000; private static final int CONNECT_TIMEOUT_MS = 3_000;
Reading a socket with connect and read timeouts
sockets
timeouts
io
Intermediate
8 steps
java
public final class EncodingDetector { public enum Encoding { UTF_8, UTF_16LE, UTF_16BE, UTF_32LE, UTF_32BE, ASCII, UNKNOWN
Detecting text encoding from raw bytes in Java
byte-manipulation
encoding-detection
bitwise-operations
Intermediate
8 steps
java
public final class ImportOrganizer { private static final Pattern IMPORT_LINE = Pattern.compile("^import\\s+(static\\s+)?([\\w.]+(?:\\.\\*)?)\\s*;\\s*$");
Sorting Java imports with a regex pass
regex
sorting
text-processing
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/batching-graphql-queries-with-dataloaders-in-spring-explained-java-a71e/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.