Is 21 the new Java 8?

#java Oct 3, 2023 5 min Mike Kowalski

Not all Java releases receive the same attention from the community. The ones marked as “LTS” have a much higher chance of broad adoption (even if many of us don’t fully understand what the “LTS” means). This alone makes the recently released Java 21 a serious contender.

I believe it’s the most influential Java release since Java 8. Just not all of its groundbreaking aspects are already evident to everybody…

Let me explain what makes this release so promising.

Back to 2014

Not every “LTS” is a breakthrough. Without a doubt, both Java 11 and Java 17 were important milestones for the whole ecosystem. Yet, none of them has ever reached the same status as Java 8. It doesn’t mean they were boring, it’s just a matter of reference.

The eighth major Java version was a true revolution. Lambdas and Streams not only promoted functional style but also made many existing code snippets permanently obsolete. Java 8 was everywhere: from conference talks, to blog posts and job offers. The whole community eventually fell in love with new features and nobody wanted to lag behind. In fact, this might have been the first case of a widespread FOMO caused by the Java version…

Over 9 years later, Java 21 has everything to become the new Java 8. Of course, in a positive sense! Just like its famous predecessor, it introduces new patterns and paradigms. Similarly to Java 8, it will make some code snippets obsolete. Finally, like every Java release, it’s packed with various improvements.

However, to become similarly iconic it also needs proper attention. Luckily, virtual threads are already in its changelog.

Chasing simplicity

Project Loom and its virtual threads probably appeared in every single Java conference in recent months. Among GraalVM native images, they seem to be the hottest topic in the JVM space. And there’s a good reason for that. Virtual threads are one of the most promising additions to the JVM in years.

The whole Project Loom is all about simplicity. Those who feel victimized by the complexity the reactive programming brings (πŸ‘‹), are looking forward to “reduce the effort of writing, maintaining, and observing high-throughput concurrent applications” (JEP 444). Virtual threads are just the beginning - features like structured concurrency need a bit more time to leave the preview zone.

Loom is for concurrent programming what lambdas have been for functional programming.

Game of Loom: implementation patterns and performance implications playing with virtual threads, Mario Fusco, Devoxx Belgium 2022

Major Java frameworks like Spring, Quarkus, and Micronaut can already benefit from virtual threads to some extent. Yet, the question is: how we, as application developers, can? There’s an amazing article by Clement Escoffier explaining what virtual threads bring to Quarkus, and how they differ from reactive programming.

Virtual threads reuse the idea of the reactive paradigm but allow an imperative development model.

When Quarkus meets Virtual Threads, Clement Escoffier

Virtual threads are not a drop-in replacement for reactive programming. Yet, in certain cases, they might allow us to avoid its complexity. They offer a simple and familiar API with amazing scalability potential. It will probably take some time to find the right balance between these two worlds.

Should we start migrating everything to virtual threads in the meantime? Not so fast…

@mikemybytes
September 4, 2023
Alan Bateman at #JVMLS: "Some developers think that they can just change the way threads are created and it's equivalent of putting 'go faster' stripes on their car" πŸ˜… It's not that simple! Great talk full of nerdy details πŸ‘‡https://t.co/3ioc8Krp1X
September 4, 2023

As explained in the article, there are still some rough edges. Not all the popular Java libraries are already virtual threads-friendly. Therefore, unlike Spring, Quarkus doesn’t come with a global virtual threads toggle. Fortunately, the situation is improving quickly. Don’t forget to keep your dependencies up-to-date!

Thanks to virtual threads coming out of preview, Java 21 might become the next long-term baseline for frameworks and libraries. Java 8 played such a role for many years due to being the oldest version supporting new, modern syntax. I expect the same may happen to Java 21 thanks to virtual threads support.

It’s a match!

Records and sealed types were already available before Java 21. Yet, this release finally unlocks their true potential. Pattern matching, familiar to those coming from Scala and Kotlin, introduces new powerful ways of expressing ideas in our code.

The following snippet no longer requires --enable-preview switch:

sealed interface ProcessingResult 
        permits ProcessingSuccessful, ProcessingFailed, ProcessingUnavailable {}

record ProcessingSuccessful(String result) implements ProcessingResult {}
record ProcessingFailed(boolean retryable, String details) implements ProcessingResult {}
record ProcessingUnavailable() implements ProcessingResult {}

class ImportantService {

    ProcessingResult process(byte[] data) {
        // some processing logic here
    }

}

class ImportantController {
    // ...

    // imagine this is an HTTP endpoint
    HttpResponse process(byte[] data) {
        ProcessingResult processingResult = service.process(data);
        
        return switch(processingResult) {
            case ProcessingSuccessful(String result) -> new HttpResponse(200, result);
            case ProcessingFailed(boolean retryable, String details) -> {
                if (retryable) {
                    // retry, set Retry-After header, etc.
                } else {
                    log.warn("Processing failed terminally: {}", details);
                    yield new HttpResponse(400); // return from the switch expression
                }
            }
            case ProcessingUnavailable unavailable -> new HttpResponse(503);
        };
    }

}

Instead of throwing exceptions, the processing logic returns a sealed type allowing to differentiate between scenarios. Thanks to switch expression exhaustiveness, adding an additional ProcessingResult implementation would cause a compile-time error forcing us to update our code. With record patterns, we can quickly reference their specific fields.

Can such a coding style send exceptions to retirement? Thinking so might be a bit overoptimistic… However, it may effectively replace them in certain cases. In my opinion, a lot depends on frameworks and libraries maintainers. The more they promote such an “exceptionless” style, the bigger the chances for wider adoption are. That’s another long-term opportunity for Java 21.

Summary

Java 8 has changed the JVM world almost instantly. For Java 21, we may need a bit more time to fully appreciate its impact. New patterns and use-cases require community adoption before turning into a standards. Yet, this latest and greatest release has everything to start another revolution in the ecosystem.

Bring it on, Java!

Mike Kowalski

Software engineer believing in craftsmanship and the power of fresh espresso. Writing in & about Java, distributed systems, and beyond. Mikes his own opinions and bytes.