Difference between a Future and a Mono

The greatest difference is that a Mono<T> can be fully lazy, whereas when you get hold of a Future<T>, the underlying processing has already started.

With a typical cold Mono, nothing happens until you subscribe() to it, which makes it possible to pass the Mono around in the application and enrich it with operators along the way, before even starting the processing.

It is also far easier to keep things asynchronous using a Mono compared to a Future (where the API tends to drive you to call the blocking get()).

Finally, compared to both Future and CompletableFuture, the composition aspect is improved in Mono with the extensive vocabulary of operators it offers.


Producer and consumer can communicate in 2 ways: synchronous and asynchronous.

In synchronous (pull-based) way, consumer is a thread and some intermediate communicator object is used. Usually it is a blocking queue. In special case, when only single value is passed during the whole producer-consumer communication, a communicator which implements interface Future can be used. This way is called synchronous, because the consumer calls communicating method like Future.get() and that methods waits until the value is available, and then returns that value as a result. That is, requesting the value, and receiving it, are programmed in the same statement, though these actions can be separated in time.

The drawback of synchronous communication is that when the consumer waits for the requested value, it wastes considerable amount of memory for it's thread stack. As a result, we can have only limited number of actions which wait for data. For example, it could be internet connections serving multiple clients. To increase that number, we can represent consumer not as a thread, but as some relatively small object, with methods called by the producer or communicator when datum for consumer is available. This way is called asynchronous. It is split in 2 actions: request to producer to pass data and passing that data to consumer. This is asynchronous (push-based) method.

Now the reply to the question is: Future is able to act as a synchronous communicator only (with get methods), and Mono can be used both as synchronous communicator (with block methods) and as an asynchronous one (with subscribe methods).

Note that java.util.concurrent.CompletableFuture can also act both as synchronous and asynchronous communicator. Why to have similar means to do the same thing? This phenomenon is called not invented here.