Introduction to Asynchronous Programming in Java
Modern applications require high performance and responsiveness. Asynchronous programming in Java is an approach that allows you to perform long-running operations (e.g., database queries, API calls, file reading) without blocking the main execution thread. Instead of waiting for a task to complete, the program continues to work, and the result is processed when ready.
In this guide, we will cover the core concepts of asynchronous programming in Java, starting with basic tools and ending with the modern CompletableFuture API. You will learn how to create non-blocking code, handle errors, and combine asynchronous tasks.
Core Concepts: Threads and ExecutorService
Threads
The simplest way to execute code asynchronously in Java is to create a new thread. However, manually creating threads is an expensive operation, and with a large number of tasks, it can lead to performance issues.
// Example of creating a threadThread thread = new Thread(() -> { System.out.println("Asynchronous task is running in thread: " + Thread.currentThread().getName());});thread.start();System.out.println("Main thread continues working");ExecutorService — Managing a Thread Pool
For efficient thread management, Java provides ExecutorService. This interface allows you to create thread pools, reuse them, and control task execution.
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;
public class ExecutorServiceExample { public static void main(String[] args) { // Create a pool of 4 threads ExecutorService executor = Executors.newFixedThreadPool(4); // Submit a task for execution executor.submit(() -> { System.out.println("Task is running in the thread pool"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Task completed"); }); // Shutdown the pool executor.shutdown(); }}Main types of thread pools:
newFixedThreadPool(n)— a fixed number of threads.newCachedThreadPool()— a dynamic number of threads.newSingleThreadExecutor()— a single thread for sequential task execution.newScheduledThreadPool(n)— for scheduling tasks with a delay.
Future and CompletableFuture
The Future Interface
Future is an interface that represents the result of an asynchronous operation. It allows you to check if the task is complete, wait for its completion, and retrieve the result.
import java.util.concurrent.*;
public class FutureExample { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executor = Executors.newSingleThreadExecutor(); Future<Integer> future = executor.submit(() -> { Thread.sleep(2000); return 42; }); System.out.println("Waiting for the result..."); Integer result = future.get(); // Blocks the thread until the result is obtained System.out.println("Result: " + result); executor.shutdown(); }}The drawback of Future is that the get() method blocks the thread. For more flexible work with asynchronous tasks, CompletableFuture was created.
CompletableFuture — A Powerful Asynchronous Tool
CompletableFuture<