Java 19 is now released, and it’s a big deal, as it ships with Virtual Threads (JEP 425), among other goodies, like structured concurrency (JEP 428), or improvements to pattern matching (JEPs 405, 427). I haven’t been as excited about a Java release in a long time.
I once claimed that this function signature needs special platform support, on top of the JVM this needs to block threads, and on top of some other platforms this gets implemented with continuations/coroutines. Not all platforms can provide support for it, and blocking (platform) threads is very expensive, therefore it should be avoided:
Future[A] => A // aka `Await.result`
The Java ecosystem has been increasingly moving to reactive APIs, with projects like Project Reactor, RxJava, or Vert.x becoming really popular. The introduction of Virtual Threads, however, has the potential to move the ecosystem back to usage of blocking APIs for I/O or structured concurrency.
Blocking threads plays nice with Java’s features in a way that reactive APIs can’t. For instance: for loops, try/catch, try-with-resources, or checked exceptions. Java was built to synchronize on concurrent tasks by blocking threads, and the incursion into reactive APIs was only temporary. Other languages, such as Kotlin, implemented coroutines for mostly the same effect. But note that Virtual Threads improves everything, including all blocking APIs from the
java.io package, and it does so for all languages running on top of the JVM, whereas Kotlin’s coroutines can’t work around the platform’s limitations (blocking I/O is still blocking I/O). This is what having control over the entire stack gives you.
“With only a tiny code change to use JDK19 “virtual threads” JRuby can now spin up over 100k fibers at once! Launching and completing them takes about 0.7s after warmup. This will make async IO servers and thousands of concurrent users possible on JRuby. 🤯”
Mind you, I don’t think this invalidates the available projects built with reactive APIs. I think Virtual Threads will make everything better, as these projects will simply adapt to provide the best API depending on use-case. We use reactive APIs due to the strong guarantees they provide, and due to their declarative approach, and that won’t change. But users will no longer be forced to use reactive APIs if all they want is efficiency.
Virtual Threads also has the potential to change APIs for Scala, too. If blocking threads is now cheap, this should no longer be a big problem in terms of efficiency:
IO[A] => A // aka `unsafeRunSync`
And neither is something like this:
Stream[IO, A] => ??? => A // Iterator#next() ftw 😛
This is interesting, as we can now do some stuff afforded with Kotlin’s coroutines, see Arrow-fx. Meaning that, instead of using
flatMap to compose
IO values, we could use a more imperative approach, and have Scala help with the safety. Many people will object to replacing
flatMap, as it’s about composition, but other Scala devs could prefer such an API. Even without any API changes, calling Java APIs will be less like walking on a landmine field. The big problem that I’m seeing for evolving Scala APIs is that this is a runtime feature, not a language one, meaning that the portability to Scala.js and Scala Native will suffer.
Did you know that Cats-Effect’s
IO now supports Scala Native? 😱
And do you remember that practice of having 2 thread-pools in your project, one for CPU-bound tasks and another for blocking I/O tasks? Oracle just ended it in Java 19. You no longer need to shift between thread-pools in order to avoid thread starvation, while efficiently using your CPU cores.
Interesting times ahead — and I’d like to predict the future, but truth is, I have no idea how our Scala effect systems will change to take advantage of it. In the meantime:
sdk install java 19-oracle
Here’s a good video explaining why Java got virtual threads, and how it compares with alternatives: