Revisiting Java in 2021 - II

Cover image by Charles Forerunner on Unsplash.

In Part I, I gave an overview of the major language features introduced between Java 11 and Java 17 - showing us what Java looks like in 2021.

I also argued that even if Java doesn't have feature parity with some of the more sophisticated JVM languages (and it definitely doesn't), Java is very deliberately moving forward and definitely in the right direction.

But that still leaves the question, where does Java fit in the modern JVM landscape? Are the newer, more feature-rich languages not the obvious choice for all development teams?

Below, I attempt to address these questions. I also discuss the JVM and give an overview of popular JVM technologies in a variety of contexts.

Goliath vs the Davids

I might make it sound like Kotlin or Scala are nipping at Java's heals, but assuredly, that is not the case. The 2021 Stack Overflow Survey results again showed the continued popularity of Java: for every 1 reported Kotlin developer, there are almost 4 Java developers. It's much worse for Scala, with the ratio being 11:1 in favour of Java (Clojure has it the hardest, but at least Clojure developers are paid well for their efforts).

Java similarly dominates the TIOBE index, sitting pretty at third place, an enviable spot compared to Scala's 32nd place and Kotlin's 37th.

What does this mean? Besides fuel for your favourite language flame-war, not much? There are, however, advantages to being popular.

Building large teams (think hundreds or thousands of developers) requires talent, and talent is easier to find if more people know the language (although, if you want the absolute best people, you might be better off choosing a more niche language).

Similarly, with so many people working in the language, the amount and quality of related resources are much higher. There are many excellent Java libraries, many well-written books on how to program it effectively and many high profile architects, thought leaders and evangelists that continue to refine and redefine how Java should be programmed.

Java also arguably has the best tooling in the business. I'd argue that IntelliJ IDEA (and family) is the best IDE out there, more so if you are coding Java. Even if you prefer VS Code, Java is well supported. Build tools such as Maven and Gradle are fast, stable and effective. There is also the JVM platform itself to consider, and more recently GraalVM, but more on that later.

Is Less More?

When it comes to programming languages, is less, more? Go(lang) certainly thinks so. Go famously rejects complexity to serve its design goal of keeping things simple and fast. The Go language designers deem it necessary to achieve scale in terms of program size and execution and to enable large teams of programmers.

So in terms of Kotlin and Scala, is Java perhaps a better choice precisely because it's a more straightforward language?

Scala specifically has so many features it's considered somewhat of an 'everything and the kitchen sink' language, including ad-hoc polymorphism, macros, ADTs, a purely functional programming model and much more.

Scala's additional complexity indeed enables powerful and elegant new solutions to programming problems. Ad-hoc polymorphism and type classes lead to beautifully flexible code. The benefits of functional programming have been widely evangelised and form the core of the reactive programming model. All of this is why Scala remains one of my favourite languages.

However, powerful new tools aren't free, and with the additional complexity, several ancillary issues are introduced downstream, such as slow compilation and tooling and the steep learning curve. These issues have nothing to do with the language features or how effectively and elegantly you can solve problems with Scala. Instead, they mar the developer experience, which, at least in my opinion, is perhaps the primary reason Scala has not achieved dominance on the JVM. Scala 3 overhauls the language, and I am curious to see how that pans out.

Like Scala, Kotlin also gives us new ways of solving problems that commonly occur in Java codebases or require third-party libraries to solve: compared to Java, Kotlin has better support for functional programming, null-safety, an expanded standard library, and has lightweight concurrency constructs similar to Go.

However, although many see Kotlin as nearly a drop-in replacement for Java (which, at a technical level, it can be), it too has its own learning curve, a fact that is oft poorly acknowledged in my experience.

This is especially true when we move beyond basic knowledge of Kotlin (how to program in Kotlin) and start considering idiomatic and effective use of Kotlin (how you should program in Kotlin). In the absence of a Kotlin expert on your team, or standard, widely accepted guidelines, effective use of a language takes experience and is achieved through trial and error. Even experts will need to make choices specific to their team and team size.

fun usingScoped() {
    val numVowels = getDTO()?.let { dto ->
        dto.string?.let {
            countVowels(it)
        }
    } ?: 0
}

fun usingIf() {
    val dto = getDTO()
    val numVowels = if (dto?.string != null) countVowels(dto.string) else 0
}

fun usingWhenAndScoped() {
    val numVowels = when (val dto = getDTO()) {
        null -> 0
        else -> dto.string?.let { countVowels(it) } ?: 0
    }
}
Which of these is the best approach to deal with the nullability of a returned value and its properties? It could depend on your team.

