Async programming in Java
-
Java supports async programming but does not have built-in
async/await syntax (unlike Rust or C#).Async-style work is done with threads, thread pools, and non-blocking result objects (
Future,
CompletableFuture).From Java 21 onward, virtual threads make blocking I/O cheap — you write synchronous-looking code while the JVM schedules thousands of tasks on few OS threads.
See also: Thread creation, Java synchronization.
1. Thread + Runnable — basic background task
-
Oldest approach: spawn a OS thread and return immediately from
main. The caller does not get a result object — use
join() to wait.
public class ThreadAsync {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
System.out.println("background work on " + Thread.currentThread());
});
t.start(); // async — main continues
t.join(); // wait for completion
System.out.println("main done");
}
}
2. ExecutorService — thread pool
-
Submit many tasks to a pool instead of creating a new OS thread per
task. Returns a
Future for each submission.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ExecutorExample {
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newFixedThreadPool(4);
Future<String> future = pool.submit(() -> {
Thread.sleep(500);
return "data from DB";
});
System.out.println("main continues while task runs...");
String result = future.get(); // block until result ready
System.out.println(result);
pool.shutdown();
}
}
3. Future — result of async computation
Future<T> represents a result that may not be ready
yet. Call get() to block and retrieve it (similar to C++
std::future).
import java.util.concurrent.*;
public class FutureExample {
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newSingleThreadExecutor();
Future<Integer> future = pool.submit(() -> {
Thread.sleep(300);
return 42;
});
System.out.println("waiting...");
System.out.println("answer = " + future.get());
pool.shutdown();
}
}
4. CompletableFuture — chain async steps
CompletableFuture (Java 8+) supports callbacks,
composition, and combining multiple async operations without blocking
main at every step — closest to modern async/await style.
import java.util.concurrent.CompletableFuture;
public class CompletableFutureExample {
static CompletableFuture<String> fetchUser() {
return CompletableFuture.supplyAsync(() -> {
sleep(200);
return "Alice";
});
}
static CompletableFuture<Integer> fetchOrders(String user) {
return CompletableFuture.supplyAsync(() -> {
sleep(200);
return user.equals("Alice") ? 3 : 0;
});
}
static void sleep(long ms) {
try { Thread.sleep(ms); } catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) {
fetchUser()
.thenCompose(CompletableFutureExample::fetchOrders)
.thenAccept(count -> System.out.println("orders = " + count))
.join(); // wait for pipeline to finish
// combine two independent async calls
CompletableFuture<String> a = fetchUser();
CompletableFuture<Integer> b = fetchOrders("Alice");
String summary = a.thenCombine(b, (user, orders) ->
user + " has " + orders + " orders"
).join();
System.out.println(summary);
}
}
5. Virtual threads — async I/O without callbacks
-
Java 21+ virtual threads are green threads managed by the JVM. Blocking
calls (socket read, JDBC query) park the virtual thread instead of
blocking an OS thread — write straight-line code, get async scalability.
import java.util.concurrent.Executors;
public class VirtualThreadExample {
public static void main(String[] args) throws InterruptedException {
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 5; i++) {
int id = i;
executor.submit(() -> {
System.out.println("task " + id + " on " + Thread.currentThread());
// blocking I/O here is fine — JVM parks this virtual thread
});
}
}
System.out.println("all tasks done");
}
}
Summary: Use CompletableFuture for
callback-style async pipelines; use
newVirtualThreadPerTaskExecutor() (Java 21+) for
high-concurrency blocking I/O with simple synchronous code.