Introduction
In a typical Spring Boot application, synchronous service calls between layers can create tight coupling and reduce flexibility for long-running or delayed processes. In this article, we’ll walk through a clean implementation of the Mailbox Pattern using BlockingQueue, allowing decoupled, asynchronous processing between the controller and the background worker.
Imagine how you drop a letter in a mailbox and the postman picks it up later. You don’t wait at the box for delivery to happen.
Similarly, the Mailbox Pattern in a Spring application allows you to accept incoming tasks instantly, drop them into a queue, and let a background thread process them later. This makes your application faster, more scalable, and loosely coupled.
Use Case
Imagine you’re receiving task requests via a REST API, but you don’t want to process them immediately either because they’re slow or you want to manage them in batches or queues.
A Mailbox Pattern helps solve this by:
- 📨 Receiving the task
- 📥 Putting it into a mailbox (queue)
- 🛠️ Processing it in a background worker
Architecture
+------------+ +-------------+ +-------------+
| REST API | | Mailbox | | Worker |
| Controller | -----> | (Queue) | -----> | Thread/Task |
+------------+ +-------------+ +-------------+
Tech Stack
- Spring Boot 3
- Java 17+
- Maven
Project Structure
spring-mailbox-demo/
├── pom.xml
└── src/
└── main/
├── java/com/example/mailbox/
│ ├── SpringMailboxDemoApplication.java
│ ├── controller/TaskController.java
│ ├── model/TaskRequest.java
│ └── service/
│ ├── Mailbox.java
│ └── TaskWorker.java
└── resources/application.properties
Full Source Code
<project xmlns="http://maven.apache.org/POM/4.0.0"
...>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>spring-mailbox-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.2</version>
</parent>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
SpringMailboxDemoApplication.java
package com.example.mailbox;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringMailboxDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringMailboxDemoApplication.class, args);
}
}
model/TaskRequest.java
package com.example.mailbox.model;
public class TaskRequest {
private String taskId;
private String payload;
public TaskRequest() {}
public TaskRequest(String taskId, String payload) {
this.taskId = taskId;
this.payload = payload;
}
public String getTaskId() {
return taskId;
}
public void setTaskId(String taskId) {
this.taskId = taskId;
}
public String getPayload() {
return payload;
}
public void setPayload(String payload) {
this.payload = payload;
}
}
How does the Controller fit in?
The controller is like the person dropping a letter in the mailbox. It doesn't care how or when the letter gets delivered — it just drops it and responds immediately with “Task submitted.”
TaskController.java
@RestController
@RequestMapping("/task")
public class TaskController {
@Autowired
private Mailbox mailbox;
@PostMapping
public ResponseEntity<String> submitTask(@RequestBody TaskRequest task) {
mailbox.submit(task); // drop it into the mailbox
return ResponseEntity.ok("Task submitted: " + task.getTaskId());
}
}
📥 What is the Mailbox really doing?
- We use a BlockingQueue in Java, which works like a thread-safe to-do list.
- The controller adds a task to it using offer().
- The worker thread pulls tasks from it using take().
- This lets the web request return instantly while the real work is still happening behind the scenes.
service/Mailbox.java
package com.example.mailbox.service;
import com.example.mailbox.model.TaskRequest;
import org.springframework.stereotype.Component;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
@Component
public class Mailbox {
private final BlockingQueue<TaskRequest> queue = new LinkedBlockingQueue<>();
public void submit(TaskRequest request) {
queue.offer(request);
}
public TaskRequest take() throws InterruptedException {
return queue.take();
}
}
What is the Worker Thread doing?
The worker acts like a postman. It runs in the background and keeps checking the mailbox for new tasks.
When it finds one, it processes it (in our case, just logs it). You can later expand this to send emails, call APIs, or save to a DB.
service/TaskWorker.java
package com.example.mailbox.service;
import com.example.mailbox.model.TaskRequest;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class TaskWorker {
@Autowired
private Mailbox mailbox;
@PostConstruct
public void startWorker() {
Thread workerThread = new Thread(() -> {
while (true) {
try {
TaskRequest task = mailbox.take();
System.out.println("Processing task: " + task.getTaskId() + " | Payload: " + task.getPayload());
Thread.sleep(2000); // simulate work
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
});
workerThread.setDaemon(true);
workerThread.start();
}
}
application.properties
server.port=8080
Why It Matters
- Helps with performance by not blocking HTTP threads.
- Supports scalability by isolating heavy processing.
- Promotes loose coupling between controller and business logic.
You can expand this pattern by:
- Adding retry or error queues
- Using ScheduledExecutorService
- Swapping BlockingQueue with a Kafka topic or Spring Integration MessageChannel
Even if you're a beginner, this pattern teaches you:
- How to run background threads in Spring
- How to decouple HTTP input from business logic
- How to simulate real-world job queues
- You now understand not just how to build it but why it’s useful in real-world applications.
Summary
This pattern may seem simple, but it unlocks powerful asynchronous processing patterns for real-world Spring applications with minimal complexity and maximum decoupling.
You can also download from :
Top comments (0)