Everything Java

← Back to blog

Published on Fri May 16 2025 12:00:00 GMT+0000 (Coordinated Universal Time) by Purusothaman Ramanujam

Google Guava EventBus: Simple Publish-Subscribe Pattern

Introduction

Google Guava’s EventBus provides a simple and effective way to implement the publish-subscribe pattern in Java applications. It allows components to communicate without direct dependencies, promoting loose coupling and event-driven architecture.

In this post, we’ll explore how to use EventBus for event handling and communication between components.

Adding Guava to Your Project

Maven Dependency

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>

Gradle Dependency

implementation 'com.google.guava:guava:32.1.3-jre'

Basic EventBus Usage

EventBus allows you to post events and have them handled by registered subscribers:

import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
public class BasicEventBusExample {
public static class MessageEvent {
private final String message;
public MessageEvent(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
public static class MessageHandler {
@Subscribe
public void handleMessage(MessageEvent event) {
System.out.println("Received message: " + event.getMessage());
}
@Subscribe
public void handleString(String message) {
System.out.println("Received string: " + message);
}
}
public static void main(String[] args) {
EventBus eventBus = new EventBus();
MessageHandler handler = new MessageHandler();
// Register handler
eventBus.register(handler);
// Post events
eventBus.post(new MessageEvent("Hello, EventBus!"));
eventBus.post("Simple string message");
// Unregister handler
eventBus.unregister(handler);
}
}

Event Types and Handlers

Multiple Event Types

public class MultipleEventTypesExample {
public static class UserEvent {
private final String userId;
private final String action;
public UserEvent(String userId, String action) {
this.userId = userId;
this.action = action;
}
public String getUserId() { return userId; }
public String getAction() { return action; }
}
public static class OrderEvent {
private final String orderId;
private final double amount;
public OrderEvent(String orderId, double amount) {
this.orderId = orderId;
this.amount = amount;
}
public String getOrderId() { return orderId; }
public double getAmount() { return amount; }
}
public static class EventHandler {
@Subscribe
public void handleUserEvent(UserEvent event) {
System.out.println("User event: " + event.getUserId() + " - " + event.getAction());
}
@Subscribe
public void handleOrderEvent(OrderEvent event) {
System.out.println("Order event: " + event.getOrderId() + " - $" + event.getAmount());
}
@Subscribe
public void handleStringEvent(String event) {
System.out.println("String event: " + event);
}
}
public static void main(String[] args) {
EventBus eventBus = new EventBus();
EventHandler handler = new EventHandler();
eventBus.register(handler);
// Post different types of events
eventBus.post(new UserEvent("user123", "login"));
eventBus.post(new OrderEvent("order456", 99.99));
eventBus.post("System notification");
eventBus.unregister(handler);
}
}

Multiple Handlers

public class MultipleHandlersExample {
public static class NotificationEvent {
private final String message;
private final String type;
public NotificationEvent(String message, String type) {
this.message = message;
this.type = type;
}
public String getMessage() { return message; }
public String getType() { return type; }
}
public static class EmailHandler {
@Subscribe
public void handleNotification(NotificationEvent event) {
if ("email".equals(event.getType())) {
System.out.println("Sending email: " + event.getMessage());
}
}
}
public static class SMSHandler {
@Subscribe
public void handleNotification(NotificationEvent event) {
if ("sms".equals(event.getType())) {
System.out.println("Sending SMS: " + event.getMessage());
}
}
}
public static class LoggingHandler {
@Subscribe
public void handleNotification(NotificationEvent event) {
System.out.println("Logging notification: " + event.getType() + " - " + event.getMessage());
}
}
public static void main(String[] args) {
EventBus eventBus = new EventBus();
EmailHandler emailHandler = new EmailHandler();
SMSHandler smsHandler = new SMSHandler();
LoggingHandler loggingHandler = new LoggingHandler();
// Register all handlers
eventBus.register(emailHandler);
eventBus.register(smsHandler);
eventBus.register(loggingHandler);
// Post events
eventBus.post(new NotificationEvent("Welcome!", "email"));
eventBus.post(new NotificationEvent("Order confirmed", "sms"));
// Unregister handlers
eventBus.unregister(emailHandler);
eventBus.unregister(smsHandler);
eventBus.unregister(loggingHandler);
}
}

AsyncEventBus

For asynchronous event processing, use AsyncEventBus:

import com.google.common.eventbus.AsyncEventBus;
import java.util.concurrent.Executors;
public class AsyncEventBusExample {
public static class AsyncEventHandler {
@Subscribe
public void handleAsyncEvent(String event) {
System.out.println("Async handler: " + event + " on thread: " + Thread.currentThread().getName());
// Simulate some processing time
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) {
// Create AsyncEventBus with custom executor
AsyncEventBus eventBus = new AsyncEventBus(Executors.newFixedThreadPool(4));
AsyncEventHandler handler = new AsyncEventHandler();
eventBus.register(handler);
// Post events asynchronously
for (int i = 1; i <= 5; i++) {
eventBus.post("Event " + i);
}
System.out.println("All events posted, they will be processed asynchronously");
// Wait for processing to complete
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
eventBus.unregister(handler);
}
}

Error Handling

EventBus provides error handling through DeadEvent and custom error handlers:

import com.google.common.eventbus.DeadEvent;
import com.google.common.eventbus.Subscribe;
public class ErrorHandlingExample {
public static class ErrorHandler {
@Subscribe
public void handleDeadEvent(DeadEvent deadEvent) {
System.out.println("No handler found for event: " + deadEvent.getEvent());
}
}
public static class FailingHandler {
@Subscribe
public void handleEvent(String event) {
if ("fail".equals(event)) {
throw new RuntimeException("Intentional failure");
}
System.out.println("Processed: " + event);
}
}
public static void main(String[] args) {
EventBus eventBus = new EventBus();
ErrorHandler errorHandler = new ErrorHandler();
FailingHandler failingHandler = new FailingHandler();
eventBus.register(errorHandler);
eventBus.register(failingHandler);
// This will be handled normally
eventBus.post("normal event");
// This will cause an exception
eventBus.post("fail");
// This will become a DeadEvent
eventBus.post(123);
eventBus.unregister(errorHandler);
eventBus.unregister(failingHandler);
}
}

Practical Examples

User Management System

public class UserManagementSystem {
public static class User {
private String id;
private String name;
private String email;
public User(String id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
// Getters
public String getId() { return id; }
public String getName() { return name; }
public String getEmail() { return email; }
}
public static class UserCreatedEvent {
private final User user;
public UserCreatedEvent(User user) {
this.user = user;
}
public User getUser() { return user; }
}
public static class UserDeletedEvent {
private final String userId;
public UserDeletedEvent(String userId) {
this.userId = userId;
}
public String getUserId() { return userId; }
}
public static class EmailService {
@Subscribe
public void handleUserCreated(UserCreatedEvent event) {
User user = event.getUser();
System.out.println("Sending welcome email to: " + user.getEmail());
}
}
public static class AuditService {
@Subscribe
public void handleUserCreated(UserCreatedEvent event) {
User user = event.getUser();
System.out.println("Audit: User created - " + user.getId() + " (" + user.getName() + ")");
}
@Subscribe
public void handleUserDeleted(UserDeletedEvent event) {
System.out.println("Audit: User deleted - " + event.getUserId());
}
}
public static class UserService {
private final EventBus eventBus;
public UserService(EventBus eventBus) {
this.eventBus = eventBus;
}
public void createUser(String id, String name, String email) {
User user = new User(id, name, email);
// Save user to database
System.out.println("User created: " + user.getName());
// Notify subscribers
eventBus.post(new UserCreatedEvent(user));
}
public void deleteUser(String userId) {
// Delete user from database
System.out.println("User deleted: " + userId);
// Notify subscribers
eventBus.post(new UserDeletedEvent(userId));
}
}
public static void main(String[] args) {
EventBus eventBus = new EventBus();
EmailService emailService = new EmailService();
AuditService auditService = new AuditService();
UserService userService = new UserService(eventBus);
// Register services
eventBus.register(emailService);
eventBus.register(auditService);
// Create and delete users
userService.createUser("1", "John Doe", "john@example.com");
userService.createUser("2", "Jane Smith", "jane@example.com");
userService.deleteUser("1");
eventBus.unregister(emailService);
eventBus.unregister(auditService);
}
}

Best Practices

