RxJava sounds like a nice formalization of how I have been &quot;doing it&quot; with Guava&#x27;s ListenableFuture and single threaded executors. I always declare up front how tasks are distributed across threads and don&#x27;t rely on random scheduling from a queue. This also creates opportunities to avoid locking and migrating data between threads.<p>It&#x27;s about as painless as it gets when you aren&#x27;t bottlenecked on number of actor like things. I never am because I eat the pain of not blocking rather than asking a framework to do it for me (or fail at it).<p>Java 8 has CompletableFuture so you don&#x27;t need to leave the JDK anymore, but the javadoc makes my eyes bleed compared to Guava&#x27;s ListenableFuture. I do like that CompletableFuture doesn&#x27;t use synchronized internally.<p>The thing you have to aware of with async is backpressure. You have now decoupled thread pool exhaustion from task creation allowing unbounded memory usage. And once you allow backpressure you need to start thinking a little bit harder about deadlocks due to bounded queues.<p>Eliminating cycles, ignoring bounds where cycles exist, or allowing errors&#x2F;shedding can help, but it&#x27;s the kind of thing that keeps me up a night.