Async to Threads
Async to Threads
Introduction
In this lesson, you'll learn about async to threads in Java. Coming from TypeScript, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In TypeScript, you're familiar with async to threads.
Java has its own approach to async to threads, which we'll explore step by step.
The Java Way
Let's see how Java handles this concept. Here's a typical example:
import java.util.concurrent.CompletableFuture;
import java.net.http.*;
CompletableFuture<String> fetchUser(int id) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("/api/users/" + id))
.build();
return client.sendAsync(request, BodyHandlers.ofString())
.thenApply(HttpResponse::body);
}
CompletableFuture<Void> loadAll() {
CompletableFuture<String> u1 = fetchUser(1);
CompletableFuture<String> u2 = fetchUser(2);
return CompletableFuture.allOf(u1, u2);
}Comparing to TypeScript
Here's how you might have written similar code in TypeScript:
async function fetchUser(id: number): Promise<User> {
const res = await fetch(`/api/users/${id}`);
return res.json();
}
async function loadAll() {
const [u1, u2] = await Promise.all([
fetchUser(1),
fetchUser(2),
]);
return [u1, u2];
}You may be used to different syntax or behavior.
TypeScript async/await is single-threaded event loop; Java is truly multi-threaded
You may be used to different syntax or behavior.
CompletableFuture<T> is the Java equivalent of Promise<T>
You may be used to different syntax or behavior.
CompletableFuture.allOf() is the Java equivalent of Promise.all()
You may be used to different syntax or behavior.
Java 21 virtual threads (Project Loom) allow writing blocking code that scales like async
Step-by-Step Breakdown
1. CompletableFuture = Promise
CompletableFuture<T> represents a value that will be available in the future. thenApply() maps the result like .then() in JavaScript.
fetchUser(1).then(user => user.name)fetchUser(1).thenApply(user -> user.getName())2. allOf = Promise.all
CompletableFuture.allOf() waits for all futures to complete. Unlike Promise.all(), it returns CompletableFuture<Void> — you need to call .get() on each future to retrieve results.
const [a, b] = await Promise.all([fetchA(), fetchB()]);CompletableFuture<String> fa = fetchA();
CompletableFuture<String> fb = fetchB();
CompletableFuture.allOf(fa, fb).join();
String a = fa.get();
String b = fb.get();3. Java 21 Virtual Threads
Virtual threads (Java 21+) let you write blocking-style code that the JVM schedules efficiently — like async/await but without the colored function problem.
async function handler(req: Request): Promise<Response> {
const data = await db.query(req.id);
return { data };
}// With virtual threads — just write blocking code:
public Response handler(Request req) throws Exception {
var data = db.query(req.id()); // blocks virtual thread, not OS thread
return new Response(data);
}4. Exception Handling in Async Code
CompletableFuture wraps exceptions in CompletionException. Use .exceptionally() to handle errors, similar to .catch() in Promises.
fetchUser(1).catch(err => console.error(err))fetchUser(1).exceptionally(ex -> {
System.err.println(ex.getMessage());
return null;
});Common Mistakes
When coming from TypeScript, developers often make these mistakes:
- TypeScript async/await is single-threaded event loop; Java is truly multi-threaded
- CompletableFuture<T> is the Java equivalent of Promise<T>
- CompletableFuture.allOf() is the Java equivalent of Promise.all()
Key Takeaways
- CompletableFuture<T> is the Java equivalent of Promise<T>
- CompletableFuture.allOf() mirrors Promise.all()
- Java is truly multi-threaded — async code can run in parallel, not just concurrently
- Java 21 virtual threads allow blocking-style code that scales like async