Async Requests
sendAsync and CompletableFuture.
Async Requests is a free Java Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Java Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Asynchronous Requests
Blocking on every HTTP call wastes threads. The JDK HTTP client offers sendAsync, which returns a CompletableFuture immediately and performs the work on a background executor.
This lets you fire many requests concurrently and compose their results without manually managing threads.
send vs sendAsync
The signatures differ in what they return:
- send returns
HttpResponse<T>and blocks, declaringIOExceptionandInterruptedException. - sendAsync returns
CompletableFuture<HttpResponse<T>>and never blocks; errors surface through the future.
A Basic sendAsync Call
sendAsync takes the same request and BodyHandler as send. The returned future completes when the response is ready.
import java.net.URI;
import java.net.http.*;
import java.util.concurrent.CompletableFuture;
public class Main {
public static void main(String[] args) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://example.com"))
.build();
CompletableFuture<HttpResponse<String>> future =
client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Request sent, future created");
}
}Transforming with thenApply
Use thenApply to transform the result once it arrives — for example, extracting the body from the response.
This runs synchronously on whatever thread completed the previous stage.
import java.util.concurrent.CompletableFuture;
public class Main {
public static void main(String[] args) throws Exception {
CompletableFuture<String> future =
CompletableFuture.completedFuture("OK 200")
.thenApply(body -> "Length=" + body.length());
System.out.println(future.get());
}
}Consuming with thenAccept
When you only want a side effect and no return value, use thenAccept. It takes a Consumer and yields a CompletableFuture<Void>.
import java.util.concurrent.CompletableFuture;
public class Main {
public static void main(String[] args) throws Exception {
CompletableFuture<Void> done =
CompletableFuture.completedFuture("payload")
.thenAccept(b -> System.out.println("Received: " + b));
done.get();
}
}Blocking with join
To wait for an async result from main, call join() (unchecked) or get() (checked). In a real server you would chain stages instead of blocking.
join() wraps failures in an unchecked CompletionException.
import java.util.concurrent.CompletableFuture;
public class Main {
public static void main(String[] args) {
CompletableFuture<Integer> f =
CompletableFuture.supplyAsync(() -> 21).thenApply(n -> n * 2);
int result = f.join();
System.out.println("Result: " + result);
}
}Handling Errors with exceptionally
exceptionally provides a fallback value when a stage fails, recovering the pipeline.
This is how you keep an async HTTP flow from dying on a network error.
import java.util.concurrent.CompletableFuture;
public class Main {
public static void main(String[] args) {
CompletableFuture<String> f = CompletableFuture
.<String>supplyAsync(() -> { throw new RuntimeException("boom"); })
.exceptionally(ex -> "fallback: " + ex.getMessage());
System.out.println(f.join());
}
}Combining Two Requests
thenCombine waits for two independent futures and merges their results. With async HTTP this fetches two endpoints in parallel and joins the bodies.
import java.util.concurrent.CompletableFuture;
public class Main {
public static void main(String[] args) {
CompletableFuture<String> a = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> b = CompletableFuture.supplyAsync(() -> "World");
String combined = a.thenCombine(b, (x, y) -> x + " " + y).join();
System.out.println(combined);
}
}Firing Many in Parallel
To run a batch of requests, collect each future into a list, then use CompletableFuture.allOf(...) to wait for all of them.
allOf returns CompletableFuture<Void>; you read individual results afterward.
import java.util.*;
import java.util.concurrent.CompletableFuture;
public class Main {
public static void main(String[] args) {
List<CompletableFuture<Integer>> futures = new ArrayList<>();
for (int i = 1; i <= 3; i++) {
int n = i;
futures.add(CompletableFuture.supplyAsync(() -> n * n));
}
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
for (var f : futures) System.out.println(f.join());
}
}Async Pattern Summary
A typical async HTTP pipeline looks like this:
client.sendAsync(req, BodyHandlers.ofString()).thenApply(HttpResponse::body)to get the body.thenAccept(System.out::println)to consume it.exceptionally(ex -> ...)to recover
No thread is ever blocked waiting on the network.
When to Go Async
Choose sendAsync when you make many concurrent calls, fan out to several services, or run inside a reactive/non-blocking server. Choose send for simple scripts and one-off calls where blocking is fine.
Quick Check
Check your understanding of async requests.
Recap
You learned to make non-blocking HTTP calls:
- sendAsync returns a
CompletableFutureinstead of blocking. - thenApply / thenAccept transform and consume the result.
- exceptionally supplies a fallback on failure.
- thenCombine merges two parallel calls; allOf waits for a batch.
- Async fits high-concurrency and reactive servers; sync fits simple scripts.
Frequently asked questions
Is the “Async Requests” lesson free?
Yes — the full text of “Async Requests” is free to read here on the web, and the Java Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Java Academy course, upgrade to CoddyKit PRO.
What will I learn in “Async Requests”?
sendAsync and CompletableFuture. You practise Java Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Java Academy?
No prior experience is required. Java Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Async Requests” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Java Academy lesson?
Yes. Every Java Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.