Chapter 15: What's New in Java?

Java, as is obvious, has been the leitmotif of this book. Even if, in some of the previous chapters, we focused on more general concepts such as architectural design and software life cycle management, the main goal of this book is to provide Java software engineers with a compendium of architectural concepts, ultimately supporting them to become better architects.

With this in mind, we cannot avoid a few words regarding the status of Java technology today, especially regarding the latest releases.

In this chapter, we are going to discuss the following topics:

  • Java versioning
  • Vendor ecosystem
  • What's new in Java 17

So, let's start with an overview of Java versioning.

Java versioning

There have been many changes made to the Java versioning scheme and schedule over its history. One first thing to note is that, at the very beginning, Java versioning used to follow a 1.x scheme, with 1.3 essentially being the first widespread version.

Since version 1.5, however, the versioning scheme ditched the 1.x prefix, so we had Java 5, 6, and so on.

Another important point to make is about naming. The very first versions were called JDKs (short for Java Development Kit – more about this in a bit). Then, from versions 1.2 to 5, the platform was named J2SE (for Java 2 Standard Edition). Since Java 6, at the time of writing, the platform is referred to as Java SE (for Java Standard Edition).

The most important thing to know about the JDK, a term that most of us are familiar with, is that until Java 8, the Java platform was distributed in two versions, the Java Runtime Environment (JRE) and the JDK. The JRE was basically a stripped-down version of the JDK, lacking all the development tools (such as the javac compiler). As said, since Java 8, only the JDK version is officially distributed.

In terms of release timelines, older Java releases used to have a long and non-uniform scheme, with major versions being released in intervals varying from between 1 and 3 years. Since Java 9, though, the platform's evolution has followed a 6-month release timeline for major versions.

One more point relates to Long-Term Support (LTS) releases. Roughly every 2 or 3 years, a version is considered LTS. This basically means a longer official support cycle (up to 10 years, depending on the vendor) with more features added (while non-LTS releases usually have fewer and simpler new features).

Last but not least, each major version (both LTS and non-LTS ones) also brings with it a set of minor versions, shipping patches, bug fixes, and security fixes.

In the following diagram, you can see the graphical representation of the support life cycle for some of the most important Java releases:

Figure 15.1 – Version support life cycle for some Java releases

Figure 15.1 – Version support life cycle for some Java releases

Other than the version numbering, a further important consideration concerns the vendor ecosystem.

Vendor ecosystem

As many of you know, Java was released as a project by (the now defunct) Sun Microsystems. It was originally developed as a language for clients and what would later be called the Internet of Things (IoT). Ironically, nowadays, it's rarely used in such scenarios and, conversely, very much used for server-side enterprise applications, which was likely not the first use case in mind when Java was designed.

In 2006, Sun released Java technology as open source under the GPL license. Sun later went out of business and was acquired by Oracle in 2010. With that transition, the Java ecosystem started to be governed mostly by Oracle itself.

Java releases are certified using the Technology Compatibility Kit (TCK), which is a test suite used for testing the compatibility of Java distribution with the specifications included in a specific version. And talking of Java distributions, the most important project here is OpenJDK.

OpenJDK distributions

OpenJDK is the main source code repository from which many widespread JDK implementations have been derived, including the Oracle Java distribution.

We know that Oracle leads the open source development of Java within the OpenJDK community. OpenJDK is essentially the reference implementation of Java technology. Oracle ships the Oracle OpenJDK (which is free and not supported commercially) and the Oracle JDK (which is commercially supported under a paid subscription).

