
Asynchronous programming, or async programming, is the programming method used to allow a program to start an activity and continue with others while the former goes on, unlike synchronous or blocking operations. This concept is essential in understanding non-blocking I/O in Java, especially when building scalable, reactive services.
For teams looking to adopt or scale this approach efficiently, it’s often valuable to hire Java concurrency experts who can architect, optimize, and troubleshoot complex async workflows.
Why is Async Programming Used?

In traditional (synchronous) programming:
- Each task blocks the program until it completes.
- If one task is slow (e.g., a network request), it delays all other tasks.
In asynchronous programming in Java:
- Slow operations (like reading a file, calling an API, or querying a database) are non-blocking.
- The program continues to run and is notified when the slow operation is complete.
This is a key aspect of Java asynchronous programming and is widely adopted in Java reactive programming models.
Real-World Analogy
Imagine you’re cooking dinner:
- You put rice on the stove (it takes 15 minutes).
- While it’s cooking, you chop vegetables, set the table, and boil water.
- You’re not waiting idly; this mirrors asynchronous task execution.
This is asynchronous behavior; you’re handling multiple things without waiting for each one to finish before starting the next. It’s much like asynchronous task execution in real-world programming.
In Programming Terms
In many languages, async programming is supported using:
- Callbacks (e.g., JavaScript)
- Futures/Promises (e.g., Java, Python, JavaScript)
async/await
keywords (e.g., Python, JavaScript, C#, Kotlin)- CompletableFuture in Java

Asynchronous programming might sound complex at first, but let’s make it simple, fun, and tasty with pizza and juice! This blog is for Java concurrency for beginners, and we’ll walk through Java’s async tools step by step, using real-life metaphors.
The Situation
You’re hungry. You order a pizza.
- Do you just stand at the door and wait?
- Or do you go do other things while it’s being made?
This is the difference between blocking and asynchronous programming in Java.
Blocking Way (No Async)
/* Blocking way */
String pizza = orderPizza(); // You just wait
System.out.println("Pizza is here: " + pizza);
Code language: JavaScript (javascript)
You stand at the door doing nothing until the pizza arrives.
Flow Diagram: Blocking Way

Async Way with CompletableFuture
CompletableFuture<String> pizzaFuture = CompletableFuture.supplyAsync(() -> orderPizza());
System.out.println("While pizza is coming, I’m watching TV...");
String pizza = pizzaFuture.get(); // Wait only when needed
System.out.println("Pizza is here: " + pizza);
Code language: JavaScript (javascript)
What’s happening?
- You place the order (supplyAsync())
- You go watch TV
- You wait (get()) only when the pizza is supposed to arrive
Flow Diagram: Async Way with CompletableFuture

ExecutorService = Your Delivery Team
The pizza is being delivered by your custom team (not the default one).
ExecutorService deliveryTeam = Executors.newFixedThreadPool(2);
CompletableFuture<String> pizzaFuture = CompletableFuture.supplyAsync(() -> orderPizza(), deliveryTeam);
System.out.println("Doing dishes while waiting for pizza...");
String pizza = pizzaFuture.get();
System.out.println("Pizza is here: " + pizza);
deliveryTeam.shutdown();
Code language: JavaScript (javascript)
Java Term | Pizza Example |
CompletableFuture | Pizza receipt – it’s coming later |
supplyAsync() | You call and place an order |
get() | You open the door when the bell rings |
ExecutorService | Your delivery guys (not the pizza shop’s default team) |
Pizza and Juice in Parallel (Hands-on Code!)
A small Java program where you order pizza and also make juice in parallel makes async clearer.
This is a practical Java multithreading tutorial showing how to:
1. Order pizza (takes 3 seconds)
2. Make juice (takes 2 seconds)
Do both in parallel using CompletableFuture, demonstrating java parallel stream vs async execution behavior.
import java.util.concurrent.*;
public class PizzaAndJuice {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(2);
CompletableFuture<String> pizzaFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("Ordering pizza...");
sleep(3000);
return "Pizza is ready!";
}, executor);
CompletableFuture<String> juiceFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("Making juice...");
sleep(2000);
return "Juice is ready!";
}, executor);
System.out.println("I’m cleaning the table while waiting...");
String pizza = pizzaFuture.get();
String juice = juiceFuture.get();
System.out.println(pizza);
System.out.println(juice);
System.out.println("Let's eat and drink!");
executor.shutdown();
}
private static void sleep(long ms) {
try { Thread.sleep(ms); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
Code language: JavaScript (javascript)
Sample Output:
Ordering pizza. . .
Making juice . . .
I’m cleaning the table while waiting . . .
Juice is ready!
Pizza is ready!
Let’s eat and drink!
What does this show?
- Pizza and juice are prepared at the same time, not one after the other.
- You’re not wasting time waiting, you can clean the table meanwhile.
- This is how asynchronous programming helps speed things up in real life and software!
Adding Error Handling & Timeout
Let’s now take this a step further by adding:
1. Error handling – What if pizza delivery fails?
2. Timeout – What if the pizza takes too long?
Error handling
CompletableFuture<String> pizzaFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("Ordering pizza...");
sleep(3000);
if (Math.random() < 0.5) throw new RuntimeException("Pizza shop is closed!");
return "Pizza is ready!";
}).exceptionally(ex -> {
System.out.println("Error: " + ex.getMessage());
return "No pizza today ";
});
Code language: JavaScript (javascript)
Timeout Handling
try {
String pizza = pizzaFuture.get(2, TimeUnit.SECONDS);
System.out.println(pizza);
} catch (TimeoutException e) {
System.out.println("Pizza took too long! Canceling order.");
pizzaFuture.cancel(true);
} catch (Exception e) {
System.out.println("Something went wrong: " + e.getMessage());
}
Code language: JavaScript (javascript)
Sample Output 1 (Success within time):
Ordering pizza. . .
Pizza is ready!
Sample Output 2 (Shop closed):
Ordering pizza. . .
Error: Pizza shop is closed!
No pizza today
Sample Output 3 (Timeout):
Ordering pizza. . .
Pizza took too long! Canceling order.
Spring Boot Integration
We call GET /order
It will:
1. Order pizza (async)
2. Make juice (async)
3. Wait for both to finish
Step-by-step: Async Pizza + Juice in Spring
1. pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Code language: HTML, XML (xml)
2. AsyncPizzaApp.java
@SpringBootApplication
public class AsyncPizzaApp {
public static void main(String[] args) {
SpringApplication.run(AsyncPizzaApp.class, args);
}
}
Code language: PHP (php)
3. PizzaService.java
@Service
public class PizzaService {
public CompletableFuture<String> orderPizza() {
return CompletableFuture.supplyAsync(() -> {
sleep(3000);
return "Pizza is ready!";
});
}
public CompletableFuture<String> makeJuice() {
return CompletableFuture.supplyAsync(() -> {
sleep(2000);
return "Juice is ready!";
});
}
private void sleep(long ms) {
try { Thread.sleep(ms); } catch (InterruptedException e) { }
}
}
Code language: PHP (php)
4. OrderController.java
@RestController
public class OrderController {
private final PizzaService pizzaService;
public OrderController(PizzaService pizzaService) {
this.pizzaService = pizzaService;
}
@GetMapping("/order")
public String orderFood() throws Exception {
CompletableFuture<String> pizzaFuture = pizzaService.orderPizza();
CompletableFuture<String> juiceFuture = pizzaService.makeJuice();
CompletableFuture.allOf(pizzaFuture, juiceFuture).join();
return pizzaFuture.get() + " & " + juiceFuture.get() + " — Enjoy your meal!";
}
}
Code language: PHP (php)
Run it!
Sample Output
Pizza is ready! & Juice is ready! Enjoy your meal!
Highlights:
Component | Purpose |
---|---|
PizzaService | Contains async tasks (pizza, juice) |
CompletableFuture | Runs tasks in parallel |
OrderController | Calls both services, waits, and returns the result |
CompletableFuture.allOf() | Waits for both tasks to complete |
Advanced: Timeout, Error Handling, Custom Thread Pool
- Timeout – Cancel pizza if it takes too long
- Exception handling – Handle pizza shop failures
- Custom thread pool – For better performance and control
Feature | Tool Used |
---|---|
Async tasks | CompletableFuture |
Parallel execution | Custom ExecutorService |
Timeout | completeOnTimeout() |
Error handling | exceptionally() |
1. AsyncConfig.java
@Configuration
public class AsyncConfig {
@Bean
public ExecutorService customExecutor() {
return Executors.newFixedThreadPool(5);
}
}
Code language: CSS (css)
2. PizzaService.java
@Service
public class PizzaService {
private final ExecutorService executor;
public PizzaService(ExecutorService executor) {
this.executor = executor;
}
public CompletableFuture<String> orderPizza() {
return CompletableFuture.supplyAsync(() -> {
sleep(3000);
if (Math.random() < 0.5) throw new RuntimeException("Pizza oven broke!");
return "Pizza is ready!";
}, executor).completeOnTimeout("Pizza took too long, canceled! ", 2, TimeUnit.SECONDS)
.exceptionally(ex -> "Pizza failed: " + ex.getMessage());
}
public CompletableFuture<String> makeJuice() {
return CompletableFuture.supplyAsync(() -> {
sleep(2000);
return "Juice is ready!";
}, executor);
}
private void sleep(long ms) {
try { Thread.sleep(ms); } catch (InterruptedException e) { }
}
}
Code language: PHP (php)
3. FoodResponse.java
public class FoodResponse {
private String pizza;
private String juice;
private String message;
// Constructors, Getters, Setters
}
Code language: PHP (php)
4. OrderController.java
@RestController
public class OrderController {
private final PizzaService pizzaService;
public OrderController(PizzaService pizzaService) {
this.pizzaService = pizzaService;
}
@GetMapping("/order")
public FoodResponse orderFood() throws Exception {
CompletableFuture<String> pizzaFuture = pizzaService.orderPizza();
CompletableFuture<String> juiceFuture = pizzaService.makeJuice();
CompletableFuture.allOf(pizzaFuture, juiceFuture).join();
return new FoodResponse(pizzaFuture.get(), juiceFuture.get(), "Enjoy your meal!");
}
}
Code language: PHP (php)
5. Application.properties
spring.jackson.serialization.indent-output=true
Code language: JavaScript (javascript)
Sample Output
{
"pizza": "Pizza is ready!",
"juice": "Juice is ready!",
"message": "Enjoy your meal!"
}
Code language: JSON / JSON with Comments (json)
Or:
{
"pizza": "Pizza took too long, canceled! ",
"juice": "Juice is ready!",
"message": "Enjoy your meal!"
}
Code language: JSON / JSON with Comments (json)
Is This Truly Non-blocking?
Is the example above (with CompletableFuture) a pattern for a non-blocking API in Spring MVC?
The answer is:
1) Yes, but only partially.
2) It’s not truly non-blocking from end-to-end in traditional Spring MVC.
Great for speeding up I/O-heavy work like file ops, DB, HTTP calls, or even implementing DevOps for scalable Java applications that require non-blocking backend services.
Let’s break this down using Java concurrency principles:
We used:
- CompletableFuture.supplyAsync() to run tasks in parallel.
- Returned a normal JSON response from the controller (FoodResponse).
Benefits:
- Internal processing is concurrent/asynchronous.
- Fast, simple, and it works in traditional Spring MVC.
- Good for speeding up I/O-heavy work like file ops, DB, HTTP calls, etc.
Limitation:
The Servlet thread is still blocked while waiting for pizzaFuture.get() and juiceFuture.get().
So it’s asynchronous internally, but blocking externally.
TRUE Non-blocking APIs in Spring (End-to-End)
To make it fully non-blocking, including the HTTP layer, you need:
1. WebFlux (Reactive Spring)
- Based on Reactor and Netty, not Tomcat.
- Uses Mono and Flux instead of CompletableFuture.
- Truly non-blocking from request to response.
Example:
@GetMapping("/order")
public Mono<FoodResponse> orderFood() {
Mono<String> pizza = orderPizzaAsync();
Mono<String> juice = makeJuiceAsync();
return Mono.zip(pizza, juice)
.map(tuple -> new FoodResponse(tuple.getT1(), tuple.getT2(), "Enjoy!"));
}
Code language: JavaScript (javascript)
2. Callable or DeferredResult in Spring MVC (semi-non-blocking)
Spring MVC also supports asynchronous request processing using:
Callable<T>
(simple async task)
DeferredResult<T>
(more flexible)
Example with Callable:
@GetMapping("/order")
public Callable<FoodResponse> orderAsync() {
return () -> {
String pizza = pizzaService.orderPizza().get();
String juice = pizzaService.makeJuice().get();
return new FoodResponse(pizza, juice, "Enjoy!");
};
}
Code language: JavaScript (javascript)
This tells the Servlet container (e.g., Tomcat):
- “Release the thread for now.”
- “Resume response when the result is ready.”
A clean balance between async vs sync in Java inside web contexts.
Pattern | Internally Async | Fully Non-blocking at HTTP Level | Framework |
CompletableFuture + get() | Yes | No | Spring MVC |
Callable / DeferredResult | Yes | Yes | Spring MVC |
Mono / Flux | Yes | Yes | Spring WebFlux |
Summary :
- If you’re using Spring MVC and want non-blocking APIs:
- Use Callable or DeferredResult
- Use WebAsyncTask for timeouts
- Move to Spring WebFlux only if you need a full reactive stack
To go truly non-blocking, use WebFlux or Spring MVC’s Callable. This is essential if you want to monitor asynchronous Java tasks and performance metrics via OpenTelemetry or distributed tracing systems.
Final Thoughts
CompletableFuture
makes async programming intuitive.- Use
ExecutorService
for performance control. completeOnTimeout()
andexceptionally()
= essential for real-world resilience.- Spring MVC supports semi-async with
Callable
; full async needs WebFlux.
Now you can enjoy pizza and parallel programming without blocking!
Absolutely! Let’s replace the playful example with a professional, real-world mini project that demonstrates the practical use of CompletableFuture and asynchronous programming in a Spring Boot application.
Mini Project: Asynchronous Document Processing Service
Real-World Scenario:
Your company provides an API for uploading documents (PDF, Word, etc.) that performs:
- Metadata extraction (e.g., title, author, page count)
- Virus scanning for uploaded files
- Returns the combined result in JSON when both tasks are done
These are independent operations and can be processed in parallel.
Technologies Used
- Java 11
- Spring Boot 3.x
- CompletableFuture with custom ExecutorService
- RESTful API with Spring Web
Project Structure
src
└── main
└── java
└── com.mobisoftinfotech.documentprocessor
├── DocumentProcessorApplication.java
├── config
│ └── AsyncConfig.java
├── controller
│ └── DocumentController.java
├── dto
│ └── DocumentResponse.java
└── service
└── DocumentService.java
Code language: CSS (css)
Step-by-Step Code
1. Pom.xml (dependencies)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
Code language: HTML, XML (xml)
2. DocumentProcessorApplication.java
package com.example.documentprocessor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DocumentProcessorApplication {
public static void main(String[] args) {
SpringApplication.run(DocumentProcessorApplication.class, args);
}
}
Code language: JavaScript (javascript)
3. Config/AsyncConfig.java
package com.example.documentprocessor.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Configuration
public class AsyncConfig {
@Bean
public ExecutorService executorService() {
return Executors.newFixedThreadPool(5); // For parallel tasks
}
}
Code language: JavaScript (javascript)
4. Dto/DocumentResponse.java
package com.example.documentprocessor.dto;
public class DocumentResponse {
private String metadata;
private String virusScanResult;
private String message;
public DocumentResponse() {}
public DocumentResponse(String metadata, String virusScanResult, String message) {
this.metadata = metadata;
this.virusScanResult = virusScanResult;
this.message = message;
}
public String getMetadata() { return metadata; }
public void setMetadata(String metadata) { this.metadata = metadata; }
public String getVirusScanResult() { return virusScanResult; }
public void setVirusScanResult(String virusScanResult) { this.virusScanResult = virusScanResult; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
}
Code language: JavaScript (javascript)
5. Service/DocumentService.java
package com.example.documentprocessor.service;
import org.springframework.stereotype.Service;
import java.util.concurrent.*;
@Service
public class DocumentService {
private final ExecutorService executor;
public DocumentService(ExecutorService executor) {
this.executor = executor;
}
public CompletableFuture<String> extractMetadata(String fileName) {
return CompletableFuture.supplyAsync(() -> {
System.out.println("Extracting metadata for " + fileName);
sleep(3000);
return "Metadata: Title=Report, Pages=12";
}, executor).completeOnTimeout("Metadata extraction timeout!", 2, TimeUnit.SECONDS)
.exceptionally(ex -> "Metadata failed: " + ex.getMessage());
}
public CompletableFuture<String> scanForVirus(String fileName) {
return CompletableFuture.supplyAsync(() -> {
System.out.println("Scanning for virus: " + fileName);
sleep(2000);
if (Math.random() < 0.3) {
throw new RuntimeException("Virus scan engine error");
}
return "Scan Result: No virus found";
}, executor).completeOnTimeout("Scan timeout!", 3, TimeUnit.SECONDS)
.exceptionally(ex -> "Scan failed: " + ex.getMessage());
}
private void sleep(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
Code language: JavaScript (javascript)
6. Controller/DocumentController.java
package com.example.documentprocessor.controller;
import com.example.documentprocessor.dto.DocumentResponse;
import com.example.documentprocessor.service.DocumentService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.CompletableFuture;
@RestController
public class DocumentController {
private final DocumentService documentService;
public DocumentController(DocumentService documentService) {
this.documentService = documentService;
}
@GetMapping("/process")
public DocumentResponse processDocument(@RequestParam(defaultValue = "report.pdf") String file) throws Exception {
CompletableFuture<String> metadataFuture = documentService.extractMetadata(file);
CompletableFuture<String> scanFuture = documentService.scanForVirus(file);
CompletableFuture.allOf(metadataFuture, scanFuture).join();
String metadata = metadataFuture.get();
String scanResult = scanFuture.get();
return new DocumentResponse(metadata, scanResult, "Document processed.");
}
}
Code language: JavaScript (javascript)
7. Application.properties
spring.jackson.serialization.indent-output=true
Code language: JavaScript (javascript)
Run & Test
Start the application and test:
URL: curl “http://localhost:8080/process?file=report.pdf”
Example Output (success):
{
"metadata": "Metadata: Title=Report, Pages=12",
"virusScanResult": "Scan Result: No virus found",
"message": "Document processed."
}
Code language: JSON / JSON with Comments (json)
Example Output (with error):
{
"metadata": "Metadata: Title=Report, Pages=12",
"virusScanResult": "Scan failed: Virus scan engine error",
"message": "Document processed."
}
Code language: JSON / JSON with Comments (json)

Real-World Relevance
This project simulates:
- File processing pipelines
- Non-blocking concurrent backend operations
- Timeout/error recovery in long-running operations (e.g., cloud scans, OCR, antivirus)
These are independent operations and can be processed in parallel, even more so when you secure async API calls in Java to maintain end-to-end data integrity.
Conclusion
Asynchronous programming in Java may seem complex at first, but it becomes a powerful ally once you grasp the core concepts. From basic CompletableFuture
usage to integrating async patterns into real-world Spring Boot applications, you now have a strong foundation to build non-blocking I/O in Java, efficient, and scalable services.
By mastering async constructs like CompletableFuture
, handling exceptions, managing timeouts, and applying these patterns to production-like scenarios, you’re not just writing faster code; you’re writing smarter code. Concepts such as Java thread management and practical examples like a java executorservice example are critical for building reliable systems. You can even explore advanced patterns like Java async search integration to enhance your application’s search capabilities.
To explore the full working code, Mini project setup, check out the complete source on GitHub
Keep experimenting, profiling, and applying these concepts in your projects. That’s how theory turns into mastery with practical Java performance tuning along the way.

Author's Bio

Rushikesh Keskar is a Senior Software Engineer at Mobisoft Infotech Pvt Ltd with more than 10 years of experience in backend development. Skilled in Java, Spring Boot, REST APIs, PostgreSQL, and third-party service integrations, he specializes in building scalable and efficient enterprise solutions. His expertise includes REST API development, database and code optimization, and resolving critical production issues.