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:
So, let's start with an overview of 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:
Other than the version numbering, a further important consideration concerns the 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 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:
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.
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 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.
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:
[…]
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");
};
[…]
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");
}
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.
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.
A lot of other changes have been added to Java 17. Here are some highlights:
While a number of other features have been added, modified, and removed, the preceding ones are the most important and impactful.
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.
3.144.237.77