Introduction
When building complex server-side applications, managing communication between components without tight coupling is a fundamental challenge. While message brokers like RabbitMQ and NATS handle inter-service communication, in-process event buses solve a different problem: enabling decoupled communication between components within the same application process.
An event bus implements the publish-subscribe pattern in-process, allowing one component to publish events without knowing which other components will handle them. This is essential for keeping your codebase modular, testable, and maintainable — especially in plugin architectures, UI frameworks, and microservices with complex internal logic.
In this article, we compare four leading open-source in-process event bus libraries: greenrobot EventBus (24,725 ⭐), Guava EventBus (part of Google Guava, 51,487 ⭐), MBassador (975 ⭐), and Square Otto (5,122 ⭐). We evaluate their performance characteristics, thread models, annotation styles, and suitability for different use cases.
Comparison Table
| Feature | EventBus (greenrobot) | Guava EventBus | MBassador | Otto (Square) |
|---|---|---|---|---|
| Stars | 24,725 | 51,487 (Guava) | 975 | 5,122 |
| Language | Java | Java | Java | Java |
| Thread Model | Flexible (posting/single/async) | Synchronous by default; async executor | Configurable per-handler | Main thread + async |
| Subscription | @Subscribe annotation | @Subscribe annotation | @Handler annotation | @Subscribe/@Produce |
| Sticky Events | Yes | No | No | No |
| Priority/Ordering | Subscriber priority | No ordering | Handler priority | No ordering |
| Error Handling | SubscriberExceptionEvent | SubscriberExceptionContext | Custom error handlers | EventBus catches + rethrows |
| Last Updated | Feb 2026 | Jun 2026 (active) | Dec 2025 | Dec 2018 (deprecated) |
| Android Support | Excellent (first-class) | Limited | Via Java | Good |
| Thread Safety | Yes | Yes | Yes | Yes |
EventBus (greenrobot) — The Performance King
EventBus by greenrobot is the most widely-used standalone event bus library in the Java ecosystem, with over 24,000 GitHub stars. Originally designed for Android, it has proven equally effective in server-side Java applications.
Key Features:
- Thread mode annotations:
@Subscribe(threadMode = ThreadMode.BACKGROUND)gives you fine-grained control over which thread handles events - Sticky events: Late subscribers can receive the most recent event of a type
- Subscriber priorities: Control the order in which subscribers receive events
- Zero configuration: Works without any setup — just annotate and go
- ProGuard/R8 friendly: No reflection overhead in production builds
Basic Usage:
| |
EventBus delivers events in approximately 2-5 microseconds per post on modern hardware, making it suitable for high-throughput server applications. Its thread mode flexibility (POSTING, MAIN, MAIN_ORDERED, BACKGROUND, ASYNC) gives developers precise control over execution context.
Guava EventBus — The Google Standard
Guava’s EventBus is part of Google’s core Java libraries, used internally at Google across thousands of services. With 51,000+ stars on the parent project, it benefits from rigorous testing and battle-hardened reliability.
Key Features:
- Simple API:
@Subscribeannotation with no thread mode — synchronous by default, async via executor - Dead events: Unhandled events are automatically posted to a dead event channel
- Hierarchical events: Subscribers to a parent event type also receive child event types
- Lightweight: Zero external dependencies beyond Guava itself
Basic Usage:
| |
Guava’s EventBus is deliberately simple — it doesn’t offer sticky events, subscriber priorities, or thread mode annotations. This simplicity is its strength: there’s less to configure, fewer footguns, and the behavior is always predictable. The dead event mechanism (@Subscribe void handleDead(DeadEvent e)) provides a safety net for unhandled events that other libraries lack.
MBassador — High Throughput Specialist
MBassador (Message Bus Ambassador) is purpose-built for high-throughput, multi-threaded environments. While smaller in adoption (975 stars), it offers unique features not found in other event buses.
Key Features:
- Handler priorities: Numeric priority values determine execution order
- Filtered subscriptions: Subscribe with conditions:
@Handler(priority = 1, filters = {@Filter(HighPriority.class)}) - Strong reference mode: Prevents garbage collection of subscribers
- Configurable reference types: Weak, strong, or custom references
- Async message publication: Built-in support for asynchronous dispatch
- MessagePublication object: Track per-message delivery status
Basic Usage:
| |
MBassador can handle millions of events per second on multi-core systems when properly configured. Its handler priority system and filtered subscriptions make it the best choice for complex event processing pipelines where ordering and conditional delivery matter.
Otto (Square) — The Deprecated Classic
Otto by Square was one of the first popular event bus libraries for Android and Java. While officially deprecated since 2018, it deserves mention as the spiritual predecessor to many modern event bus designs. Otto was a fork of Guava’s EventBus enhanced with Android-specific features.
Basic Usage (Historical):
| |
Otto’s key contributions were the @Produce annotation (for initial-value events) and ThreadEnforcer (for compile-time thread safety checks). While Otto itself is deprecated, its design patterns live on in EventBus and modern reactive frameworks.
Performance Benchmark Comparison
Here’s a summary of performance characteristics based on community benchmarks (operations per second on a 4-core system):
| Metric | EventBus | Guava EventBus | MBassador |
|---|---|---|---|
| Single-thread posts/sec | ~2.5M | ~1.8M | ~3.2M |
| Multi-thread posts/sec | ~5.0M | ~3.2M | ~8.1M |
| Post-to-handler latency | ~3µs | ~5µs | ~1.5µs |
| Memory overhead (no subscribers) | ~500KB | ~300KB | ~800KB |
| Zero-GC pressure | Good | Excellent | Very Good |
MBassador consistently leads in raw throughput, followed by EventBus, then Guava. However, Guava has the lowest memory footprint and the most predictable behavior under load.
Choosing the Right Event Bus
- Choose EventBus if: You need a battle-tested, Android-compatible solution with flexible thread models and sticky events. Ideal for applications spanning mobile and server-side.
- Choose Guava EventBus if: You already use Guava, value simplicity over features, and need hierarchical event dispatch. Best for server-side applications where predictable synchronous behavior is preferred.
- Choose MBassador if: You need maximum throughput, handler priorities, or filtered subscriptions. The right choice for high-performance event processing pipelines where every microsecond counts.
- Use Otto’s patterns, not its code: Otto is deprecated, but its @Produce concept and ThreadEnforcer approach are worth implementing in custom solutions.
For related patterns, see our guides on zero-downtime message broker HA for inter-service messaging, circuit breaker resilience patterns for fault tolerance, and brokerless messaging with ZeroMQ for lightweight IPC alternatives.
FAQ
When should I use an in-process event bus instead of a message broker?
Use an in-process event bus when all communicating components live within the same JVM process. It offers sub-microsecond latency (versus millisecond-range for brokers) and zero network overhead. Use a message broker (RabbitMQ, NATS, Kafka) when components run in different processes, containers, or machines.
Is an event bus the same as reactive streams (RxJava, Reactor)?
No. An event bus is a many-to-many broadcast mechanism where any subscriber can receive any event type. Reactive streams (RxJava, Project Reactor) are composable pipelines for asynchronous data processing with backpressure support. Event buses complement reactive programming by providing the event source.
Are event buses thread-safe?
Yes — all four libraries discussed are explicitly designed for thread safety. However, thread safety means the bus itself won’t corrupt under concurrent access, not that your handler code is automatically thread-safe. Always synchronize shared state accessed from multiple handler threads.
What happens to events if no subscriber is registered?
In EventBus and MBassador, events with no subscribers are silently dropped. Guava EventBus posts them to a DeadEvent channel, allowing you to register a handler for unhandled events — useful for logging, metrics, or triggering fallback logic.
Can I use these event buses in Kotlin or other JVM languages?
Yes. All four libraries work seamlessly with Kotlin, Scala, Groovy, and Clojure. Kotlin’s coroutine support pairs well with EventBus’s async thread modes, and Scala developers often combine Akka’s actor model with EventBus for intra-actor messaging.
How do I avoid memory leaks with event bus subscribers?
The most common memory leak pattern is forgetting to unregister subscribers. Always call bus.unregister(this) in your cleanup/shutdown lifecycle. EventBus uses weak references by default for some configurations, but explicit unregistration is the safest approach. Consider using try-with-resources or lifecycle-aware components (Spring beans, Android Lifecycle) to automate cleanup.
💰 想测试你的市场判断力?我用 Polymarket 做预测市场交易——这是全球最大的预测市场平台,从大选结果到技术监管时间线,什么都可以押注。和赌博不同,这是真正的信息市场:你懂的信息越多,胜率越高。我靠预测技术相关事件的走向已经赚了不少。用我的邀请链接注册:Polymarket.com