Reactive programming with Java 8 and simple-react : pull / push model

John McClean
Mar 30, 2015 · 3 min read

simple-react operates under a mixed pull /push model, with a default state of pull being dominant over push. All tasks within a simple-react Stream are executed as CompletableFuture tasks on a Java ExecutorService. Each of those tasks are chained together using the CompletableFuture chaining facilities. The CompletableFuture chains are managed or created by a Stream.

The Stream pulls the chain of CompletableFutures into existance. Data is pushed asynchronously through the chain of CompletableFutures when it is ready.

bookmark simple-react on github!
fig 1. forEach pulls the chain of CompletableFutures into existance, data is pushed through that chain

NB as of simple-react 0.95 Stream terminal operations such as forEach, collect etc take full advantage of LazyFutureStream parallel asynchronous operations (i.e. up to the configurable max of async Chains of CompletableFutures will be pulled into existence).

The LazyFutureStream forEach operator will pull chains into existance one at a time. For EagerFutureStream and SimpleReactStreams, all chains are created immediately and data starts to push through immediately.

The run operator for LazyFutureStream allows greater managed parallelism, with a controlled number of chains being created at one time.

fig 2. the run operator allows more active CompletableFuture chains for LazyFutureStream

Building push based systems

It is possible to build push based systems using simple-react. simple-react provides three data structures (async.Queue, async.Topic & async.Signal) that can be used to push data into a simple-react Stream (or even a standard JDK 8 Stream).

fig 3. data pushed to a Queue and then pulled when ready into a simple-react LazyFutureStream


One challenge of allowing external code to push data into a Stream, is that it may push data in at a faster rate than the Stream can process it (predicably causing out of memory errors). Users can decide whether or not pushable Streams should use bounded or unbounded async datastructures to accept data.

An unbounded Queue or Topic will accept all messages added to it, and may mean if the producer is faster than the consuming Stream that your application may run out of memory.

A bounded Queue or Topic allows applications to either block when the Queue or Topic is full, or to force the producer to drop messages. User code can control this by calling offer (blocking) or add (dropping).

The Tutorial : Reactive programming with Java 8

John McClean

Written by

Architecture @ Verizon Media. Maintainer of Cyclops. Twitter @johnmcclean_ie