  1. Keep Events Simple: Events should be simple data carriers
  2. Use Descriptive Event Names: Make event names clear and descriptive
  3. Handle Exceptions: Implement proper error handling in event handlers
  4. Unregister Handlers: Always unregister handlers when done
  5. Use AsyncEventBus for Long Operations: Use async processing for time-consuming operations
  6. Monitor Event Flow: Use DeadEvent handlers to monitor unhandled events

Common Pitfalls

  1. Memory Leaks: Forgetting to unregister handlers
  2. Blocking Operations: Performing blocking operations in synchronous handlers
  3. Exception Propagation: Not handling exceptions in event handlers
  4. Event Ordering: Don’t rely on event processing order
  5. Circular Dependencies: Avoid posting events from within event handlers

Performance Considerations

  1. Synchronous vs Asynchronous: Use AsyncEventBus for long-running operations
  2. Handler Registration: Register/unregister handlers efficiently
  3. Event Object Creation: Create event objects efficiently
  4. Memory Usage: Monitor memory usage with many handlers
  5. Thread Safety: EventBus is thread-safe for posting events

Conclusion

Guava’s EventBus provides a simple and effective way to implement the publish-subscribe pattern in Java applications. It promotes loose coupling between components and enables event-driven architecture.

Use EventBus for decoupling components, implementing cross-cutting concerns, and building reactive systems. Remember to handle errors properly and unregister handlers when done.

EventBus is particularly useful for implementing features like logging, notifications, auditing, and any scenario where you need loose coupling between event producers and consumers.

Resources

Written by Purusothaman Ramanujam

← Back to blog