Back to Java
2026-02-188 min read

Async Fetch API (Java)

Learn Async Fetch API (Java) step by step with clear examples and exercises.

Why This Matters

In today's fast-paced digital world, efficient and responsive web applications are crucial for providing a smooth user experience. The Async Fetch API in Java allows developers to make non-blocking HTTP requests, ensuring smoother user experiences by reducing latency and improving performance. By making asynchronous calls, the main thread is free to continue processing other tasks while waiting for a response from the server. This results in more responsive applications and improved user experiences.

Prerequisites

Before diving into the Async Fetch API, it is crucial to have a solid understanding of the following concepts:

  1. Java basics (variables, methods, classes, and exception handling)
  2. Synchronous HTTP requests using Java's HttpURLConnection class
  3. Concurrency in Java with threads and ExecutorService
  • Understanding the Thread class and its lifecycle
  • Using Runnable interfaces for task execution
  • Synchronization mechanisms like locks and wait/notify
  1. Callable and Future interfaces for asynchronous task execution
  • Defining tasks that return a result and can throw exceptions
  • Waiting for the completion of a task with Futures
  1. Java 9 or later versions (the Async Fetch API was introduced in Java 9)

Core Concept

The Async Fetch API leverages the java.util.concurrent package to simplify asynchronous HTTP requests. It allows developers to make non-blocking HTTP requests, freeing up the main thread to continue processing other tasks while waiting for a response.

The key classes involved in this process are:

  1. ExecutorService: responsible for managing a pool of threads that execute Callable tasks asynchronously
  2. Callable: an interface for defining tasks that return a result and can throw exceptions
  3. Future: a handle to the result of a Callable task, allowing developers to wait for its completion or check its status
  4. CompletableFuture: an advanced class that simplifies working with Futures and provides additional functionality like chaining and composition
  5. HttpURLConnection: the class used to make HTTP requests in Java (prerequisite knowledge)

Subheadings under Core Concept

  1. Creating and managing ExecutorService instances
  2. Defining Callable tasks for asynchronous HTTP requests
  3. Managing Futures and waiting for task completion
  4. Using CompletableFuture for chaining and composition
  5. Implementing error handling and exception propagation

Worked Example

To illustrate how the Async Fetch API works in Java, let's create a simple example where we make multiple asynchronous HTTP requests to fetch data from different URLs.

import java.net.URI;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toList;

public class AsyncFetchExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newCachedThreadPool();

List<URI> urls = List.of(
new URI("https://example1.com"),
new URI("https://example2.com"),
new URI("https://example3.com")
);

List<Callable<String>> tasks = urls.stream()
.map(AsyncFetchExample::createHttpRequestTask)
.toList();

List<Future<String>> futures = executor.invokeAll(tasks);

Map<URI, Future<String>> uriToFutureMap = futures.stream().collect(Collectors.toMap(Future::get, f -> f));

int totalCharacters = 0;
double averageLineLength = 0;
int lineCount = 0;

for (URI uri : urls) {
String response = uriToFutureMap.get(uri).get();
Scanner scanner = new Scanner(response);
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
totalCharacters += line.length();
lineCount++;
averageLineLength += line.length();
}
}

System.out.println("Total number of characters: " + totalCharacters);
System.out.println("Average line length: " + (averageLineLength / lineCount));

executor.shutdown();
}

private static Callable<String> createHttpRequestTask(URI url) {
return () -> {
try (HttpURLConnection connection = (HttpURLConnection) url.toURL().openConnection()) {
connection.setRequestMethod("GET");
connection.connect();

int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
return new Scanner(connection.getInputStream()).useDelimiter(Pattern.compile("Z")).next();
} else {
throw new RuntimeException("Failed to fetch data from URL: " + url);
}
} catch (IOException e) {
throw new RuntimeException("Failed to fetch data from URL: " + url, e);
}
};
}
}

In this example, we create an ExecutorService, which manages a pool of threads. We then define a list of URLs and create a list of Callable tasks that perform HTTP requests to these URLs. The invokeAll() method executes all tasks concurrently and returns a list of Futures representing the results.

We store each Future in a Map for easy access later, then process the fetched data by iterating through the lines of each response, calculating the total number of characters and average line length. Finally, we print out the results and shut down the executor service.

Subheadings under Worked Example

  1. Creating an ExecutorService for managing threads
  2. Defining Callable tasks for asynchronous HTTP requests
  3. Processing fetched data from multiple URLs concurrently
  4. Handling exceptions and errors in the example
  5. Shutting down the ExecutorService after task completion

Common Mistakes

  1. Forgetting to call executor.shutdown(): This can lead to memory leaks as threads in the pool may not be properly terminated.
  2. Not handling exceptions properly: It's essential to catch and handle exceptions to ensure your application remains stable and responsive.
  3. Misusing Executors: Using a fixed-size ExecutorService instead of a cached one can lead to performance issues, as threads may be wasted if they are not reused often enough.
  4. Not using CompletableFuture for chaining or composition: While it's possible to work with Futures directly, using CompletableFuture provides more flexibility and simplifies complex asynchronous operations.
  5. Ignoring response codes: Always check the HTTP response code to ensure that the request was successful and handle errors appropriately.
  6. Not properly closing resources (e.g., InputStreams): Ensure you close all resources, such as InputStreams, when they are no longer needed to prevent resource leaks.
  7. Overusing threads: Be mindful of creating too many threads, as this can lead to excessive resource consumption and potential performance issues.

Subheadings under Common Mistakes

  1. Properly handling exceptions in Callable tasks
  2. Using CompletableFuture for error handling
  3. Closing resources in Callable tasks
  4. Managing thread pool sizes effectively
  5. Handling timeouts and retries

Practice Questions

  1. Modify the example above to fetch data from five URLs and print out a summary of the fetched data, including the total number of characters, the average length of each line, and the response time for each request.
  2. Implement a method called fetchDataAsync() that accepts a list of URLs as input, returns a CompletableFuture> containing the fetched data, and handles exceptions appropriately. The method should also return a CompletableFuture representing the completion of the operation.
  3. Use the fetchDataAsync() method to fetch data from ten URLs concurrently, print out the results, and measure the time taken for the operation using the System.nanoTime() method. The method should return a CompletableFuture representing the completion of the operation.
  4. Implement a method called fetchDataWithTimeoutAsync() that accepts a list of URLs and a timeout (in milliseconds) as input, returns a CompletableFuture> containing the fetched data, and handles exceptions appropriately. The method should also return a CompletableFuture representing the completion of the operation. If a request takes longer than the specified timeout, it should be cancelled, and an exception should be thrown.
  5. Use the fetchDataWithTimeoutAsync() method to fetch data from five URLs with a timeout of 10 seconds each, print out the results, and measure the time taken for the operation using the System.nanoTime() method. The method should return a CompletableFuture representing the completion of the operation.
  6. Modify the worked example to handle potential connection issues by retrying failed requests up to three times before giving up.
  7. Implement a method called fetchDataWithRetriesAsync() that accepts a list of URLs and a maximum number of retries as input, returns a CompletableFuture> containing the fetched data, and handles exceptions appropriately. The method should also return a CompletableFuture representing the completion of the operation. If a request fails more than the specified number of times, it should give up and throw an exception.
  8. Use the fetchDataWithRetriesAsync() method to fetch data from five URLs with a maximum of three retries, print out the results, and measure the time taken for the operation using the System.nanoTime() method. The method should return a CompletableFuture representing the completion of the operation.

FAQ

Q: Can I use Async Fetch API with Java 8 or earlier versions?

A: No, the Async Fetch API was introduced in Java 9. If you're working with an older version, consider using third-party libraries like OkHttpClient for asynchronous HTTP requests.

Q: What are some best practices when working with ExecutorService and Futures?

A: Some best practices include limiting the number of threads in the pool to avoid excessive resource consumption, properly shutting down the executor service after all tasks have completed, and using CompletableFuture for chaining or composition.

Q: How can I handle timeouts when making asynchronous HTTP requests with Java's Async Fetch API?

A: You can use a Callable that returns an overloaded CompletableFuture.supplyAsync() method, which accepts a Supplier, a Callable, and a BiConsumer, TimeoutHandler>. The timeout handler can be used to cancel the task if it takes too long to complete.

Q: Is it possible to make asynchronous HTTP requests using Java's Async Fetch API without creating an ExecutorService?

A: No, you must create an ExecutorService to manage the threads that execute your Callable tasks asynchronously. However, some libraries like OkHttpClient provide higher-level abstractions for making asynchronous HTTP requests without requiring explicit thread management.

Q: How can I handle retries when making asynchronous HTTP requests with Java's Async Fetch API?

A: You can use a loop to retry the request if it fails, or you can use a library like RetryBuilder to manage retries more easily. In both cases, be mindful of not creating too many retries, as this can lead to excessive resource consumption and potential performance issues.

Q: What are some other libraries for making asynchronous HTTP requests in Java?

A: Some popular libraries for making asynchronous HTTP requests in Java include OkHttpClient, Apache HttpClient, and Netty. These libraries provide higher-level abstractions for making HTTP requests, often with more features than the built-in Async Fetch API.

Async Fetch API (Java) | Java | XQA Learn