Master Java Callbacks & Functional Programming: Write Elegant, Structured Code Beyond CRUD
This article explains Java callback mechanisms — synchronous and asynchronous — through interfaces, anonymous classes, lambdas, and CompletableFuture, demonstrates their use in Spring's BeanFactory and a payment integration, and shows how functional interfaces enable flexible data transformation for multi-bank integration.
1. Overview
A callback is a programming pattern where a function passes another function as an argument to be invoked upon task completion or specific events. It enables asynchronous operations, event-driven programming, and improves code extensibility, flexibility, and modularity.
2. Callback Structure and Roles
2.1 What Is a Callback
The callback mechanism registers a method ( callback()) from Class A into Class B. When Class A calls methodB() on Class B, Class B later invokes the registered callback(), creating a bidirectional call: A → B → A.
Callbacks are classified as:
Synchronous callback : executed immediately during the caller's execution, no threading involved.
Asynchronous callback : invoked after a background task finishes (e.g., network request, file I/O), often on a different thread.
2.2 Three Parts of a Callback
Callback registration : passing the callback function as a parameter.
Task execution : the main function performs its work (sync or async).
Callback invocation : after completion, the callback is called with success or error data.
2.3 Roles
Event-driven programming (button clicks, page load).
Asynchronous handling (non-blocking result/error processing).
Modularity: decouples tasks via separate callback handlers.
3. Implementation Ways in Java
3.1 Via Interface
Define a callback interface with a method (e.g., onComplete(String)), accept it in a task class, and invoke it upon completion. The example uses an anonymous inner class to implement the interface.
// 1. Define callback interface
interface Callback {
void onComplete(String result);
}
// 2. Business logic class accepting callback
class Task {
public void execute(Callback callback) {
String result = "Task Completed!";
callback.onComplete(result);
}
}
// 3. Usage with anonymous class
public class CallBackTest {
public static void main(String[] args) {
Task task = new Task();
task.execute(new Callback() {
@Override
public void onComplete(String result) {
System.out.println("Callback received: " + result);
}
});
}
}3.2 Via Lambda Expression (Java 8+)
For single-method interfaces (functional interfaces), lambdas simplify the syntax:
public class CallBackTest {
public static void main(String[] args) {
Task task = new Task();
task.execute(result -> System.out.println("Callback received: " + result));
}
}3.3 Asynchronous Callback with CompletableFuture
Java's CompletableFuture enables async callbacks. The example simulates a 2-second task:
import java.util.concurrent.CompletableFuture;
interface Callback {
void onComplete(String result);
}
class AsyncTask {
public void executeAsync(Callback callback) {
CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(2000); } catch (InterruptedException e) { }
return "Task Completed!";
}).thenAccept(callback::onComplete);
}
}
public class CallBackTest {
public static void main(String[] args) {
AsyncTask task = new AsyncTask();
task.executeAsync(result -> System.out.println("Async callback received: " + result));
System.out.println("Main thread continues executing...");
}
} supplyAsyncruns the task asynchronously; thenAccept registers the callback; the main thread proceeds without waiting.
3.4 Payment Async Callback (Architecture Level)
In third-party payment integration, the merchant registers a notify URL (callback endpoint) with the payment platform. After payment, the platform POSTs the result to that URL. The Spring controller example shows: /create initiates payment, passing notifyUrl from config. /notify receives the async notification and delegates to payService.asyncNotify().
Config snippet (YAML) includes notifyUrl for WeChat Pay and Alipay.
4. Callback in Spring Framework
Spring's BeanFactory uses callbacks internally. The core method doGetBean() calls getSingleton(beanName, () -> createBean(...)). The second argument is an ObjectFactory functional interface. Inside getSingleton(), the callback is invoked via singletonFactory.getObject(), which executes createBean(). This demonstrates functional-style callback for lazy singleton creation with proper locking and lifecycle callbacks ( beforeSingletonCreation, afterSingletonCreation).
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
beforeSingletonCreation(beanName);
try {
singletonObject = singletonFactory.getObject(); // callback invokes createBean()
newSingleton = true;
} finally {
afterSingletonCreation(beanName);
}
if (newSingleton) addSingleton(beanName, singletonObject);
}
return singletonObject;
}
}5. Practical Business Example: Multi-Bank Data Transformation
Problem: different banks provide credit data in varying formats. Hard-coding per bank leads to duplication. Solution: extract the transformation step into a functional interface DataTransformer<T, R> with a single method R transform(T input). A generic DataProcessor accepts data and a transformer, applying it via lambda or method reference.
@FunctionalInterface
public interface DataTransformer<T, R> {
R transform(T input);
}
public class DataProcessor<T, R> {
public R processData(T data, DataTransformer<T, R> transformer) {
return transformer.transform(data);
}
}
// Usage examples
public static void main(String[] args) {
// Example 1: String -> Integer (length)
Integer length = DataProcessor.processData("Hello", String::length);
System.out.println(length); // 5
// Example 2: Person -> Student via method reference
Person person = new Person();
person.setName("zhang san");
person.setAge(18);
Student student = DataProcessor.processData(person, DataProcessor::convertToStudent);
System.out.println(student);
}
private static Student convertToStudent(Person person) {
Student student = new Student();
student.setName(person.getName());
student.setAge(person.getAge());
return student;
}This approach lets each bank provide its own transformer lambda, keeping the core pipeline unchanged.
6. Summary
Java callbacks, implemented via interfaces, anonymous classes, lambdas, and CompletableFuture, enable asynchronous, decoupled, and modular code. They appear in framework internals (Spring's singleton creation) and real-world architectures (payment notifications). Functional interfaces combined with lambdas provide a clean way to parameterize behavior, as shown in the multi-bank data transformation case, turning repetitive CRUD-like code into elegant, reusable pipelines.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
ITPUB
Official ITPUB account sharing technical insights, community news, and exciting events.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
