How to Use Guava's EventBus for Publish-Subscribe Communication
Guava's EventBus provides an in-process publish-subscribe mechanism that lets Java components communicate without holding explicit references to one another.
The Google Guava library includes a lightweight yet powerful EventBus implementation that enables decoupled messaging between application layers. This pattern removes tight coupling by allowing objects to publish events and subscribe to specific types without direct dependencies. Learning how to use Guava's EventBus for publish-subscribe communication helps build maintainable Java applications with clean separation of concerns.
Core Components of Guava EventBus
The EventBus Class
The EventBus class in com/google/common/eventbus/EventBus.java serves as the central message hub. It maintains a registry of subscribers and dispatches published events to matching handler methods. According to the source implementation (lines 28-49, 96-110, 124-132), the bus performs runtime type checking to determine which subscribers should receive a given event.
Subscriber Methods with @Subscribe
Subscribers define public methods annotated with @Subscribe from com/google/common/eventbus/Subscribe.java. Each subscriber method must accept exactly one argument representing the event type it handles:
import com.google.common.eventbus.Subscribe;
public class OrderListener {
@Subscribe
public void onOrderCreated(OrderEvent event) {
// Process the event
System.out.println("Order received: " + event.getOrderId());
}
}
The bus uses SubscriberRegistry.java to map event types to subscriber methods internally.
Posting Events
Publish events through the bus using EventBus#post(Object). The bus traverses the class hierarchy to find subscribers whose parameter type is assignable from the event class:
EventBus bus = new EventBus();
bus.register(new OrderListener());
// Publish event - delivered to all matching subscribers
bus.post(new OrderEvent("12345")); // EventBus.java:58-66
Thread Safety and Execution Model
Direct Execution by Default
By default, EventBus uses MoreExecutors.directExecutor(), meaning subscriber methods execute synchronously in the caller's thread. This synchronous behavior ensures ordering but blocks the publisher until all subscribers complete.
Custom Executors for Asynchronous Processing
Supply a custom Executor during construction to offload processing to background threads. The AsyncEventBus subclass in com/google/common/eventbus/AsyncEventBus.java provides a convenient constructor for this pattern:
Executor executor = Executors.newFixedThreadPool(4);
EventBus asyncBus = new EventBus(
"async-bus",
executor,
EventBus.Dispatcher.perThreadDispatchQueue(),
EventBus.LoggingHandler.INSTANCE
);
Concurrent Event Handling
The bus guarantees that a specific subscriber method is not invoked concurrently for the same event type unless explicitly marked with @AllowConcurrentEvents. This annotation allows the bus to invoke the method from multiple threads simultaneously when using a multi-threaded executor.
Error Handling and Subscriber Exceptions
Exceptions thrown by subscriber methods do not propagate back to the poster. Instead, EventBus catches exceptions and delegates them to a SubscriberExceptionHandler. The default implementation (LoggingHandler inside EventBus) logs exceptions to standard error.
You can provide a custom handler to implement circuit breakers, dead letter queues, or monitoring:
EventBus bus = new EventBus(new SubscriberExceptionHandler() {
@Override
public void handleException(Throwable exception, SubscriberExceptionContext context) {
System.err.println("Exception in " + context.getSubscriberMethod() + ": " + exception);
}
});
Complete Working Example
This example demonstrates the full lifecycle from event definition to consumption:
// 1. Define event payload
public final class UserLoginEvent {
private final String username;
public UserLoginEvent(String username) { this.username = username; }
public String getUsername() { return username; }
}
// 2. Create subscriber
import com.google.common.eventbus.Subscribe;
public class AuditLogger {
@Subscribe
public void recordLogin(UserLoginEvent event) {
System.out.println("AUDIT: User " + event.getUsername() + " logged in");
}
}
// 3. Wire components together
import com.google.common.eventbus.EventBus;
public class Application {
public static void main(String[] args) {
EventBus bus = new EventBus(); // EventBus.java:64-66
bus.register(new AuditLogger()); // EventBus.java:35-37
// Simulate login
bus.post(new UserLoginEvent("alice")); // EventBus.java:58-66
}
}
Handling Unmatched Events with DeadEvent
When no subscribers match a posted event type, Guava wraps the event in a DeadEvent and reposts it. Create a catch-all subscriber to handle orphaned events:
import com.google.common.eventbus.DeadEvent;
import com.google.common.eventbus.Subscribe;
public class DeadEventListener {
@Subscribe
public void onDeadEvent(DeadEvent dead) {
System.out.println("No handler for: " + dead.getEvent());
}
}
Register this listener to monitor for configuration errors or unhandled event types in com/google/common/eventbus/DeadEvent.java.
Summary
- EventBus acts as the central publish-subscribe hub located at
com/google/common/eventbus/EventBus.java, managing registrations and dispatching. - @Subscribe annotation marks handler methods in
com/google/common/eventbus/Subscribe.java, requiring exactly one parameter of the event type. - EventBus#post(Object) delivers events to all matching subscribers based on type assignability, processing them sequentially by default.
- Threading defaults to direct synchronous execution, but custom
Executorinstances orAsyncEventBusenable background processing. - DeadEvent captures events with no subscribers, allowing you to log or handle orphaned messages.
- Exceptions are isolated from publishers through
SubscriberExceptionHandler, preventing cascading failures.
Frequently Asked Questions
What is the difference between EventBus and AsyncEventBus?
EventBus executes subscribers in the caller's thread by default using a direct executor, while AsyncEventBus (in com/google/common/eventbus/AsyncEventBus.java) is a convenience subclass that requires you to provide an Executor during construction. AsyncEventBus immediately returns control to the poster and processes events asynchronously, though you can achieve the same behavior with the base EventBus by passing a custom executor to its constructor.
How does EventBus handle subscriber exceptions?
When a subscriber method throws an exception, the bus catches it and passes it to a SubscriberExceptionHandler. The default handler logs to standard error without interrupting other subscribers or propagating the exception to the event poster. You can customize this behavior by providing your own handler implementation to the EventBus constructor to implement retry logic, metrics collection, or error reporting.
Can a subscriber method receive multiple event types?
Yes, a single subscriber class can define multiple @Subscribe methods, each accepting different event types. However, each individual subscriber method can only accept one specific event type as its parameter. The bus dispatches each event to all matching methods across all registered subscribers based on the runtime type of the posted object.
Is EventBus thread-safe for concurrent registration?
Yes, the EventBus implementation is thread-safe. Multiple threads can safely call register(), unregister(), and post() concurrently. The internal SubscriberRegistry uses thread-safe collections to manage subscriber mappings. However, remember that unless you use @AllowConcurrentEvents, the bus serializes invocations of a specific subscriber method to prevent concurrent execution of the same handler.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →