square/otto
An enhanced Guava-based event bus with emphasis on Android support.
Healthy across all four use cases
weakest axisPermissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓19 active contributors
- ✓Apache-2.0 licensed
- ✓CI configured
Show all 6 evidence items →Show less
- ✓Tests present
- ⚠Stale — last commit 7y ago
- ⚠Concentrated ownership — top contributor handles 64% of recent commits
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/square/otto)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/square/otto on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: square/otto
Generated by RepoPilot · 2026-05-09 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/square/otto shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
GO — Healthy across all four use cases
- 19 active contributors
- Apache-2.0 licensed
- CI configured
- Tests present
- ⚠ Stale — last commit 7y ago
- ⚠ Concentrated ownership — top contributor handles 64% of recent commits
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live square/otto
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/square/otto.
What it runs against: a local clone of square/otto — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in square/otto | Confirms the artifact applies here, not a fork |
| 2 | License is still Apache-2.0 | Catches relicense before you depend on it |
| 3 | Default branch master exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 2729 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of square/otto. If you don't
# have one yet, run these first:
#
# git clone https://github.com/square/otto.git
# cd otto
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of square/otto and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "square/otto(\\.git)?\\b" \\
&& ok "origin remote is square/otto" \\
|| miss "origin remote is not square/otto (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(Apache-2\\.0)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"Apache-2\\.0\"" package.json 2>/dev/null) \\
&& ok "license is Apache-2.0" \\
|| miss "license drift — was Apache-2.0 at generation time"
# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
&& ok "default branch master exists" \\
|| miss "default branch master no longer exists"
# 4. Critical files exist
test -f "otto/src/main/java/com/squareup/otto/Bus.java" \\
&& ok "otto/src/main/java/com/squareup/otto/Bus.java" \\
|| miss "missing critical file: otto/src/main/java/com/squareup/otto/Bus.java"
test -f "otto/src/main/java/com/squareup/otto/AnnotatedHandlerFinder.java" \\
&& ok "otto/src/main/java/com/squareup/otto/AnnotatedHandlerFinder.java" \\
|| miss "missing critical file: otto/src/main/java/com/squareup/otto/AnnotatedHandlerFinder.java"
test -f "otto/src/main/java/com/squareup/otto/Subscribe.java" \\
&& ok "otto/src/main/java/com/squareup/otto/Subscribe.java" \\
|| miss "missing critical file: otto/src/main/java/com/squareup/otto/Subscribe.java"
test -f "otto/src/main/java/com/squareup/otto/Produce.java" \\
&& ok "otto/src/main/java/com/squareup/otto/Produce.java" \\
|| miss "missing critical file: otto/src/main/java/com/squareup/otto/Produce.java"
test -f "otto/src/main/java/com/squareup/otto/ThreadEnforcer.java" \\
&& ok "otto/src/main/java/com/squareup/otto/ThreadEnforcer.java" \\
|| miss "missing critical file: otto/src/main/java/com/squareup/otto/ThreadEnforcer.java"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 2729 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~2699d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/square/otto"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
Otto is a decoupled event bus for Android applications, forked from Google's Guava event bus. It allows different UI components and services to communicate via publish-subscribe events without direct references, using Java annotations (@Subscribe, @Produce) to declare handlers and event sources. Key feature: automatic producer/consumer matching and thread-safe delivery optimized for Android's main thread. Two-module Maven project: otto/ contains the core event bus implementation (otto/src/main/java/com/squareup/otto with Bus.java, AnnotatedHandlerFinder.java, etc.), and otto-sample/ is a complete Android app demonstrating location events. Build uses maven-android-plugin for APK generation.
👥Who it's for
Android app developers building modular apps who need to decouple Activities, Fragments, and Services from each other while maintaining efficient communication. Used by teams at Square and other Android shops that value clean architecture and testability.
🌱Maturity & risk
Mature but deprecated (as of 2014). The project is stable and production-ready (v1.3.8 released) with proper Maven/Gradle distribution, but Square officially recommends migrating to RxJava/RxAndroid for new projects. No active development visible—this is maintenance-only mode.
Low functional risk for existing users, but deprecated status is critical: Square publicly recommends against new projects using Otto. Dependencies are minimal (only Guava for core, Android SDK for samples), but the library is no longer receiving feature updates or security patches. Single-maintainer risk is present as a legacy project.
Active areas of work
No active development. The repo is in maintenance mode—last changes were likely bug fixes or dependency updates. Snapshot version is 1.3.9-SNAPSHOT (pom.xml shows otto-parent at this version), but no ongoing feature work visible. Users are encouraged to migrate to RxJava per the README.
🚀Get running
Clone and build with Maven: git clone https://github.com/square/otto.git && cd otto && mvn clean install. The parent pom.xml (../pom.xml) defines shared config. Sample APK builds with mvn clean package in otto-sample/. No npm/gradle wrapper needed—pure Maven project.
Daily commands:
Build: mvn clean install from repo root. Run tests: mvn test (test files in otto/src/test/). Build sample APK: cd otto-sample && mvn clean package (requires Android SDK). Deploy snapshot: .buildscript/deploy_snapshot.sh (Maven credentials required in .buildscript/settings.xml).
🗺️Map of the codebase
otto/src/main/java/com/squareup/otto/Bus.java— Core event bus implementation that manages subscriber registration, event posting, and thread enforcement—the primary public API every user interacts with.otto/src/main/java/com/squareup/otto/AnnotatedHandlerFinder.java— Reflection-based discovery of @Subscribe and @Produce annotated methods; critical for runtime handler registration and event producer identification.otto/src/main/java/com/squareup/otto/Subscribe.java— Core annotation marking methods as event subscribers; defines the contract for consumer methods across the entire library.otto/src/main/java/com/squareup/otto/Produce.java— Core annotation marking methods as event producers; enables sticky event behavior and initialization logic.otto/src/main/java/com/squareup/otto/ThreadEnforcer.java— Thread safety enforcement strategy (particularly for Android's main thread); critical for safe concurrent event delivery.otto/src/main/java/com/squareup/otto/EventHandler.java— Wraps subscriber methods with reflection and invocation logic; represents a single subscription binding.otto/pom.xml— Maven POM defining library dependencies, versioning, and Android-specific build configuration.
🛠️How to make changes
Add a new event type
- Create a simple POJO event class (e.g., MyEvent.java) with relevant fields and a constructor (
otto-sample/src/main/java/com/squareup/otto/sample/LocationChangedEvent.java) - In your subscriber class, add a method annotated with @Subscribe(threadingMode=ThreadingMode.MAIN) and the event type parameter (
otto-sample/src/main/java/com/squareup/otto/sample/LocationHistoryFragment.java) - Call BusProvider.getInstance().post(new MyEvent(...)) from any object registered with the bus to broadcast the event (
otto-sample/src/main/java/com/squareup/otto/sample/BusProvider.java)
Register/unregister an object as a subscriber
- In your Activity/Fragment lifecycle, call Bus.register(this) in onCreate() or onStart() (
otto-sample/src/main/java/com/squareup/otto/sample/LocationActivity.java) - Ensure @Subscribe methods are defined on the same object (this class must be registered with its own bus instance) (
otto-sample/src/main/java/com/squareup/otto/sample/LocationHistoryFragment.java) - Call Bus.unregister(this) in onDestroy() or onStop() to prevent memory leaks (
otto-sample/src/main/java/com/squareup/otto/sample/LocationActivity.java)
Create a sticky producer for initialization events
- Add a method annotated with @Produce that returns your event type; this event is held by the bus and delivered immediately to new subscribers (
otto/src/test/java/com/squareup/otto/StringProducer.java) - Register the object containing the @Produce method with Bus.register(); subsequent subscribers will immediately receive the last produced event (
otto/src/test/java/com/squareup/otto/EventProducerTest.java) - Update Bus.post() calls to invoke the producer and deliver cached events to newly registered subscribers (
otto/src/main/java/com/squareup/otto/Bus.java)
Add custom thread enforcement
- Create a class implementing ThreadEnforcer interface with custom enforce(targetRunnable, callingThread) logic (
otto/src/main/java/com/squareup/otto/ThreadEnforcer.java) - Pass the custom enforcer to Bus constructor: new Bus(myThreadEnforcer) instead of the default (
otto/src/main/java/com/squareup/otto/Bus.java) - Test your enforcer against expected thread behaviors in unit tests similar to ThreadEnforcerTest.java (
otto/src/test/java/com/squareup/otto/ThreadEnforcerTest.java)
🔧Why these technologies
- Reflection (via AnnotationHandlerFinder) — Enables zero-boilerplate subscriber registration; annotations allow automatic discovery of methods without manual factory registration.
- Java annotations (@Subscribe, @Produce) — Declarative, compile-time safe API for marking handlers; easily IDEs-discoverable and less error-prone than string-based routing.
- ThreadEnforcer pattern — Decouples thread-safety strategy from core bus logic; allows Android main-thread enforcement without coupling to Android SDK in core library.
- Sticky producers (caching last event) — Solves initialization race condition; late subscribers immediately receive the latest state without missing events.
⚖️Trade-offs already made
-
Reflection-based handler discovery at register() time
- Why: Provides a clean, annotation-driven API with minimal boilerplate.
- Consequence: Runtime performance cost and potential memory overhead during registration; not suitable for highly dynamic, short-lived subscribers.
-
Synchronous event delivery (no async queue)
- Why: Simplicity and immediate, ordered delivery of events; easier debugging.
- Consequence: Long-running subscribers block the posting thread; must offload heavy work to background threads manually.
-
Single Bus instance pattern (BusProvider singleton)
- Why: Provides a unified, global event namespace across the app.
- Consequence: Tightly couples distant components; can make testing harder; memory leaks if subscribers aren't unregistered.
-
Type-based event routing (no middleware/interceptors)
- Why: Minimal, focused library; events routed directly by type without transformation.
- Consequence: No built-in support for event filtering, transformation, or cross-cutting concerns; requires custom wrapper events.
🚫Non-goals (don't propose these)
- Asynchronous event delivery or thread pools (user must manage threading explicitly)
- Event persistence or replay across app lifecycle
- Type-safe event ordering guarantees or priority queues
- Built-in request-response or RPC patterns (one-way event emission only)
- Android-specific UI binding framework (only ThreadEnforcer.MainThread support, not LiveData/Rx integration)
🪤Traps & gotchas
Deprecated library: Otto is officially deprecated in favor of RxJava. Sticky events: @Produce-annotated methods create caching behavior—forgetting to clear cached state can leak references or stale data across lifecycle transitions. Thread enforcement: ThreadEnforcer.MAIN catches off-main-thread posts to handlers; must use ThreadEnforcer.NONE in tests or headless code. Reflection overhead: AnnotatedHandlerFinder uses reflection on first registration—no caching across Bus instances; create one shared Bus per app. Android lifecycle: No built-in cleanup—must call bus.unregister(this) in onDestroy() to avoid memory leaks from retained event handlers. Maven Android plugin: Requires Android SDK tools installed (ANDROID_HOME set) to build samples; pom.xml uses deprecated android-maven-plugin (not Gradle).
🏗️Architecture
💡Concepts to learn
- Annotation-driven event routing — Otto uses reflection to scan @Subscribe/@Produce annotations at runtime; understanding how AnnotatedHandlerFinder works is crucial to avoid performance pitfalls and lifecycle bugs
- Publisher-Subscriber pattern — Core architectural pattern in Otto—decouples event sources from handlers; essential for understanding why Otto exists and how to structure apps with it
- Sticky events (producer caching) — Otto's @Produce methods cache and replay events to late subscribers; unique to Otto and can cause subtle bugs if misunderstood (e.g., stale location data persisting across lifecycle)
- Thread confinement / MainThread enforcement — ThreadEnforcer ensures handlers run on correct thread (main vs background); critical for Android apps to prevent ANRs and race conditions in UI code
- Dead event handling — Otto posts unhandled events to DeadEvent subscribers for logging/debugging; understanding this mechanism helps catch integration bugs where events aren't reaching expected handlers
- Reflection-based dependency injection (poor man's DI) — Otto avoids explicit wiring by scanning annotations; understanding reflection overhead and lifecycle implications informs decisions about when to use Otto vs manual composition
🔗Related repos
google/guava— Original event bus that Otto forked and enhanced; understanding Guava's design clarifies Otto's foundation and threading modelReactiveX/RxJava— Recommended successor to Otto—provides same event-driven programming with better composability and thread control; migration path for Otto usersReactiveX/RxAndroid— Android-specific RxJava bindings; the paired library for RxJava on Android that replaces Otto's thread-handling specialtysquare/okhttp— Fellow Square library with similar philosophy of decoupled, testable components; good reference for Android library patterns at Squaregreenrobot/EventBus— Contemporary Android event bus alternative (still maintained); offers similar @Subscribe/@Publish API with different threading/performance tradeoffs for comparison
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add comprehensive thread safety tests for Bus under high contention
The repo has ThreadEnforcerTest.java and ThreadEnforcer.java indicating threading is critical for Android. However, there are no stress tests for concurrent subscriber registration/unregistration with event posting. Given that Otto emphasizes Android support where threading bugs are common, adding concurrent stress tests would catch race conditions and validate thread safety guarantees.
- [ ] Create otto/src/test/java/com/squareup/otto/ConcurrentBusTest.java
- [ ] Add tests for concurrent register/unregister while posting events from multiple threads
- [ ] Add tests for concurrent event posting with 50+ subscribers
- [ ] Verify ThreadEnforcer behavior under concurrent access patterns
- [ ] Reference existing ReentrantEventsTest.java patterns for event re-entrancy during posting
Migrate from deprecated Android Maven plugin to Gradle/AGP
The otto-sample/pom.xml uses the deprecated android-maven-plugin (com.simpligility.maven.plugins). Modern Android development uses Gradle with Android Gradle Plugin. This migration would modernize the sample app, improve build times, and make it easier for new Android developers to contribute and understand the sample.
- [ ] Convert otto-sample/pom.xml to Gradle (build.gradle.kts or build.gradle)
- [ ] Create android/build.gradle with proper AGP configuration (minSdk, targetSdk, compileSdk)
- [ ] Migrate AndroidManifest.xml configuration from Maven to build.gradle
- [ ] Update .travis.yml to build using Gradle instead of Maven
- [ ] Ensure otto-sample APK builds and LocationActivity sample works with new build system
Add integration tests for Subscribe/Produce annotations across package boundaries
AnnotatedHandlerFinderTest.java and AnnotatedProducerFinderTest.java exist, but only test within the com.squareup.otto package. The outside/ test package suggests package-visibility concerns. Add comprehensive tests verifying that @Subscribe/@Produce annotations work correctly across different package visibility modifiers (package-private, public, inherited) to prevent future regressions.
- [ ] Create otto/src/test/java/com/squareup/otto/AnnotationVisibilityTest.java
- [ ] Create test classes in different packages (e.g., otto/src/test/java/com/example/otto/)
- [ ] Test @Subscribe on package-private, protected, and public handler methods
- [ ] Test @Produce on methods with various visibility modifiers
- [ ] Test handler discovery in parent classes across package boundaries
- [ ] Verify existing OutsideEventBusTest.java covers these scenarios or consolidate
🌿Good first issues
- Add test coverage for DeadEvent handling: otto/src/main/java/com/squareup/otto/DeadEvent.java exists but no visible tests in otto/src/test/. Create otto/src/test/java/com/squareup/otto/DeadEventTest.java covering unhandled event routing, DeadEvent subscribers, and thread enforcement for dead events.
- Document thread modes in Subscribe annotation: Subscribe.java (otto/src/main/java/com/squareup/otto/) lacks Javadoc explaining when to use MAIN vs POSTING vs ASYNC threading. Add detailed Javadoc with Android-specific examples (e.g., 'use MAIN for UI updates, POSTING for synchronous handlers').
- Expand BusProvider example: otto-sample/src/main/java/com/squareup/otto/sample/BusProvider.java is minimal. Add producer example (e.g., LocationProducer with @Produce that caches last location) and document lifecycle cleanup (unregister on Fragment.onDestroy()) with inline comments.
⭐Top contributors
Click to expand
Top contributors
- @JakeWharton — 64 commits
- [@Andy Dennie](https://github.com/Andy Dennie) — 9 commits
- @swankjesse — 5 commits
- @imminent — 4 commits
- @loganj — 2 commits
📝Recent commits
Click to expand
Recent commits
2a67589— Merge pull request #206 from TurboProgramming/patch-1 (JakeWharton)dfac913— Replacement of "compile" with "implementation" (ardacebi)7499e61— Merge pull request #193 from JesseBuss/blog_link (JakeWharton)6f0002a— update blog link (Jesse Buss)a85ae72— Add a reference to Kaushik’s blog post on Otto to Rx (swankjesse)35f3d8e— Merge pull request #181 from square/jwilson_notto (JakeWharton)008bd6e— Deprecate Otto in favor of RxJava (swankjesse)8d17f1e— Merge pull request #171 from square/jwilson_1026_update_javadoc_links (JakeWharton)e92c9d5— Update links to Javadoc. (swankjesse)f23d016— [maven-release-plugin] prepare for next development iteration (JakeWharton)
🔒Security observations
Otto presents moderate security concerns primarily due to its deprecated status with no active maintenance, outdated dependencies (support-v4 and legacy Android SDK), and reliance on reflection-based annotation processing. While the core event bus architecture is sound and doesn't exhibit obvious injection vulnerabilities, the lack of maintenance combined with dependency risks creates a significant threat. Organizations using Otto should prioritize migration to RxJava/RxAndroid. If continued use is necessary, implement comprehensive security testing and consider vendoring dependencies with security patches applied locally.
- High · Deprecated Project with No Active Maintenance —
README.md, Project Status. Otto is officially deprecated in favor of RxJava and RxAndroid. The project is no longer actively maintained, meaning security vulnerabilities discovered in the codebase will not receive patches or updates. This creates a significant risk for applications that continue to depend on this library. Fix: Migrate to RxJava and RxAndroid as recommended by the project maintainers. If migration is not immediately possible, conduct a thorough security audit of Otto and implement compensating controls in dependent applications. - Medium · Outdated Dependency: Android Support Library —
otto-sample/pom.xml, otto/pom.xml. The project uses legacy 'support-v4' library which has been superseded by AndroidX. Legacy support libraries may contain unpatched security vulnerabilities and are no longer receiving updates from Google. Fix: Migrate from support-v4 to AndroidX equivalents (androidx.appcompat:appcompat and androidx.fragment:fragment). Update all dependency references accordingly. - Medium · Outdated Google Android Dependency —
otto-sample/pom.xml. The project depends on 'com.google.android:android' which is an outdated SDK. Modern Android development should use the Android SDK from the Maven Central Repository or use Android Studio's Gradle dependencies. Fix: Replace the legacy 'com.google.android:android' dependency with the modern Android SDK dependencies via Gradle or update Maven dependencies to use current Android libraries. - Medium · Potential Reflection-Based Security Risk —
otto/src/main/java/com/squareup/otto/AnnotatedHandlerFinder.java. The codebase uses annotation-based handler discovery (AnnotatedHandlerFinder.java) which relies on reflection. If event classes or handlers can be influenced by untrusted input, this could potentially be exploited for reflection-based attacks or unexpected code execution. Fix: Validate and sanitize all event objects and handler registrations. Ensure that event publishing cannot be directly influenced by untrusted external input. Consider implementing a whitelist of allowed event types. - Low · Credentials in Build Configuration Files —
.buildscript/settings.xml, .buildscript/deploy_snapshot.sh. The presence of '.buildscript/settings.xml' and 'deploy_snapshot.sh' files suggests deployment credentials may be stored in the repository or these files. Even if credentials are not currently exposed, the presence of these files indicates a historical risk. Fix: Ensure credentials are never committed to the repository. Use environment variables or secure credential management systems. Verify git history for any accidentally committed credentials and rotate them if found. - Low · Lack of Input Validation in Event Bus —
otto/src/main/java/com/squareup/otto/Bus.java. The Bus implementation may not adequately validate event objects before processing them. While event buses typically expect controlled event types, lack of validation could lead to unexpected behavior with malformed objects. Fix: Implement validation for event objects, especially in public-facing APIs. Add checks for null events and ensure type safety. Document expected event object requirements.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.