Callback Pattern in Java: Mastering Asynchronous Communication
Also known as
- Call-After
- Event-Subscription
- Listener
Intent of Callback Design Pattern
The Java Callback Design Pattern is a piece of executable code passed as an argument to other code, which is expected to call back (execute) the argument at a convenient time.
Detailed Explanation of Callback Pattern with Real-World Examples
Real-world example
A real-world analogy for the Callback design pattern can be found in the restaurant industry. Imagine a situation where you place an order at a busy restaurant. Instead of waiting at the counter for your food to be ready, you provide the cashier with your phone number. Once your order is prepared, the kitchen staff calls or sends a text message to notify you that your meal is ready for pickup.
In this analogy, placing your order is analogous to initiating an asynchronous task. Providing your phone number is akin to passing a callback function. The kitchen preparing your order represents the asynchronous processing, and the notification you receive is the callback being executed, allowing you to retrieve your meal without having to wait idly. This separation of task initiation and task completion is the essence of the Callback design pattern.
In plain words
Callback is a method passed to an executor which will be called at a defined moment.
Wikipedia says
In computer programming, a callback, also known as a "call-after" function, is any executable code that is passed as an argument to other code; that other code is expected to call back (execute) the argument at a given time.
Programmatic Example of Callback Pattern in Java
We need to be notified after the executing task has finished. We pass a callback method for the executor and wait for it to call back on us.
Callback
is a simple interface with single method.
public interface Callback {
void call();
}
Next we define Task
that will execute the callback after the task execution has finished.
public abstract class Task {
final void executeWith(Callback callback) {
execute();
Optional.ofNullable(callback).ifPresent(Callback::call);
}
public abstract void execute();
}
@Slf4j
public final class SimpleTask extends Task {
@Override
public void execute() {
LOGGER.info("Perform some important activity and after call the callback method.");
}
}
Finally, here's how we execute a task and receive a callback when it's finished.
public static void main(final String[] args) {
var task = new SimpleTask();
task.executeWith(() -> LOGGER.info("I'm done now."));
}
Program output:
17:12:11.680 [main] INFO com.iluwatar.callback.SimpleTask -- Perform some important activity and after call the callback method.
17:12:11.682 [main] INFO com.iluwatar.callback.App -- I'm done now.
When to Use the Callback Pattern in Java
Use the Callback pattern when
- Asynchronous event handling in GUI applications or event-driven systems
- Implementing notification mechanisms where certain events need to trigger actions in other components.
- Decoupling modules or components that need to interact without having a direct dependency on each other
Real-World Applications of Callback Pattern in Java
- GUI frameworks often use callbacks for event handling, such as user interactions (clicks, key presses)
- Node.js heavily relies on callbacks for non-blocking I/O operations
- Frameworks that deal with asynchronous operations, like Promises in JavaScript, use callbacks to handle the resolution or rejection of asynchronous tasks
- CyclicBarrier constructor can accept a callback that will be triggered every time a barrier is tripped.
Benefits and Trade-offs of Callback Pattern
Benefits:
- Decouples the execution logic of an operation from the signaling or notification logic, enhancing modularity and reusability
- Facilitates asynchronous processing, improving the responsiveness and scalability of applications
- Enables a reactive programming model where components can react to events as they occur
Trade-offs:
- Callback hell or pyramid of doom: Deeply nested callbacks can lead to code that is hard to read and maintain
- Inversion of control can lead to harder-to-follow code flow, making debugging more challenging
- Potential issues with error handling, especially in languages or environments where exceptions are used, as errors might need to be propagated through callbacks
Related Java Design Patterns
- Command: Callbacks can be implemented as Command objects in scenarios where more flexibility or statefulness is required in the callback operation
- Observer: Callbacks can be seen as a more dynamic and lightweight form of the Observer pattern, with the ability to subscribe and unsubscribe callback functions dynamically
- Promise: In some languages or frameworks, Promises or Futures can be used to handle asynchronous operations more cleanly, often using callbacks for success or failure cases