Certainly, it's here that Java holds some advantage over the more powerful languages. Java, of course, has a learning curve, but it is flatter than either Scala or Kotlin since it's a simpler language. There are also many resources on how to program it effectively. It might not be exciting, but Java is a known quantity.

The JVM Platform

Of course, Java is both a language and a platform through the JVM. James Ward recently gave an excellent overview of the modern Java platform, and I encourage you to check it out; I'll highlight some of it below.

As we saw above, three of the top 20 programming languages are JVM based. It doesn't really matter what your preferred style of programming is; the JVM has you covered.

The ecosystem is also rich with frameworks and libraries to develop just about anything. Frameworks such as Spring Boot, Micronaut and Quarkus are de facto standards for creating web applications, especially backends and microservices. More Rails or Django like full-stack and UI rich options are available in the Play Framework, Vaadin (with Boot) and Spring Boot itself.

Another area where the JVM has a significant presence is in Big Data and data streaming communities. Many of the dominant frameworks in this area are written in Java or Scala, including Apache Kafka, Hadoop, Spark, Beam, Flink, and NiFi. Of course, the implementation language might not be the language used to interface with the framework, but Java is always supported, and in many cases, the default API.

A lot of progress has also been made in the Deep Learning space on the JVM over the last couple of years. There are two major Java-based Deep Learning frameworks: DJL (Deep Java Library) from Amazon and DL4J (Deep Learning for Java), now with the Eclipse foundation. The libraries differ somewhat in their approach. DJL provides interfaces to 'Engines', which provide the lower level n-dimensional arrays and automatic differentiation functionality. Supported engines include MXNet, Pytorch and Tensorflow.
DL4J's approach is more fundamental with its own implementation of n-dimensional arrays (ND4J) and related functionality.

Reactive programming is one of the hallmarks of modern systems. Most major JVM frameworks support reactive coding, with full end-to-end reactive stacks now being possible, including the database layer via R2BC.
It's also worth mentioning Akka, an implementation of the Actor Model on the JVM, which is extremely mature and supports writing highly reactive, distributed systems.

Within the Cloud and Cloud-Native contexts, the JVM also excels. JVM based applications are well-supported by all major cloud providers. The JVM is also easy to host in a container, and containers are well supported by major frameworks such as Spring. Applications run well regardless of whether they are developed in Java, Kotlin or Scala.

However, when running a Cloud-Native or Serverless Java application, there is some concern regarding JVM overhead. Quarkus addresses this directly; however, another major piece of the puzzle here is the GraalVM.

GraalVM

For those unfamiliar with the GraalVM project, it's a significant and impressive piece of engineering with several cutting edge features (such as polyglot programming). Pertinently, it also includes GraalVM Native Image, which enables the ahead-of-time compilation of Java applications to native binaries.

Having native binaries is, of course, game-changing in the Serverless and Cloud-Native contexts, with start-up time seeing reported improvements of 50x, and memory footprints decreasing by as much as 5x. I've used Quarkus before, and though I didn't run benchmarks, I can report startup time was on the order of microseconds.

This does come with some restrictions and caveats; for example, it's not as straightforward to use Reflection in your Java code.

It may also not yet be that straightforward to get your application working on the GraalVM alongside your favourite framework. But progress is being made rapidly.

The Future of Java

There are several high profile Java projects in the pipeline aiming to further modernize the language. Project Loom aims to introduce new lightweight concurrency constructs à la coroutines.

Valhalla is introducing new inline types by updating the memory model, allowing better utilization of modern hardware architectures.

Finally, Project Leyden aims to address startup time and time to peak performance for Java.

Besides Java, the issues mentioned above regarding Kotlin will be addressed over time, and the language itself continues to evolve. I also mentioned Scala 3, which has a lot of potential.

Conclusion

Whether Java is the right choice for you, I believe, depends on context.

Considered, predictable changes are a significant advantage, if not an outright necessity, in specific contexts. Huge teams (thousands of developers) running projects for decades, or projects that have upgrade cycles measured in years, or codebases that work in an environment that necessitates safety and predictability (e.g. finance or medicine) thrive on this kind of roadmap.

Further, as detailed above, Java, as a platform, is certainly not lacking in capability. Regardless of your chosen JVM language, you're unlikely to find yourself in a corner where the isn't a framework, library, or tool to support and accelerate your development.

Am I advocating for Java? Maybe a little. Would I start a new project in Java? Probably not; I'd prefer Kotlin. However, does it make sense if other teams choose Java instead in 2021? Absolutely.

Follow me on Twitter.
Or email me at: interesting at avanwyk.com

Andrich van Wyk

Andrich van Wyk

I'm Andrich van Wyk, a software architect and ML specialist. This is my personal blog; I write here about data science, machine and deep learning and software engineering. All opinions are my own.