📘 Premium Read: Access my best content on Medium member-only articles — deep dives into Java, Spring Boot, Microservices, backend architecture, interview preparation, career advice, and industry-standard best practices.
✅ Some premium posts are free to read — no account needed. Follow me on Medium to stay updated and support my writing.
🎓 Top 10 Udemy Courses (Huge Discount): Explore My Udemy Courses — Learn through real-time, project-based development.
▶️ Subscribe to My YouTube Channel (172K+ subscribers): Java Guides on YouTube
🧾 Introduction
You might have heard about callbacks in JavaScript or Python.
✅ But did you know you can use callback functions in Java too?
✅ Callback functions make your code more flexible, more reusable, and less tightly coupled.
In this guide, you’ll learn:
- What callback functions are
- How to implement callbacks in Java
- Real-world examples (including functional interfaces and lambdas)
- Best practices and common mistakes
Let's master callbacks in Java 🚀.
✅ What is a Callback Function?
A callback is:
A function that you pass to another function (or object), so that it can call back (invoke) when needed.
✅ Instead of hardcoding behavior, you pass behavior dynamically.
✅ In Java, callbacks are typically implemented using:
- Interfaces
- Anonymous classes
- Lambda expressions (Java 8+)
📦 Simple Example: Callback Using Interface
Step 1: Define the Callback Interface
public interface Callback {
void onSuccess(String message);
}
Step 2: Create a Class That Accepts a Callback
public class DataFetcher {
public void fetchData(Callback callback) {
// Simulate data fetching
System.out.println("Fetching data...");
try {
Thread.sleep(1000); // simulate delay
} catch (InterruptedException e) {
e.printStackTrace();
}
callback.onSuccess("Data fetched successfully!");
}
}
Step 3: Use the Callback
public class Main {
public static void main(String[] args) {
DataFetcher fetcher = new DataFetcher();
fetcher.fetchData(new Callback() {
@Override
public void onSuccess(String message) {
System.out.println("Callback received: " + message);
}
});
}
}
✅ Output:
Fetching data...
Callback received: Data fetched successfully!
✅ How Callback Works Internally?
Step | Action |
---|---|
1 | You pass an implementation of Callback to fetchData() |
2 | fetchData() does some work |
3 | Once done, it calls back your onSuccess() method |
✅ Control is inverted — caller defines what happens after an event, not the library.
Using Lambda Expressions for Cleaner Callbacks (Java 8+)
In Java 8 and newer, you can simplify callbacks using lambdas.
✅ First, mark your interface as a functional interface:
@FunctionalInterface
public interface Callback {
void onSuccess(String message);
}
✅ Then use a lambda:
public class Main {
public static void main(String[] args) {
DataFetcher fetcher = new DataFetcher();
fetcher.fetchData(message -> System.out.println("Lambda callback: " + message));
}
}
✅ Much cleaner, especially for single-method callbacks!
Real-World Use Cases for Callbacks in Java
1. Event Handling (Button Clicks, Form Submissions)
In desktop apps (Swing/JavaFX) or web frameworks:
button.setOnAction(event -> System.out.println("Button clicked!"));
✅ The button fires a callback when clicked.
2. Asynchronous Processing
APIs like Spring's ListenableFuture
or custom background jobs:
future.addCallback(new SuccessCallback<String>() {
@Override
public void onSuccess(String result) {
System.out.println("Background task completed with: " + result);
}
});
✅ Reacting to the completion of background work.
3. Strategy Pattern (Behavior Injection)
Instead of hardcoding behavior:
paymentService.pay(amount, paymentMethod -> {
paymentMethod.process(amount);
});
✅ Different strategies (Credit Card, PayPal) can be plugged in dynamically.
Great! Let's walk through a clean, real-world implementation of callback functions in Java, inspired by concepts from Baeldung’s article on Java Callback Functions.
✅ Callback Functions in Java (Real-World Example)
Real-World Scenario:
Imagine a FileUploader service that uploads a file, and once done, it notifies the caller via a callback.
1. Define the Callback Interface
@FunctionalInterface
interface UploadCallback {
void onUploadComplete(String fileName);
}
2. FileUploader Class That Accepts the Callback
public class FileUploader {
// ✅ Simulate file upload and trigger callback
public void upload(String fileName, UploadCallback callback) {
System.out.println("Uploading file: " + fileName);
// Simulate upload delay (real-world: file writing, cloud sync, etc.)
try {
Thread.sleep(1000); // 1-second fake delay
} catch (InterruptedException e) {
e.printStackTrace();
}
// ✅ Callback triggered
callback.onUploadComplete(fileName);
}
}
3. Client Code That Uses the Callback
public class Main {
public static void main(String[] args) {
FileUploader uploader = new FileUploader();
// ✅ Pass lambda expression as callback implementation
uploader.upload("report.pdf", fileName -> {
System.out.println("✅ Upload finished for: " + fileName);
// Further actions: send email, update DB, log to monitoring...
});
}
}
Output:
Uploading file: report.pdf
✅ Upload finished for: report.pdf
Why This Works:
UploadCallback
is a functional interface, allowing use of lambdas.FileUploader
is decoupled from what happens after the upload — that logic is pushed to the caller via the callback.- This promotes clean, extensible, event-driven design.
💡 Common Use Cases:
- Asynchronous processing (upload/download)
- Payment gateways
- Event listeners
- UI button handlers
- REST API response handlers
✅ Real-World Example: Payment Processor with Callback
🎯 Scenario:
A PaymentProcessor
processes a payment and then notifies the result via a callback (success or failure).
// ✅ Callback interface
interface PaymentCallback {
void onSuccess(String transactionId);
void onFailure(String reason);
}
// ✅ Payment processor class
class PaymentProcessor {
public void processPayment(String userId, double amount, PaymentCallback callback) {
System.out.println("Processing payment for user: " + userId + ", amount: ₹" + amount);
// Fake logic to simulate payment outcome
if (amount > 0) {
// Success callback
String txnId = "TXN" + System.currentTimeMillis();
callback.onSuccess(txnId);
} else {
// Failure callback
callback.onFailure("Invalid payment amount");
}
}
}
// ✅ Main class
public class Main {
public static void main(String[] args) {
PaymentProcessor processor = new PaymentProcessor();
// ✅ Call with a lambda implementation of callback
processor.processPayment("user123", 1500.0, new PaymentCallback() {
@Override
public void onSuccess(String transactionId) {
System.out.println("✅ Payment successful! Transaction ID: " + transactionId);
}
@Override
public void onFailure(String reason) {
System.out.println("❌ Payment failed: " + reason);
}
});
System.out.println("---");
// Try with invalid amount
processor.processPayment("user456", 0.0, new PaymentCallback() {
@Override
public void onSuccess(String transactionId) {
System.out.println("✅ Payment successful! Transaction ID: " + transactionId);
}
@Override
public void onFailure(String reason) {
System.out.println("❌ Payment failed: " + reason);
}
});
}
}
Output:
Processing payment for user: user123, amount: ₹1500.0
✅ Payment successful! Transaction ID: TXN1714557436982
---
Processing payment for user: user456, amount: ₹0.0
❌ Payment failed: Invalid payment amount
✅ Callback Example: Calculator with Callback Function
🎯 Scenario:
A calculator performs an operation (like addition) and notifies the result via a callback.
Full Example: (Java 8+)
// ✅ Step 1: Define a functional callback interface
@FunctionalInterface
interface ResultCallback {
void onResult(int result);
}
// ✅ Step 2: Calculator class that accepts a callback
class Calculator {
public void add(int a, int b, ResultCallback callback) {
int sum = a + b;
// ✅ Trigger the callback with the result
callback.onResult(sum);
}
public void multiply(int a, int b, ResultCallback callback) {
int product = a * b;
callback.onResult(product);
}
}
// ✅ Step 3: Main method to test
public class Main {
public static void main(String[] args) {
Calculator calc = new Calculator();
// ✅ Pass a lambda as the callback
calc.add(5, 3, result -> System.out.println("Addition result: " + result));
calc.multiply(4, 6, result -> System.out.println("Multiplication result: " + result));
}
}
Output:
Addition result: 8
Multiplication result: 24
✅ Why This Is a Callback Example:
ResultCallback
is a functional interface.- The caller provides logic (
result -> ...
) that gets called back after the calculation is done. - The calculator doesn’t care what you do with the result — it just invokes the callback.
✅ Best Practices for Using Callbacks in Java
Best Practice | Why It Matters |
---|---|
Use @FunctionalInterface |
Ensures only one method (good for lambdas) |
Always handle errors inside callbacks | Avoid crashing the main thread |
Keep callback methods lightweight | Callbacks should be fast and non-blocking |
Document the callback clearly | Specify when and how the callback is triggered |
🔴 Common Mistakes to Avoid
Mistake | Problem |
---|---|
Forgetting to call the callback | Leaves the caller waiting forever |
Using non-functional interfaces unnecessarily | Miss out on lambda benefits |
Blocking in a callback | Slows down event loops or threads |
✅ Always treat callbacks carefully to avoid performance issues!
✅ Final Thoughts
✅ Callbacks are not just for JavaScript.
✅ In Java too, they help you:
- Decouple code
- Inject behavior dynamically
- Build flexible systems like event-driven apps, asynchronous workflows, and dynamic strategies
Good developers call methods.
Great developers design methods that accept callbacks.
Master callbacks —and you’ll unlock more powerful, reusable, and scalable Java code 🚀.
Comments
Post a Comment
Leave Comment