Stop Writing CRUD: Use Callbacks and Functional Programming for Elegant Java Code
This article explains the callback pattern in Java, compares synchronous and asynchronous callbacks, shows how to implement them with interfaces, anonymous classes, and Lambda expressions, and demonstrates practical uses in Spring bean factories and data‑processing pipelines.
1. Overview
Callback is a programming pattern where a function is passed to another function and invoked after a specific event or task completes, enabling asynchronous operations, event‑driven programming, and better modularity.
1.1 What is a Callback?
A callback registers a method as a parameter to another method; when the target operation finishes, the registered method is called, forming a two‑way invocation relationship (A calls B, B calls back A).
1.2 Callback Structure
Callback registration : pass the callback function to a task (e.g., an async job).
Task execution : the main function runs the task, synchronously or asynchronously.
Callback invocation : after task completion, the callback is invoked based on success or failure.
1.3 Benefits of Callbacks
Event‑driven programming – trigger code on events such as button clicks.
Asynchronous handling – process results without blocking the main thread.
Modularization – decouple components by delegating result handling to separate callbacks.
2. Implementing Callbacks in Java
Java typically uses interfaces , anonymous inner classes , or Lambda expressions (Java 8+) to realize callbacks.
2.1 Using an Interface
interface Callback {
void onComplete(String result);
}
class Task {
public void execute(Callback callback) {
// Simulate business logic
String result = "Task Completed!";
callback.onComplete(result);
}
}
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);
}
});
}
}The Callback interface defines onComplete, Task invokes it after finishing, and an anonymous class provides the concrete handling.
2.2 Using Lambda (Java 8+)
public class CallBackTest {
public static void main(String[] args) {
Task task = new Task();
task.execute(result -> System.out.println("Callback received: " + result));
}
}Lambda simplifies the syntax when the callback interface has a single abstract method (a functional interface).
2.3 Asynchronous Callback with CompletableFuture
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...");
}
}The main thread proceeds while the async task runs; thenAccept triggers the callback after completion.
2.4 Payment System Asynchronous Callback
In a real‑world payment flow, the client registers a callback URL with a third‑party payment provider. After processing, the provider calls back to the registered endpoint, delivering the result without blocking the client.
@Controller
@RequestMapping("/api/mall/pay")
public class PayController {
@Resource private PayService payService;
@Resource private WxPayConfig wxPayConfig;
@GetMapping(value="/create", produces="text/html")
public ModelAndView create(@RequestParam("orderNo") String orderNo,
@RequestParam("payType") Integer payType) {
PayResponse response = payService.create(orderNo, payType);
Map<String, String> map = new HashMap<>();
if (Objects.equals(payType, PayConstant.TYPE_WXPAY)) {
map.put("codeUrl", response.getCodeUrl());
map.put("orderNo", orderNo);
map.put("returnUrl", wxPayConfig.getReturnUrl());
return new ModelAndView("wxView", map);
} else if (Objects.equals(payType, PayConstant.TYPE_ALIPAY)) {
map.put("body", response.getBody());
return new ModelAndView("alipayView", map);
}
throw new RuntimeException("Unsupported pay type");
}
@PostMapping("/notify")
@ResponseBody
public String asyncNotify(@RequestBody String notifyData) {
return payService.asyncNotify(notifyData);
}
}This demonstrates how a payment system uses a callback URL to notify the application of success or failure.
3. Callback Mechanism in Major Frameworks – Spring Example
Spring’s BeanFactory defines methods such as getBean(String name). The actual bean creation occurs in AbstractBeanFactory, where a functional interface ObjectFactory is passed as a callback to lazily create the bean.
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
return doGetBean(name, requiredType, null, false);
}During singleton creation, getSingleton receives an ObjectFactory<?> that, when invoked, calls createBean() to instantiate the bean. This is a classic use of callbacks and functional programming within the Spring container.
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// pre‑creation handling
beforeSingletonCreation(beanName);
try {
singletonObject = singletonFactory.getObject(); // callback
addSingleton(beanName, singletonObject);
} finally {
afterSingletonCreation(beanName);
}
}
return singletonObject;
}
}Thus, Spring’s core bean‑creation logic relies on callbacks to defer object construction until needed.
4. Practical Business Use – Data Transformation
When integrating heterogeneous financial data sources, a generic data‑transformer can be defined as a functional interface and applied via Lambda expressions, keeping the conversion logic concise and reusable.
@FunctionalInterface
public interface DataTransformer<T, R> {
R transform(T input);
}
public class DataProcessor {
public static <T, R> R processData(T data, DataTransformer<T, R> transformer) {
return transformer.transform(data);
}
public static void main(String[] args) {
// Example 1: String length
Integer len = processData("Hello", String::length);
System.out.println(len); // 5
// Example 2: Person to Student conversion
Person p = new Person();
p.setName("zhang san");
p.setAge(18);
Student s = processData(p, DataProcessor::convertToStudent);
System.out.println(s);
}
private static Student convertToStudent(Person person) {
Student s = new Student();
s.setName(person.getName());
s.setAge(person.getAge());
return s;
}
}This pattern cleanly separates data extraction from transformation, allowing different banks’ formats to be handled with minimal boilerplate.
5. Summary
Java’s callback mechanism—implemented via interfaces, anonymous classes, or Lambda expressions—enables asynchronous programming, decouples components, and integrates smoothly with frameworks like Spring. Although Java does not natively treat functions as first‑class citizens like JavaScript, these techniques provide comparable flexibility for building elegant, maintainable code.
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.
Shepherd Advanced Notes
Dedicated to sharing advanced Java technical insights, daily work snippets, and the power of persistent effort.
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.
