Open-source project
JakeWharton/RxReplayingShare avatar
JakeWharton/RxReplayingShare

ReplayingShare: the RxJava operator that caches only while you listen

An RxJava transformer which combines replay(1), publish(), and refCount() operators.

619 stars28 forksJavaApache-2.0

At a glance

What is it?
ReplayingShare is a small RxJava transformer that behaves like replay(1) and publish() at once: it disconnects from an expensive upstream when nobody subscribes, yet still hands the last value to the next subscriber that arrives. The trade-off table in its documentation is the clearest argument for it.
Who is it for?
ReplayingShare earns a place in an Android or JVM codebase where one expensive upstream is shared by screens that come and go, such as a rendered chart behind a debounce, where dropping the upstream between subscriptions is worth more than keeping a warm cache. It is the wrong choice for finite streams, for pipelines where a terminal event is normal, and for projects already on RxJava 1 or 2 who would have to adopt a separate artifact line.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly Java, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The gap the operator fills between replay and publish

RxJava gives you two behaviours that pull in opposite directions. `replay(1)` keeps the last emitted value so a new subscriber receives it immediately, but the upstream stays connected for the lifetime of that cached value. `publish()` shares one upstream connection among subscribers, but a subscriber arriving later gets nothing until the next emission. `refCount()` on top of either decides when the upstream should disconnect. `ReplayingShare` is a transformer that combines all three, and its own documentation compares it against the two obvious hand rolled combinations in a table with three rows: disconnecting from the upstream when there are no subscribers, replaying the latest value to new subscribers while other subscribers are active, and replaying the latest value to new subscribers when no other subscriber is active. It is the third row that no other entry satisfies.

The cache fills only while somebody is connected

The distinguishing mechanism is narrow and worth stating precisely. The last emitted value is cached only when one or more downstream subscribers are connected to the shared instance. That single condition buys two things at once. When the last subscriber goes away, the upstream can be shut down, so an expensive source stops burning work while the app is in the background. And when a new subscriber arrives later, the value last seen by any subscriber is replayed to it, so the newcomer gets a populated screen rather than an empty one. The catch sits in the same sentence: if nobody was connected when the value arrived, there is nothing to replay. A subscriber that connects to a quiet upstream waits for the next real emission, exactly as it would with `publish()`.

Reading the trade-off table before you choose

The alternatives are spelled out in the project's own comparison, and each one fails in a specific direction. `replay(1).refCount()` disconnects when subscribers go away, which is what you want for cost, but it does not replay the latest value to a new subscriber when no other subscriber is active, so the first arrival after an idle period starts from nothing. `publish().refCount()` also disconnects, and it never replays at all, not even while other subscribers are active. `replay(1).autoConnect(1)` is the opposite profile: it replays in both cases, including when the upstream has been idle, but it never disconnects from the upstream, so the expensive source runs forever. If your upstream is cheap, the autoConnect variant is simpler. If it is expensive and you can tolerate an empty first frame, `replay(1).refCount()` may be enough. The operator exists for the case where neither trade-off is acceptable.

Composing it onto a shared upstream

The usage pattern is to apply the transformer once to an upstream and cache the resulting instance so that every new subscriber gets the same shared stream. In Java the transformer is applied with `compose` and the static `ReplayingShare.instance()`:

java
@Singleton class Chart {
  private final Observable<Bitmap> chart;

  @Inject Chart(Observable<List<Data>> data) {
    chart = data.debounce(1, SECONDS)
        .map(list -> bigExpensiveRenderChartToBitmapFunction(list))
        .compose(ReplayingShare.instance());
  }

  Observable<Bitmap> data() {
    return chart;
  }
}

Kotlin users get the same behaviour through an extension function, which comes from a separate optional artifact:

kotlin
@Singleton class Chart
@Inject constructor(data: Observable<List<Data>>) {
  val chart: Observable<Bitmap> = data.debounce(1, SECONDS)
      .map(list -> bigExpensiveRenderChartToBitmapFunction(list))
      .replayingShare()
}

The example is deliberately about an expensive render: the point of the operator is that the render is not repeated for every new subscriber while the upstream is connected, and is not repeated at all while nobody is listening.

Any terminal event clears the cache

