Callback Functions in Java with Examples

📘 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

Spring Boot 3 Paid Course Published for Free
on my Java Guides YouTube Channel

Subscribe to my YouTube Channel (165K+ subscribers):
Java Guides Channel

Top 10 My Udemy Courses with Huge Discount:
Udemy Courses - Ramesh Fadatare