CodeGym /Courses /Module 5. Spring /Lecture 295: Hands-on: Asynchronous Calls in GraphQL usin...

Lecture 295: Hands-on: Asynchronous Calls in GraphQL using `CompletableFuture`

Module 5. Spring
Level 16 , Lesson 4
Available

CompletableFuture is a powerful tool from the Java library that gives a convenient API for working with async tasks. Its key advantages:

  • Lets you run tasks on separate threads without blocking the main thread.
  • Supports building chains of dependent tasks.
  • Makes it easier to run async operations and handle their results.

In the context of GraphQL, CompletableFuture allows us to:

  1. Handle requests in parallel (for example, fetch data from multiple sources).
  2. Avoid blocking server resources while waiting for external API responses.

Step 1: Project setup

To work with CompletableFuture in GraphQL you'll need:

  • A Spring Boot app with Spring GraphQL already configured.
  • Basic entities and Data Fetchers set up in your GraphQL project.

If you don't have a project yet, create a minimal structure:


spring init --dependencies=web,graphql,data-jpa,h2 demo-graphql

Add dependencies to pom.xml (if not already added):


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-graphql</artifactId>
</dependency>

Step 2: Schema for async GraphQL

In schema.graphqls add a query that returns a list of users with their orders:


type User {
    id: ID!
    name: String!
    orders: [Order]!
}

type Order {
    id: ID!
    description: String!
}

type Query {
    users: [User]!
}

Step 3: Implementing asynchronous Data Fetchers

1. Simulating data access

For simplicity we'll create two Service components:

  • One to get users (UserService).
  • Another to get orders (OrderService).

@Service
public class UserService {

    public List<User> getUsers() {
        // Simulate fetching data
        return List.of(
                new User(1L, "Alice"),
                new User(2L, "Bob")
        );
    }
}

2. Defining Data Fetchers

In our GraphQL API we need to:

  1. Return the list of users.
  2. For each user, attach an async call to fetch their orders.

@Component
public class GraphQLDataFetcher {

    private final UserService userService;
    private final OrderService orderService;

    public GraphQLDataFetcher(UserService userService, OrderService orderService) {
        this.userService = userService;
        this.orderService = orderService;
    }

    @QueryMapping
    public List<User> users() {
        return userService.getUsers();
    }

    @SchemaMapping(typeName = "User", field = "orders")
    public CompletableFuture<List<Order>> orders(User user) {
        // Asynchronous call to fetch orders
        return orderService.getOrdersForUser(user.getId());
    }
}

What's happening here?

  1. The users method returns the list of users.
  2. For each user the orders method is invoked, which asynchronously loads orders for that specific user.

Step 4: Testing async behavior

1. Enabling GraphQL Playground

Add to application.properties:


spring.graphql.graphiql.enabled=true
spring.graphql.graphiql.path=/graphiql

Now you can test your GraphQL API via the web UI at http://localhost:8080/graphiql.

2. Running a query

Send the following query:


{
  users {
    id
    name
    orders {
      id
      description
    }
  }
}

Expected result:


{
  "data": {
    "users": [
      {
        "id": "1",
        "name": "Alice",
        "orders": [
          { "id": "1", "description": "Order 1 for user 1" },
          { "id": "2", "description": "Order 2 for user 1" }
        ]
      },
      {
        "id": "2",
        "name": "Bob",
        "orders": [
          { "id": "1", "description": "Order 1 for user 2" },
          { "id": "2", "description": "Order 2 for user 2" }
        ]
      }
    ]
  }
}

Step 5: Testing async code

Unit tests for CompletableFuture

Create a simple test to verify async behavior:


@SpringBootTest
class OrderServiceTest {

    @Autowired
    private OrderService orderService;

    @Test
    void testGetOrdersForUser() throws Exception {
        CompletableFuture<List<Order>> future = orderService.getOrdersForUser(1L);

        // We block only in tests
        List<Order> orders = future.get(); // Blocking only for tests
        assertEquals(2, orders.size());
        assertEquals("Order 1 for user 1", orders.get(0).getDescription());
    }
}

Integration tests for GraphQL

Use MockMvc to test GraphQL queries:


@WebMvcTest(GraphQLDataFetcher.class)
class GraphQLDataFetcherTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void testUsersQuery() throws Exception {
        String query = "{ users { id, name, orders { id, description } } }";
        mockMvc.perform(post("/graphql")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"query\": \"" + query + "\"}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.data.users").isArray());
    }
}

Step 6: Practice and analysis

How to improve performance?

  • Make sure heavy operations run asynchronously.
  • Use an ExecutorService to manage threads.
  • Add caching if data rarely changes.

Common mistakes:

  1. Blocking the main thread. Use CompletableFuture correctly to avoid blocking.
  2. Incorrect exception handling. Always handle errors with exceptionally to deal with failures.
  3. Unclosed resources. Async operations can leave connections open. Make sure all resources are closed properly.
return CompletableFuture.supplyAsync(() -> {
    // Your logic code
}).exceptionally(ex -> {
    // Log errors
    return Collections.emptyList();
});

Your GraphQL API is now asynchronous, performant, and ready to interact with external APIs or slow systems. You can be proud — you've taken a step toward building a high-performance app of the future!

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION