Project: A Mini Callback System
Project: A Mini Callback System
Throughout this tutorial you have learned about lambda syntax, functional interfaces, Predicate, Function, Consumer, Supplier, method references, and function composition. In this final lesson you pull all of those ideas together into a small but realistic project: a mini callback / event-bus utility built entirely on functional interfaces — no third-party libraries, no heavyweight frameworks.
The goal is not to build a production event system. The goal is to see how lambdas replace boilerplate, how you can store and compose behaviour as data, and why this design is so flexible.
What We Are Building
We will build an EventBus class that lets you:
- Register multiple
Consumer<T>listeners for a named event topic. - Fire (publish) an event, which calls every registered listener in order.
- Register one-time listeners that automatically unsubscribe after their first invocation.
- Add a
Predicate<T>filter so a listener only fires when a condition is met.
The entire implementation is under 80 lines of Java and uses nothing but java.util.function and standard collections.
Step 1 — The Core EventBus
Start with the simplest version: a map from topic string to a list of Consumer callbacks.
Usage is already clean:
Consumer. There is no need to invent an interface — the JDK already has the right shape.
Step 2 — One-Time Listeners
A once listener fires on the first matching event and then removes itself. The trick: wrap the caller-supplied consumer in another consumer that unregisters itself before delegating.
Step 3 — Filtered Listeners
Add an onIf method that accepts a Predicate<T>. The listener runs only when the predicate returns true. This keeps business logic out of individual handlers.
Now you can write expressive subscriber registrations:
Step 4 — Putting It All Together
Here is a runnable demo using a simple record as the event payload:
synchronized blocks or replace ArrayList with CopyOnWriteArrayList. Always identify concurrency requirements before reaching for the simplest data structure.
Design Takeaways
- Behaviour as data: lambdas stored in a
List<Consumer<T>>are first-class values you can add, remove, and iterate — exactly like any other object. - Composition without inheritance:
onIfcombines aPredicateand aConsumerinto a newConsumerinline, no subclass required. - Open/Closed: to add a new kind of listener (throttled, async, logged) you wrap the existing consumer — you never modify the bus itself.
- Testability: because every handler is just a function, you can pass test doubles (assertions as lambdas) with zero mocking framework overhead.
Summary
You have built a fully functional, composable callback system in under 80 lines by applying every concept from this tutorial: lambdas as listener implementations, Consumer for event handling, Predicate for filtering, variable capture for the once-wrapper, and inline function composition for onIf. This is the essence of functional-style Java — small, well-named pieces of behaviour wired together rather than deep class hierarchies.