Short answer
All sequence/stream processing libs are offering very similar API for pipeline building. The differences are in API for handling multi-threading and composition of pipelines.
Long answer
RxJava is quite different from Stream. Of all JDK things, the closest to rx.Observable
is perhaps java.util.stream.Collector
Stream
+ CompletableFuture
combo (which comes at a cost of dealing with extra monad layer, i. e. having to handle conversion between Stream<CompletableFuture<T>>
and CompletableFuture<Stream<T>>
).
There are significant differences between Observable and Stream:
- Streams are pull-based, Observables are push-based. This may sound too abstract, but it has significant consequences that are very concrete.
- Stream can only be used once, Observable can be subscribed to many times.
Stream#parallel()
splits sequence into partitions,Observable#subscribeOn()
andObservable#observeOn()
do not; it is tricky to emulateStream#parallel()
behavior with Observable, it once had.parallel()
method but this method caused so much confusion that.parallel()
support was moved to separate repository: ReactiveX/RxJavaParallel: Experimental Parallel Extensions for RxJava. More details are in another answer.Stream#parallel()
does not allow to specify a thread pool to use, unlike most of RxJava methods accepting optional Scheduler. Since all stream instances in a JVM use the same fork-join pool, adding.parallel()
can accidentally affect the behaviour in another module of your program.- Streams are lacking time-related operations like
Observable#interval()
,Observable#window()
and many others; this is mostly because Streams are pull-based, and upstream has no control on when to emit next element downstream. - Streams offer restricted set of operations in comparison with RxJava. E.g. Streams are lacking cut-off operations (
takeWhile()
,takeUntil()
); workaround usingStream#anyMatch()
is limited: it is terminal operation, so you can’t use it more than once per stream - As of JDK 8, there’s no
Stream#zip()
operation, which is quite useful sometimes. Streams are hard to construct by yourself, Observable can be constructed by many waysEDIT: As noted in comments, there are ways to construct Stream. However, since there’s no non-terminal short-circuiting, you can’t e. g. easily generate Stream of lines in file (JDK providesFiles#lines()
andBufferedReader#lines()
out of the box though, and other similar scenarios can be managed by constructing Stream from Iterator).- Observable offers resource management facility (
Observable#using()
); you can wrap IO stream or mutex with it and be sure that the user will not forget to free the resource – it will be disposed automatically on subscription termination; Stream hasonClose(Runnable)
method, but you have to call it manually or via try-with-resources. E. g. you have to keep in mind thatFiles#lines()
must be enclosed in try-with-resources block. - Observables are synchronized all the way through (I didn’t actually check whether the same is true for Streams). This spares you from thinking whether basic operations are thread-safe (the answer is always ‘yes’, unless there’s a bug), but the concurrency-related overhead will be there, no matter if your code need it or not.
Round-up
RxJava differs from Streams significantly. Real RxJava alternatives are other implementations of ReactiveStreams, e. g. relevant part of Akka.
Update
There’s trick to use non-default fork-join pool for Stream#parallel
, see Custom thread pool in Java 8 parallel stream.
Update
All of the above is based on the experience with RxJava 1.x. Now that RxJava 2.x is here, this answer may be out-of-date.