There is a constraint in the documentation that decides where this operator belongs. It is designed for composition with infinite or extremely long-lived streams, and any terminal event will clear the cached value. That is sensible for the intended case, since a stream that has completed will not emit again and keeping its last value would only produce a stale picture. It also means the operator is a poor fit for a stream that ends by design, such as a one-shot request that completes after returning a result, or a stream that is repeated on a timer and completes each cycle. In those cases the replay guarantee quietly disappears at the moment you would most like to have it, and the project documentation does not describe any way to keep a terminal value cached.

Three artifact lines, one per RxJava generation

The library is published as separate artifacts per RxJava major version, because the operator is source compatible with each generation. For RxJava 3 the Gradle coordinates are:

groovy
implementation 'com.jakewharton.rx3:replaying-share:3.0.0'
// Optional:
implementation 'com.jakewharton.rx3:replaying-share-kotlin:3.0.0'

The Kotlin artifact is optional and only needed if you want the extension function. The Maven form carries the same coordinates:

xml
<dependency>
  <groupId>com.jakewharton.rx3</groupId>
  <artifactId>replaying-share</artifactId>
  <version>3.0.0</version>
</dependency>
<!-- Optional: -->
<dependency>
  <groupId>com.jakewharton.rx3</groupId>
  <artifactId>replaying-share-kotlin</artifactId>
  <version>3.0.0</version>
</dependency>

For RxJava 2.x the group is `com.jakewharton.rx2` at version 2.2.0, and for RxJava 1.x the group is `com.jakewharton.rx` at version 1.0.1, each with a matching optional kotlin artifact. Snapshots of the development version are published to Sonatype's snapshots repository, which is where you look if you need a change that has not reached a release.

A small library with a short history

The repository is deliberately small, which suits a single operator. The top level holds the `replaying-share` module and the `replaying-share-kotlin` module, a Gradle build with `build.gradle`, `settings.gradle` and `gradle.properties`, a `CHANGELOG.md`, and a `marbles.png` marble diagram with its `marbles.key` source, the standard way this kind of library documents the timing of an operator. The default branch is `trunk`, and there are no GitHub releases, so version history lives in the changelog file rather than in release tags. The last push was on 2026-10-15. The licence is Apache-2.0, with the copyright line dated 2016, so the code has been out there long enough to be trusted while the surrounding documentation remains thin: nothing in the documentation covers backpressure behaviour for the shared Flowable, scheduler assumptions, or what happens when two subscribers connect at the same instant.

Editorial conclusion

ReplayingShare earns a place in an Android or JVM codebase where one expensive upstream is shared by screens that come and go, such as a rendered chart behind a debounce, where dropping the upstream between subscriptions is worth more than keeping a warm cache. It is the wrong choice for finite streams, for pipelines where a terminal event is normal, and for projects already on RxJava 1 or 2 who would have to adopt a separate artifact line. Before adding it, check the artifact coordinates for your RxJava generation, since the project publishes distinct versions per major line, and confirm that the upstream you are sharing is genuinely long lived, because any terminal event clears the cached value and the documentation does not describe backpressure or scheduler behaviour for the operator.

Frequently asked questions

What does ReplayingShare do that replay(1).refCount() does not?

Both disconnect from the upstream when there are no subscribers, but replay(1).refCount() does not replay the latest value to a new subscriber when no other subscriber is active. ReplayingShare does replay the last value seen by any subscriber in that situation.

Which RxJava versions does ReplayingShare support?

It is published per RxJava generation: com.jakewharton.rx3:replaying-share:3.0.0, com.jakewharton.rx2:replaying-share:2.2.0 and com.jakewharton.rx:replaying-share:1.0.1. Each line has an optional kotlin artifact for the extension function.

How do I use ReplayingShare from Kotlin?

Add the optional replaying-share-kotlin artifact for your RxJava line and call the extension function on the upstream, as in the documented example where a debounced stream is mapped to a bitmap and then given a replayingShare() call.

Does ReplayingShare work with a stream that completes?

Not well. The operator is designed for infinite or extremely long-lived streams, and any terminal event clears the cached value, so the replay guarantee is gone once the upstream completes.

Where can I get a snapshot build of ReplayingShare?

Snapshots of the development version are published in Sonatype's snapshots repository, so you can depend on the current build before a numbered release appears.

Official sources

  1. Issues
  2. JakeWharton/RxReplayingShare on GitHub
  3. License: Apache-2.0
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/jakewharton-rxreplayingshare.svg)](https://hysenlabs.com/projects/jakewharton-rxreplayingshare)

Was this useful?