Many other vendors provide their own distributions, with small differences between them. All such distributions are created starting from the OpenJDK open source code base:

  • AdoptOpenJDK is a multivendor project for distributing vanilla OpenJDK builds (https://adoptopenjdk.net).
  • Red Hat provides its own build featuring support for the Red Hat Enterprise Linux operating system and some add-ons, such as support for the Shenandoah garbage collection implementation (https://developers.redhat.com/products/openjdk).
  • Azul Technology builds a commercially supported implementation, including some proprietary garbage collection features (https://www.azul.com/downloads).
  • AWS ships Corretto, an OpenJDK build designed to run on the AWS Cloud infrastructure (https://aws.amazon.com/it/corretto).
  • IBM ships OpenJ9, originally developed for running on mainframe technology and now available, under the OpenJDK umbrella, for other architectures (https://www.eclipse.org/openj9).
  • GraalVM is an interesting concept built on top of OpenJDK (we have already seen some of its features in Chapter 7, Exploring Middleware and Frameworks, when discussing the native compilation of Quarkus). GraalVM comes from the experience of Oracle Labs and brings a lot of different and interesting things to the Java technology, including a module for native compilation (as we mentioned before), and modules for polyglot usage, in order to run code written in Python, JavaScript, Ruby, and more.

These are the most commonly used Java distributions. The choice, unless you are looking for a very specific feature, is mostly dependent on circumstances, such as existing support contracts or commercial pricing. In the absence of specific needs, AdoptOpenJDK is usually a good place to start.

A recent ecosystem report built by Snyk (https://snyk.io/jvm-ecosystem-report-2021), shows that the builds of AdoptOpenJDK are the most popular by far (around 44%), followed by the different flavors (commercial and otherwise) of the Oracle distribution. Another important piece of news from the report is the growing adoption of Java 11 and the move away from Java 8. However, we will see how the adoption of Java 17 will grow in the upcoming months and years.

In this regard, let's see what's new in the latest version of Java, Java 17.

What's new in Java 17

Java 17 is an LTS release, meaning that, depending on the vendor, it will be supported for more than 5 years (up to 10, in some cases). It was released in September 2021.

Let's look at some of the new features introduced with this version.

Sealed classes

Sealed classes were introduced with Java 15, and the feature became officially supported with Java 17. They provide a way to declaratively define classes and interfaces while restricting which objects can extend it or implement such classes and interfaces.

This can be particularly useful in specific cases, such as if you are defining an API, as you can, at design time, control some aspects of the usage of APIs.

Here is a simple example:

public sealed class Payment permits Instant, Wire,

  CreditCard […]

In this example, we declare a Payment class, and we define that only Instant, Wire, and CreditCard can extend it. In this particular example, we suppose these classes are in the same package as Payment, but it is possible to explicitly declare the full package if we wanted to place it somewhere else.

Also, the exact same syntax can be applied to interfaces:

public sealed interface Payment permits Instant, Wire,

  CreditCard […]

This is the same behavior, just for interfaces, so the implementation is allowed only for the interfaces listed.

It's worth noticing that a compile-time error is raised if non-allowed operations (such as extending a class with a non-declared type) are performed. This will help the code to be more stable and testable.

Pattern matching for switch statements

This is a preview feature, meaning that it must be enabled (by passing a command-line parameter to the JVM) and is not officially completely supported (even if the exact boundaries of support are defined by each vendor).

This feature is about extending the behavior of the switch construct.

While there are many different potential use cases (and more will likely be refined and finalized in the upcoming releases), these three are the main ones:

  • Type checking: The switch construct can behave like an instanceof operator, checking by type as in the following example:

    […]

    switch (o) {

        case Instant i -> System.out.println("It is an

          instant payment");

        case Wire w    -> System.out.println("It is a wire

          transfer");

        case CreditCard c -> System.out.println("It is a

          credit card transaction");

          default -> System.out.println("It is another

            kind of payment");

            };

    […]

  • Null safety: While, in the previous implementations, the switch expressions raised a NullPointerException if the object evaluated is null, with this new null safety feature, it is possible to explicitly check for the null case. In this example, the switch expression checks over a string variable, also checking the null case:

    switch (s) {

      case "USD", "EUR" -> System.out.println("Supported

        currencies");

      case null    -> System.out.println("The String

        is null");

            default    -> System.out.println("Unsupported

              currencies");

        }

  • Refining patterns: It is possible to use a syntax for expressing more than one condition in a switch branch. So, essentially, the following construct is allowed:

    switch (o) {

        case Instant i && i.getAmount() > 100->

          System.out.println("It is an high value instant

            payment");

        case Instant i -> System.out.println("It is a

          generic instant payment");

        case Wire w    -> System.out.println("It is a wire

          transfer");

As you can see, this is a nice feature allowing for compact and readable code.

Strongly encapsulating JDK internals

Since Java 9, there's been a progressive effort to restrict access to the JDK internals. This is meant to discourage the direct utilization of classes residing in packages such as sun.*, com.sun.*, jdk.*, and more. The goal of this restriction is to reduce coupling to a specific JVM version (hence freeing the JVM developers up to evolve such classes, even introducing breaking changes if necessary) and enhance security.

To do so, the JDK progressively offered alternatives. Moreover, since Java 9 (and up to Java 16), source code using those internal classes and methods must be compiled by passing the --illegal-access parameter, which can be configured to permit, deny, or print warnings with details of usage.

In Java 17, this parameter is no longer usable. Instead, it is possible to use the --add-open parameter, which allows us to declare specific packages that can be used. It is a common opinion that even this possibility will progressively be denied in upcoming versions, to completely deny the explicit usage of JDK internals in custom code.

More changes in Java 17

A lot of other changes have been added to Java 17. Here are some highlights:

  • Support for the macOS/AArch64: This allows the compilation and execution of Java code on Mac machines running on M1 chips.
  • Enhanced pseudo-random number generators: This is a partial refactoring of utilities for pseudo-random number generation, including the deletion of duplicated code and the pluggability of different algorithms.
  • Foreign function and memory API: This is an incubating set of features (which are still not stable and will be subject to further evolution) aimed at simplifying and securing access to resources (code and data) living outside the JVM. This means being able to access memory locations and call methods not managed or implemented in the JVM. To do so in previous versions, you were required to use Java Native Interfaces (JNI) classes, which are generally considered less secure (and more complex to use).
  • Context-specific deserialization filters: As a part of an effort started some JVM versions ago, this is a way to define validation for code deserialization. Serialization and deserialization of classes are generally considered potential security issues, as specifically crafted payloads can execute arbitrary (and unsafe) operations. This feature allows the definition of filters to prevalidate the kind of code allowed in deserialization operations.
  • Deprecation of the applet API for removal: Applets haven't been used for a long time, for many reasons, including performance and security issues. Moreover, most (if not all) of the modern browsers don't support them anymore. So, they are being deprecated and will be completely removed from the JDK.
  • Deprecation of the security manager for removal: The security manager is an API primarily intended for usage along with applets. It was released in Java 1.0. It has been progressively abandoned, both due to complexity and performance issues and because applets are now less commonly used. So, it is now deprecated and will be removed in an upcoming version of the JDK.
  • Vector API: This is a new API in the incubation phase (meaning it will be subject to changes and further evolution). It aims to define a new API for the computation of vectors. Other than being simple to use, this API is designed to compile code, specifically targeting available optimizations for supported CPU architectures, thereby boosting performance where possible.

While a number of other features have been added, modified, and removed, the preceding ones are the most important and impactful.

Summary

In this chapter, we have looked at some of the novelties introduced with the latest release of the Java platform (17).

We have had the opportunity to have a look at the Java versioning scheme and release schedule. We had a quick overview of the Java vendor ecosystem, a snapshot of what is an evolving situation at the time of writing. The same applies to the newest functionalities of the platform itself. While some features are notable by themselves, of course, many will be modified further in the near future.

This completes our journey into cloud-native architectures with Java. I hope I have provided some interesting insights and ideas, and I wish the best of luck to every reader in defining elegant and successful applications and having satisfying careers as software architects.

Further reading

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.237.77