For over two decades, the JVM (and thus Java) has ruled supreme while other runtimes have come and gone. Ruby, Python, .Net, NodeJS, have all tried to take the throne. But the JVM offered something none could: a multi-threaded, JIT-compiled, high-performance, fully backward compatible runtime.

That the JVM hasn't kept up with the times, can be demonstrated with a simple example. Say you write a small command-line utility function in Java that has dependencies on a few jars. How does one package and distribute this utility? One could look at StackOverflow to see which plugin of your favorite build system is currently popular to create a Fat Jar. However, the official 'Java way' to package your code and its dependencies is still the good old .war file. A faster moving organization would have acknowledged how users like to ship their code. They would have extended the .jar file format (or introduced a new file format) to be able to package dependencies in a standardized manner. But no such thing on the horizon with Java.

Java is moving faster than ever now, one might say. It does get 2 minor releases a year now. (Though, does anyone really use the features of non-LTS Java versions?). Sure the module system in Java 9 allowed the JVM to shed some of its weight, but major additions to the JVM which would put it on par with competition still seem to be a long way away. Project Valhalla, which was supposed to introduce 'Value Types' to Java, was announced back in 2014 and still has no release date. Project Loom, which brings green threads, was announced in 2018 and is in a similar state. Want to interop with native code, one still has to deal with the insanity of 20-year-old JNI interfaces.

One also wonders, even after these features are introduced, how compelling would they be to use compared to their implementations in other languages/runtimes. Java's Achilles heel after all is the need to maintain backward compatibility. Its that backward compatibility requirement that got us the Java Streams API, where instead of myList.map(...) one has to write the monstrosity that is myList.stream().map(...).collect(Collectors.toList()).

There was a time where Java was the best choice for almost everything. GWT on the frontend, Spring on the backend. Databases like Hadoop, ElasticSearch, Kafka, Spark, all written on top of the JVM. Java also had the absolute best IDE in Eclipse and Intellij, making it a reason to choose Java over other languages in a professional environment.

What's different in the 2020s is that over the last decade, languages/runtimes have evolved to become best in class for specific domains. They took their learnings from the JVM, finally realizing that type safety is needed and performance is important. They ackonowledged that one language/runtime doesnt fit all needs.

  • Want to write a large, complicated UI? ReactJS and Typescript allow you to do that while providing a type system even more expressive than the JVM.
  • Want to write a web server? Golang allows you to handle thousands of requests at a time without the need for a thread per request. It makes concurrency so easy, the equivalent in Java would be an order of magnitude more complex.
  • Want to write a big data database? Rust has you covered. Deal with huge amounts of data without needing to battle the GC and interact with low-level system calls without battling with JNI or sun.misc.Unsafe.

With all these languages being fully typed, Jetbrains now has been able to make equally good IDE for those languages too.

From 2020 onwards, the JVM doesn't seem to be the best choice for anything. It will continue its legacy as a big, heavyweight, one size fits all VM. Good at everything, but never the best. The future is polyglot and it's free of the JVM.