# RxRelay: RxJava Subjects Without the Terminal State Trap

> RxRelay adds three relay types to RxJava that behave like Subjects but cannot call onComplete or onError. It is a small, focused library for bridging non-Rx APIs during a migration, and it is worth understanding where that bridge stops being useful.

**JakeWharton/RxRelay** — RxJava types that are both an Observable and a Consumer.

- Repository: https://github.com/JakeWharton/RxRelay
- Stars: 2,454 · Forks: 126
- Language: Java
- License: Apache-2.0
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/jakewharton-rxrelay

## The Subject statefulness problem RxRelay removes

An RxJava Subject is both an Observable and an Observer, which makes it the standard tool for pushing values from a non-Rx source into a reactive pipeline. The catch is the Observable contract: once a Subject receives onComplete or onError, it enters a terminal state and stops being usable for moving data. The README calls this statefulness damaging and notes that while terminal behavior is sometimes desired, most of the time it is not. That mismatch is the entire reason RxRelay exists. A Relay is a Subject with the terminal methods removed from its API surface. You get accept for pushing values and the usual subscription methods for consuming them, but there is no onComplete and no onError to call by accident. The audience is narrow and specific: engineers adapting a callback-based or listener-based API into RxJava, usually mid-migration, who want the convenience of a Subject without the risk that some upstream error path silently kills the stream. The README is explicit that this is a transitional tool, not an architectural destination.

## How BehaviorRelay, PublishRelay and ReplayRelay differ in data flow

All three relays extend a common Relay base class, and the README notes that the base class also allows custom implementations. The difference between the three is purely about what a late subscriber sees. BehaviorRelay emits the most recent item it has observed and all subsequent items to each subscribed Observer. The README's first example creates one with BehaviorRelay.createDefault("default"), subscribes, then accepts "one", "two" and "three", and the observer receives all of those events. The second example shows the replay-of-one behavior: after createDefault("default") and accept("zero"), a subscriber that arrives before "one", "two" and "three" receives those three but not "zero", because only the latest value is retained. PublishRelay emits only items observed after a given subscriber attaches. The README example subscribes observer1, accepts "one" and "two", then subscribes observer2 and accepts "three"; observer1 sees all three, observer2 sees only "three". ReplayRelay buffers everything it observes and replays the full buffer to any later subscriber, so in the README example both observers receive "one", "two" and "three" regardless of when they subscribed. There is no AsyncRelay, and the README gives the reason directly: relays have no terminal events, so there is nothing for the async variant's behavior to support.

## Adding RxRelay to a Gradle or Maven build

The README documents two dependency declarations, one for Gradle and one for Maven. The artifact coordinate is com.jakewharton.rxrelay3:rxrelay and the documented version is 3.0.1. In a Gradle build, add the implementation line to your dependencies block:

```groovy
implementation 'com.jakewharton.rxrelay3:rxrelay:3.0.1'
```

For Maven, the same artifact is declared with groupId, artifactId and version elements:

```xml
<dependency>
  <groupId>com.jakewharton.rxrelay3</groupId>
  <artifactId>rxrelay</artifactId>
  <version>3.0.1</version>
</dependency>
```

The README also notes that snapshots of the development version are published to Sonatype's snapshots repository, though it does not give a version number or coordinate for those. Once the dependency resolves, a first real use is the BehaviorRelay pattern from the README: create the relay with a default value, subscribe an observer, then push values through accept. The observer should see the default immediately on subscription, then each accepted value in order. If you are adapting a listener interface, the listener callback body becomes a single relay.accept(...) call, and the terminal callbacks that a Subject would have forwarded to onComplete or onError simply have nowhere to go.

## Where relays are the wrong tool

The most important limitation is stated by the project itself rather than discovered by users: relays cannot signal completion or failure. If your pipeline genuinely needs to tell downstream operators that a source has finished, or that an error occurred, a Relay cannot express that, and you should use a Subject instead. This is not a missing feature to work around; it is the design. The second limitation is memory. ReplayRelay buffers all items it observes and replays them to every subscriber, and the README describes no bound, eviction policy or size limit on that buffer. A long-lived ReplayRelay fed by a high-volume source will retain everything, so it suits finite or low-volume sequences far better than an unbounded stream. BehaviorRelay retains only the latest value, which is safer, but it also means a subscriber that arrives late silently misses everything before the most recent emission, which can be surprising if you expected a full history. Finally, the README's own framing is a caution: as more code moves to reactive, the need for Subjects and Relays should diminish. If you find yourself designing new architecture around a Relay, you are probably using a transitional bridge as a permanent fixture.

## RxRelay against the Subject it replaces

The honest alternative to RxRelay is the RxJava Subject classes themselves: PublishSubject, BehaviorSubject and ReplaySubject. They cover the same three buffering strategies, so the choice is not about capability but about API surface. A Subject gives you onNext, onComplete and onError, which means any code holding a reference can terminate the stream. A Relay gives you accept and subscription methods only, so the terminal methods are not reachable through the type. If your upstream source can legitimately complete or fail and you want that propagated, a Subject is the right call and RxRelay is the wrong one. If the upstream is a listener that never signals completion, wrapping it in a Subject means every call site is one mistaken onComplete away from a dead stream, and a Relay removes that failure mode entirely. The trade-off is that you lose the ability to represent completion, which is a real cost in pipelines that need it. For a codebase already on RxJava 2 or 3, RxRelay adds one small dependency and no new concepts beyond the three relay types.

## Maintenance, versioning and licence

The repository is not archived, and the last push was on 2026-09-17. The README documents version 3.0.1 as the current release, and the package name com.jakewharton.rxrelay3 encodes the RxJava major version it targets. That naming is worth noting during upgrades: moving from the 2.x line to the 3.x line changes the Maven group and package coordinates, not just the version string, so a dependency bump alone will not work. The repository contains a CHANGELOG.md at the top level, which is where release history would be recorded, though the README itself does not describe an upgrade procedure or a compatibility matrix. The licence is Apache-2.0, with copyright attributed to Netflix, Inc. and Jake Wharton. Apache-2.0 permits commercial and closed-source use, modification and redistribution provided the licence and notices are preserved; it also includes an express grant of patent rights from contributors. That is a general description of the licence text, not legal advice, and teams with specific compliance questions should route them to counsel. Because the library is small and its surface area is three classes plus a base class, the upgrade cost between minor releases is likely to be low, but the 2.x to 3.x coordinate change is the one migration step the README makes visible.

## Conclusion

Adopt RxRelay if you are bridging callback-based or listener-based APIs into RxJava and want to avoid accidental onComplete or onError calls closing a stream you still need. Do not adopt it as a general-purpose event bus replacement or as a substitute for a proper reactive architecture; the README itself says the need for Subjects and Relays should diminish as more code moves to reactive. Before adding it, verify that your RxJava version matches the com.jakewharton.rxrelay3:rxrelay:3.0.1 artifact, because the package name is versioned and the old 2.x coordinates will not resolve to this release.

## FAQ

### What is RxRelay in RxJava?

RxRelay provides RxJava types that are both an Observable and a Consumer, essentially Subjects with the ability to call onComplete or onError removed. The README describes them as a way to bridge non-Rx APIs into Rx without the worry of accidentally triggering a terminal state.

### What is the difference between BehaviorRelay and PublishRelay?

BehaviorRelay emits the most recent item it has observed and all subsequent items to each subscribed Observer, while PublishRelay emits only items observed after a given subscriber attaches. In the README's PublishRelay example, the first observer receives every accepted value and the second receives only the value accepted after it subscribed.

### Is there an AsyncRelay in RxRelay?

No. The README states there is no AsyncRelay because relays have no terminal events to support its behavior.

## Sources

- [Issues](https://github.com/JakeWharton/RxRelay/issues)
- [JakeWharton/RxRelay on GitHub](https://github.com/JakeWharton/RxRelay)
- [License: Apache-2.0](https://github.com/JakeWharton/RxRelay/blob/trunk/LICENSE)
- [README](https://github.com/JakeWharton/RxRelay/blob/trunk/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/jakewharton-rxrelay
