post-thumb

A look in the rearview mirror, from JDK 1.3 to JDK 22

For reasons that I will have to detail in a future post, I am diving back partly into the code, and into Java. The last time I coded “for real”, it was to make Java applications on feature phones (the stuff between Nokia 3210 and smartphones), in J2ME , and Windows Mobile applications on Windows Phone. Before the era of smartphones therefore. Before that, I had developed mainly in Java for web services, via servlets, an in-house templating engine, then via Java Server Pages .

So, since then, on the Java side, many, many, many things have changed.

Before getting my hands dirty with Java code in sandbox mode, I wanted to do a purely bibliographical retrospective. In the meantime, the advent of code calendar started, and with a few colleagues we started doing it. This allowed me to actually recode in Java, without having to manage all the development sidelines, focusing on the language and algorithms. A real pleasure, and the opportunity to actually use all the developments in Java since Java 1.4.

This retrospective does not cover the whole “naming” part of Java. The precise definition and respective scope of “Java”, “JDK”, “J2SE”, “J2EE”, “JEE”, “Jakarta” deserve a dedicated article. It addresses the run/ops part very little, while many interesting things have appeared since the beginning (new garbage collectors, String compression, monitoring of an application in the JVM, faster loading of bytecode , sharing of bytecode between different JVM etc). Finally, this Java retrospective is necessarily influenced by my experience and my professional interests. And it is not exhaustive: I am not a software historian

Where am I leaving from?

I learned Java in 2000. Mainly by reading, by thinking. My bedside book was Java in a nutshell, from O’Reilly, the one with the tiger, which should cover, from memory, Java 1.3. The cover of the book “Java in a nutshell”

Then, on the job, using it in real life. To make applets as part of a student job. Then, it was the first language I used during my internship and my life as a web developer, in JDK1.3. In addition to Perl and shell-script for all the extras. My workplace offices in 2000

No framework at the time, apart from in-house frameworks. The page was generated entirely on the server side, as browsers knew how to do few things. The heyday of the Atos Servlet Web, and of an in-house bookstore. In-house library, com.utilities, which has been enriched over the years to fill in the gaps in the Java API at the time.

Now, at the end of 2023, we are in Java 21. Just in terms of the language, not to mention the countless frameworks around it, what has happened since then?

Preamble: By what means or artifacts does Java evolve?

Concerning the release of new unstable features, I am rather used to the release of several variants of the same product. In the free world for example, there are “stable”, “unstable” builds or, worse, “nightly builds”. For instance, for the free and open source Firefox browser, it is possible to install several variants of Firefox , it is possible to install several variants of Firefox:

  • the nightly version: it is an unstable preview, which can crash while browsing the web
  • the beta version: this is a stable preview, with new features not yet fully tested
  • the release (or nominal) version: deemed stable - this is the one that everyone downloads.

Regarding Java, it doesn’t work like this: Java is produced in only one variant. It’s an “all in one”. Except that you still have to give the new features time to “dry up the plaster” before considering them ready for production. And we can imagine that, in terms of risks, there is a difference between a new API and a profound evolution of the language. However, when you want to download Java, there is no choice between a beta version of Java or not. How do they do ?

On the Java side, an evolution can be of three different types: preview feature, experimental feature or incubator module. I explain below what this corresponds to and how it allows Java to offer new functionalities in an “all in one”, while minimizing the risks of instability in production.

Preview feature

A previewed feature may be new at the language, JVM or API level. It is completely specified and implemented. However, in order to avoid a design error (I remember the inconsistency of the APIs in the java.util package…), this new functionality is made available to developers for their feedback. There is therefore a risk in using this type of functionality. The implementation must already be of high quality and stable; it must be available on all platforms where Java already works.

Theoretically, the feature could ultimately be completely abandoned. In practice, this never seems to have happened. A feature in preview in a Java release can continue to be in the following ones, in 2nd preview, 3rd preview etc.

Historically, there has been at least one big feedback taken into account. In Java 12, the ability to use switch in an expression evaluation (JEP 325 ) initially used the break keyword to return a value. In Java 13, this break keyword has been replaced by yield (JEP 354 ) to avoid confusion.

In the JEP title, the preview features are suffixed with (Preview), (Second Preview), (Third Preview) etc. Concretely, the activation of all the features in preview mode is done at compilation and execution by adding –-enable-preview to the command line. However, correct integration into IDEs is the responsibility of each IDE, and this can take some time.

Experimental feature

A preview feature being already very stable, how do we share something unstable, the equivalent of the unstable branches of open-source and/or free projects? We use the status of experimental feature. Unlike a preview feature, the implementation is allowed to be unstable, and the integration of the feature by JVM providers is optional.

These very often, or almost always, involve features at the JVM level, for example the introduction of new garbage collectors.

In the title of the JEP, the experimental features are suffixed with (Experimental). Concretely, to use them, you must add a -XX parameter to the command line, for example -XX:+EnableValhalla. Activation is carried out individually for each experimental feature, unlike preview features where all preview features are activated/deactivated in one block.

Incubator module

An integrated functionality in the form of incubation modules may concern a modification of API and/or tools, not yet mature enough to be preview features. In the Java sense of the term (introduced with Java 9), it is a module. The name of this module, package(s), and/or command-line tool name are prefixed with jdk.incubator.. Unlike a preview feature, the naming is not definitive.

Unlike an experimental feature, an incubator module is part of the JDK, and all JDK vendors must provide them.

Two examples, one past: JEP-110: HTTP/2 Client (Incubator) , an incubator module appeared with Java 9 and fully integrated into the JDK in Java 10; and the other, still in progress at the time of Java 21: JEP 460: Vector API (Seventh Incubator) , in incubation since JEP 338 of Java 16, which consists of using hardware support when calculating Vector (in the mathematical sense of the term - nothing to do with java.util.Vector).

In the JEP title, the incubator modules are suffixed with (Incubator). Concretely, to use them, you must add to the command line a --add-modules jdk.incubator.vector (example of [JEP 460: Vector API (Seventh Incubator)](https://openjdk.org /jeps/460)). Activation is carried out individually for each module, unlike preview features where all preview features are activated/deactivated in one block.

The prehistoric era: the evolution of Java during the Sun era

After this little introduction on the difference between preview feature, experimental feature and incubator module, let’s get to the heart of the matter.

2002: Java 1.4

An important new feature for code quality: the arrival of assertions and therefore of the assert instruction. Another important new feature: We can chain exceptions. It’s hard to believe, but before, it wasn’t possible, and it was complicated to trace back to the root exception. A new API for reading and writing to disk, ten times faster than the previous one: Non blocking I/O .

tl;dr;
For the rest, things that had no impact on my daily life at the time (Java Web Start and [Java Print Service](https ://docs.oracle.com/javase/8/docs/technotes/guides/jps/index.html), unused on the server side), including quite a bit of integration into Java of APIs already existing elsewhere:

  • Java integrates an XML parser and an XSLT engine jsr/detail?id=63). Old-fashioned parsing, with callbacks at the start and end of each XML element encountered. From memory, already existing elsewhere.
  • Perl regular expressions are integrated into Java. But before, we could use the implementation of the Apache Foundation (package org.apache.regexp.RE) anyway.
  • The Preferences API (java.util.prefs): I imagine that all the big projects of the time already had utility libraries to manage this, and undoubtedly in a more sophisticated way.
  • A log API . Lo4j already existed within the Apache Foundation .

Improvements that were not useful to me at the time, and which are now managed directly by Java frameworks, such as the java built-in authentication service . Improvements not immediately useful to the web developer of the time (IP v6 support, better random number generator, JNDI to speak with an LDAP server - yes, at the time, this was not useful to me).

2004: Java 5 - generics and annotations

In terms of form, we are moving from Java 1.4 to Java 5. Why such a change? Why not Java 1.5? Pure marketing, to mark the occasion in view of the big changes (and it’s true) in Java.

Many very practical improvements on a daily basis:

  • autoboxing/unboxing (no need to explicitly convert for example int to Integer and vice versa).
  • the generic types (make a List<Integer>=new ArrayList<>() for example) which avoid having to permanently cast its code. Code that is ultimately more readable and less prone to errors at runtime, with stricter typing verification at compilation. I’ll come back to it right after.
  • the keyword enum , which provides support for cleaner enumerations than a series of final static int constants ` and much less cumbersome to use than creating entire classes dedicated to this.
  • a for loop that is easier to write and read when you just want to iterate over a collection. Before Java 5, you had to tediously write:
       for (Iterator i = c.iterator(); i.hasNext(); ) {
            String s = (String) i.next();
            ...
        }

downright heavier than a:

       for (String s : c) {
            ...
        }
  • variable-length method arguments (I remember that this allowed to reduce unnecessary code where we used to struggle with declaring methods with an increasing number of arguments).
  • method arguments in variable numbers (I remember that this made it possible to reduce unnecessary code where we were bored declaring methods with more and more arguments).
  • the introduction of the annotation mechanism . I’ll also come back to it right after.

A little focus on generic types

A long-discussed addition: JSR 14 dates from 2000, and the [topic is already addressed in 1998](https://www. research.ed.ac.uk/en/publications/making-the-future-safe-for-the-past-adding-genericity-to-the-java) by influential figures such as [Gilad Bracha](https:// en.wikipedia.org/wiki/Gilad_Bracha), David Stoutamire , Philip Wadler and Martin Odersky .

Before, for example, as soon as we had a collection, we had to spend our time casting the elements when we collected them. With a huge risk of problem detection only at runtime:

    List v = new ArrayList();
    v.add(new Integer(42));
    v.add("test"); // One could even write this kind of line that would pass compilation AND execution.
    Integer i = (Integer) v.get(0); 
    i=(Integer) v.get(1); // We retrieve the "test" String and this launch an exception at runtime, even though it compiles smoothly :-(

And now, it’s much more concise (in addition, I’ll add autoboxing here), and reliable:

    List<Integer> v = new ArrayList<>();
    v.add(42);
    // v.add("test"); // This line no longer compiles.
    int i=v.get(0);   // Here, there is certainty that if it compiles, it will also run. For typing only
    i=v.get(1);       // There will still be an IndexOutOfBoundsException since we put the v.add("test") line in a comment.

A little focus on annotations

Annotations are not completely new. The javadoc is based on the same mechanism, but used outside the compiler. Within the compiler itself, @deprecated was an annotation already used. And, before Java 5, there were already tools to modify the code before compilation, such as XDoclet for example. The possibility of creating your own annotations and their integration into the bytecode made it possible to go much further.

At the very beginning, the annotations were ultimately just simple order/information provided to the compiler. Now they are an essential part of the Java ecosystem.

The arrival of annotations in the byte code made it possible to define the link between the code and the database (ORM) with annotations such as @Entity, @Table, @Id, directly in the code . It was then up to the JPA implementation (typically Hibernate) at runtime to make the right connections so that everything would “automagically” fall into place. Bye bye XML configuration files. This also allowed frameworks like Spring to allow the declaration of inversion of control (@Autowired) or to declare a web service (@GetMapping("/monEndpoint")) directly in the Java code. , without going through heavy external XML configuration files again.

Clearly, a big step forward in daily coding practice, with more understandable code, less prone to errors.

Some annotations are defined as standard (@Override, @Deprecated, @SuppressWarnings, @SafeVarargs, @FunctionalInterface). While others exist in the enterprise part of Java (Java EE / Java Enterprise Edition / Jakarta EE), like JPA annotations (@Entity, @ManyToMany, @JoinTable) and are de facto often integrated by Java projects, by libraries which have become de facto standards, like JUnit (@ Test, @Before, @After). And to give ourselves a headache, there are annotations on annotations (@Retention, @Target, @Inherited, @Documented), used when developing your own annotations.

This greatly facilitates the use of frameworks.

The downside is the risk of not fully understanding what the framework does and how it does it. In particular, when there is a link to a relational or non-relational database, this magic aspect can lead to very average performance when dealing with large volumes of data.

2006: Java 6

Integration of compilation and [scripting](https://www.jcp.org/en/jsr/detail?id =223) in Java. I almost used it for a client project, but ultimately didn’t.

For the rest, performance improvements, and changes on the Java side on the workstation. On the server-side development, not much changes though.

Not being a full-time developer for several years, my team spent a lot of time doing things that were exotic at the time: developing mobile applications for Windows Mobile, for feature phones in J2ME, and even for iPhone some time after, when Steve Jobs said there would be no third-party applications on the iPhone .

The historic era: the evolutions of Java since the acquisition of Sun by Oracle.

Five years passed between Java 6 and Java 7. It was a long time, and it was the longest period between two versions of Java. During this time, Oracle acquired Sun for 7.4 billion dollars in 2009. Oracle had been involved in Java for a long time: Oracle email addresses were often seen in JSRs well before 2009. Oracle was a member of the Java Community Process, the mechanism for developing and revising Java’s technical specifications.

Starting from 2001, I haven’t been actively developing and I’m completely out of the game. Therefore, I will rely on studying the Java release notes to catch up.

On the subject of evolving the Java platform, the big innovation is the Java Enhancement Proposal (JEP). The first one, JEP 1: JDK Enhancement-Proposal & Roadmap Process , introduced the concept of Java Enhancement Proposal in June 2011. There were no JEPs before to track the evolution of the language, but there were JSRs (Java Specification Requests). The first operational JEP is JEP 101: Generalized Target-Type Inference , which was integrated as early as Java 8.

2011: Java 7

There are some very appreciable enhancements for developers: it’s now possible to make large numbers more readable by using the thousands separator “_”, or writing them in binary notation. It’s also possible to use switch statements with Strings, catch multiple exceptions at once, and rely on try-with-resources to no longer worry about properly closing a resource in a finally block. Honestly, using it on Advent of Code 2023 , all of this has proven to be very useful.

2014: Java 8 (LTS) - lambda functions and Streams API

In terms of development, the new features include the native integration of a JavaScript interpreter , and a new date/time API (which was indeed not great initially, from memory) “borrowed” from the third-party library Joda-Time . Well, not exactly “borrowed”: it’s the same person, Stephen Colebourne , who is behind both Joda-Time and this new API. He took the opportunity to address certain issues from Joda-Time. Also, interfaces can define static methods.

And last but not least, a major, major evolution: the lambda expressions ! It’s a feature that has been discussed for introduction in Java since 2006 . However, it wasn’t easy to integrate this while maintaining the current logic of the language. It took 8 years! This, for example, allows the avoidance of creating anonymous classes and therefore improves code readability. Before:

    monObjetQuiEmetDesEvenements.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent event) {
            System.out.println("quelque-chose est arrivé");
        }
    });

After:

    monObjetQuiEmetDesEvenements.addActionListener(event -> System.out.println("quelque-chose est arrivé"));

It can be a bit confusing because the simplicity of reading/writing relies on the compiler’s type inference. In the previous example, the compiler “automagically” determines that event is an ActionEvent and that the provided implementation corresponds to the actionPerformed() method. It can even be as confusing as Perl code when reading something like this, which displays all the elements of a list:

    list.forEach(System.out::println);

which, in old-school mode, would be written as follows:

    for (int i = 0; i < list.size(); i++) {
        System.out.println(list.get(i));
    }    

Here, there is the ability to designate a method via the :: notation. It can reference any method, whether it is static (String::valueOf), instance-based (myObject::toString), or even a constructor (String::new).

Furthermore, another similarity with Perl is that the value of the last expression serves as the return value. Read the following code:

BiFunction<Integer, Integer, Long> additionner = (val1, val2) -> (long) val1 + val2;`

There is no return keyword. Nevertheless, the additionner function returns a Long. There’s a lot of magic behind this: technically, the compiler infers the type of lambda functions to fit into one of the classes in the java.util.function package. In this package, we find the BiFunction class used in one of the previous examples, which represents a function taking two input parameters and producing an output.

Now that we’ve seen all of this, we can tackle the big piece, whose syntax baffles old-timers like me: the streams/filter/map/reduce on collections : we understand the meaning of what it should do, but have no idea why it “works.” So, here’s a little lesson for the old-timers (I know someone who told me he wanted to understand how all of this works). Here, for example, is a piece of puzzling code:

    record Position(int x, int y) {};
    List<Position> list = ...;
    // find max lines and cols
    int nbLines = 1 + list.stream().mapToInt(pos -> pos.y()).max().getAsInt();
    int nbCols = 1 + list.stream().mapToInt(pos -> pos.x()).max().getAsInt();

If we break up the line list.stream().mapToInt(pos -> pos.y()).max().getAsInt(); from left to right:

  1. list.stream().mapToInt(pos -> pos.y()).max().getAsInt();
    The stream() method is defined in the Collection interface, so it’s safe to say that it’s available pretty much all the time when needed. If the data is in an array and not in a collection, the static method Arrays.stream(T[] array) does the same thing. So, here, it returns an instance of Stream<Position>. This Stream object is the starting point, the source of the stream: it provides a sequence of elements that will pass through a pipeline.
  2. list.stream().mapToInt(pos -> pos.y()).max().getAsInt();
    Next, we call the mapToInt() method on this instance of Stream<Position>. The Stream class has several methods of the form mapToSomething, which take a lambda function as a parameter that must return a Something, in this case an Int. The specific lambda function, pos -> pos.y(), extracts the y-coordinate of a position on the plane. The mapToInt method will call this lambda function for each element of the stream. This mapToInt method will itself return a stream, which will be an IntStream. It also returns a Stream.
    There are others methods of Stream like mapToSomething, that are often used, and that also return a Stream: sorted(), sorted(Comparator<? super T> comparator), reversed() for sorting, and filter(Predicate<? super T> predicate) for filtering.
    We could then again chain another call to a mapToSomething
  3. list.stream().mapToInt(pos -> pos.y()).max().getAsInt();
    … But here we will use another method of IntStream that all SomeNumberTypeStream have: the max() method. This will return the maximum value passed through the stream. Not as a primitive type, but as an OptionalInt , an object that manages the fact that if the stream is empty, there is no known maximum.
  4. list.stream().mapToInt(pos -> pos.y()).max().getAsInt();
    Finally, we call getAsInt(), which therefore returns this maximum value if the stream was not empty and throws an exception otherwise.
    At the end of the process, we can either end with a primitive type such as int/long, for example by finishing with .max().getAsInt();/.max().getAsLong();. We can also end by obtaining a List with .collect(toList()), or an array with .toArray().
    If we want to end with processing each element, instead of retrieving the list and going through it, we can terminate the stream processing by passing a lambda function as a parameter to .forEach(Consumer<? super T> action). However, be careful if the lambda function has a side effect: it could go wrong if the stream is being processed in parallel.

There are many other interesting aspects to delve into on this topic, such as internal optimizations (when possible, the entire stream is not processed) or parallel streams. It’s something that would have greatly interested me if I had hopped on the bandwagon back then with time to explore all this in depth.

2017: Java 9 - REPL and modules

A Java shell . Apparently, mainly motivated by educational reasons: Scala, Ruby, JavaScript, Haskell, Clojure, and Python can be used in a REPL (Read-Eval-Print Loop), and therefore, Java is less commonly used for learning computer science. Funny, it reminds me of the REPL of GFA Basic on Atari.

Various improvements that don’t seem to fundamentally change the daily routine in server development (support for high resolution screens , support for XML catalogs ). More compact Strings in memory don’t change anything in terms of development, but they are appreciated in terms of memory usage on servers. After adopting date management from the Joda-Time library in Java 8, Oracle continues to enhance Java by incorporating an HTTP client as an incubator module, which will undergo some adjustments in versions 10 and 11. Interfaces can now define private methods.

The removal of JavaDB , an Apache project that allowed for embedding a database engine in Java. I didn’t even know that existed. Verification: it was embedded in JDK versions 7 and 8 .

Funny enough, following changes in Java 8, the character _ could be used as an identifier . Java 9 addressed the issue by disallowing it. However, it made a comeback, six years later in preview in Java 21 with the introduction of sealed classes .

The introduction of the module concept aims to resolve the JAR hell that I have indeed encountered (when your code no longer works because a JAR and/or a class has been interleaved or has changed position in your classpath). It’s a feature that took a long time to be integrated into Java, as the first attempts to integrate Jigsaw date back to 2011 , six years earlier, in Java 7. A module is a “coherent whole.” A module is generally represented by a single JAR archive. Modules group together multiple packages. Up to this point, there’s not much difference from the practices of the time, ultimately. However, this intermediate level affects the visibility of contained packages and classes. A module can include a certain implementation of a package but and can decide to not expose it publicly. Unfortunately, it does not enable multiple versions of the same package to coexist, in the case of modules needing different implementation versions of the same package. The JAR hell isn’t over ;-(

This introduces a concept that has existed for years in other ecosystems like Perl: the notion of dependencies.

The Java code included in the JDK used this modularization to no longer make purely internal APIs accessible. The code was reorganized. This was transparent to all users of Java in the “normal” way. It also enabled easier internal maintenance for Oracle and resulted in a faster JVM startup.

2018: Java 10 - var keyword

We can now declare a local variable with var , and the compiler infers its type. It will feel strange to me, as I was so used to knowing what a variable was before reading its name. Afterwards, I don’t feel like it’s being used a lot (no statistics found on this - it’s just a feeling from talking to people around me and searching the web).

There are internal improvements that don’t fundamentally change the daily grind in build. They probably have more impact in run (garbage collector , JIT compiler, Class-Data sharing to speed up startup and reduce memory footprint, default root certificates , etc).

2018: Java 11 (LTS) - First paid version of Oracle JDK

Internal language improvements without developer impact. However, a complement (JMX and JConsole existed previously) to instrument the JVM with Flight Recorder has been added.

Something exotic: following the introduction of the REPL in Java 9, it is now possible to launch Java source code : the interpreter will compile it and then execute it, passing arguments as parameters. It is even possible to include a shebang at the beginning of a .java file that would make it executable, allowing it to be run as a script. Surprising, isn’t it?

Some very useful new methods for strings: isBlank(), lines(), strip(), stripLeading(), stripTrailing(), and repeat().

Support for TLS version 1.3 and HTTP/2 with the java.net.http package, which is no longer a preview feature.

Ah! CORBA , JavaFX, Java Web Start and applets are removed. Good idea in my opinion!

Major change in licensing: for enterprises, Oracle’s JDK becomes a paid service. Oracle continues to distribute Java for free under an open-source license (GPL v2 with the classpath exception) through the OpenJDK project, which does not benefit from the same security updates as the paid version.

Furthermore, there is a new release cadence for Java: a new version every 6 months. With such a short interval between releases, some versions will bring few new features. This is the case for the next two versions, Java 12 and Java 13.

2019: Java 12

For developers, a new feature in preview mode: switch can be used in an expression. And the introduction of the yield keyword—but not with the same meaning as in Python. This is useful for enhancing code comprehension.

More changes in the JVM execution, with a new experimental garbage collector, Shenandoah .

2019: Java 13

For developers, the ability to write strings across multiple lines using a text block via """ . Not a fundamental change, but it’s nice, and surprising that it wasn’t already integrated into the language, considering it is widely used elsewhere. It is in preview mode at this stage. As well as switch in expressions, which moves to second preview .

Some changes in the JVM execution.

The contemporary era: the last three years

2020: Java 14

Some syntactic sugar (Pattern matching for instanceof ) to make the code lighter when following a instanceof test. Before:

    if (obj instanceof Machin) {
        Machin m = (Machin) obj;
        m.methodSpecificToAMachin();
    }

After:

    if (obj instanceof Machin s) {
        s.methodSpecificToAMachin();
    }

So, it’s indeed less cumbersome. But, it can have some twisted consequences in certain cases. For example:

    if (obj instanceof String something) {
        // Here, the type of "something" is a String, as if we wrote "String something=(String) obj"
    } else {
        // but, here, "something" is not declared as "(String) obj". 
        // So, if we try to use it here, it will not compile.
        // Or ... it will compile if, and only if "somethings" 
        // is a property if the class containing this piece of code
    }
    if (obj instanceof String s && s.length() > 5) { 
    	// correct: "s.length()" is understood by the compiler as "((String) obj).length()"
    }
    if (obj instanceof String s || s.length() > 5) {
    	// incorrect: "s.length()" is not understood by the compiler as "((String) obj).length()"
    } 

It’s a preview feature.

The introduction of record, in preview mode, which are typed tuples. We could already do this before, by going through a class definition, but now, it’s frankly less cumbersome to write and read.

A more informative NullPointerException .

switch, text block, and record continue to evolve (still a preview feature).

Outside of pure development: a system for packaging Java+JRE applications , the ability to access memory areas outside the JVM (incubator feature), monitoring , and various updates to the garbage collector.

2020: Java 15

Introduction, in preview, of Sealed classes & interfaces, which allow a class or interface to control/indicate who has the right to inherit it. It’s strange, but it has a real use, especially when developing a library and not knowing how it will be used/extended by a third party.

Text blocks / Strings across multiple lines are introduced definitively .

The ZGC garbage collector , designed to freeze the JVM less, is now usable in production. The Shenandoah garbage collector has also arrived in production.

Finally, some cleanup (Solaris/SPARC , Nashorn JavaScript Engine - introduced in 2014 with Java 8) and preparation for cleanup (RMI ).

2021: Java 16

In incubator mode, the ability to use hardware acceleration during vector operations (in the mathematical sense, not the Vector Java object). This has no impact on classic web applications and provides an alternative to JNI for binding to native code.

Also in incubator, there is the ability to call native code without the overhead of JNI or its successors. These recent modifications, as well as the ability to access memory areas outside the JVM (Java 14 preview), ultimately move towards stronger integration with the world outside of Java.

Implementation of Unix sockets , which can be useful on the server side for communicating with legacy applications.

Internal modifications (C++ , change of the versioning system and of the source code repository , ports to new platforms), without developer impact. Apart from the port to Alpine Linux which is of interest to people using Docker.

Pattern matching for instanceof and records have been definitively endorsed.

2021: Java 17 (LTS)

Some clean-up (deprecation of the applets API and Security Manager , removal of parts of RMI , arrival of experimental AOT and JIT compilers ), marginal improvements (Pseudorandom Number Generators , pmacOS port , Java2D redesign on Mac , security , sealed classes and Vector API - both still in preview - have been improved). What were in incubator are still there. Nothing really new.

For developers, the big new feature is the possibility (preview feature), to do pattern matching in a switch , in order to select code based on the type of the object:

    Object   o   =   ...;
    return   switch   (o)   {
       case   null      -> "Null";
       case   String s  -> String.format("String %s", s);
       case   Long   l  -> String.format("long %d", l);
       case   Double d  -> String.format("double %f", d);
       case   Integer i && i>0   // refining patterns
                        -> String.format("positive int %d", i);
       case   Integer i && i==0   
                        -> String.format("zero int %d", i);
       case   Integer i && i<0   
                        -> String.format("negative int %d", i);
       default          -> o.toString();
       };
    };

2022: Java 18

Internal cleanup/plumbing (internal reimplementation of java.lang.reflect ). Marginal improvements (Vector API, pattern matching in switches, and the possibility of calling native code - always in preview - are improved). The implementation of a mechanism that allows name resolution without going through the OS might seem like a minor improvement. However, it is a necessary step for a future and useful implementation of DNS over HTTPS.

UTF8 becomes the default character encoding . Not too soon. Python was doing it all along! This may shake a little when upgrading. Especially since an encoding problem generally does not prevent the application from starting. No clean cut. It would probably be necessary to test all file consumption and production (csv, txt, PDF).

Java integrates a static web server , jwebserver, not for use in production, just in prototype mode. Not even for serious development, given the lack of functionality (no ACL, no HTTPS, no authentication). Apparently, as with the REPL, it is for educational purposes.

The nice little thing: the addition of @snippet as an alternative to the <pre> used for frame example code in a javadoc. And which allows you to go further (adding a link, highlighting a particular passage in the code).

The finally of the try {...} catch {...} finally {...} clauses becomes deprecated . You must use try-with-resources introduced in Java 7, 11 years ago.

2022: Java 19

Further improvements on elements still in preview or in incubator (API Vector , the pattern matching in switches, and the possibility of calling native code ).

In preview, the ability to perform pattern matching, on record and not only on class. A logical sequence.

The arrival of a real novelty: virtual threads. Java will be able to have virtual threads , aka lightweight threads, like other languages/platforms! This inevitably makes me think of Erlang. The main advantage of virtual threads, compared to system threads, is the memory savings: a system thread uses a base of 2MB of memory! In preview only.

2023: Java 20

The only new feature, in incubator mode, is scoped values , a feature allowing immutable variables (i.e., those that do not change after their initial assignment) to be shared between threads. This is interesting when there are a large number of virtual threads. It is already possible to have thread-local variables. But, with open access in terms of read/write access to these variables. Not very secure. Moreover, when creating child threads, the parent thread’s variables are essentially duplicated, which is costly in terms of time and memory. This is clearly not compatible with a system based on several thousand virtual threads.

For the rest, there are only refinements of elements already in preview or incubator mode.

2023: Java 21 (LTS)

It’s an LTS (Long Term Support release). Oracle is committed to providing long-term support for clients using their paid version of the JDK. Unsurprisingly, this is accompanied by the integration of two elements previously in preview:

In addition, there is the introduction of an interface to unify the way of iterating, accessing the first and last elements of an ordered collection , and reversing and adding an element to the beginning or end. The somewhat interesting thing about this is to look at how it has been inserted into the hierarchy of existing classes and interfaces. But this will probably not revolutionize anything.. However, it is likely not going to revolutionize anything.

In preview, a very nice feature: the ability to use text blocks to create a string . This has the power of text report creations in Perl. It doesn’t necessarily go as far as that, as Perl reports, which made a lot of sense in a text terminal world, may have less relevance now. This is interesting because it can make certain code passages clearer. This feature can also be used to produce things beyond just text output, such as JSON (in the form of JSONObject) or even SQL.

In preview mode, the introduction of underscore as a keyword . The idea is, like in many other languages, to simply use _ to represent a variable that won’t be used later. Obviously, no one would declare a variable and not use it later. But in the case of pattern matching, or when there is an unused return from a certain function/method, this makes sense to help improve the readability of the code.

In preview mode, once again, you can create a Hello World program in Java that looks like a Hello World in C, as you can create unnamed classes , with an instance method main() rather than a static method. It’s worth testing in combination with JEP 330 for writing Java scripts! Welcome to cgi-scripts, so 2000s, written in Java!

Improvements to features already in preview or incubator. Some cleanup with the deprecation of the 32-bit x86 version .

A few enhancements for the Z Garbage Collector . And something very ops-oriented: the future prohibition of dynamically loading agents . This will pose problems for ops who instrument the JVM after it has started.

2024: Java 22

Unsurprisingly, many elements that are not yet mature remain in preview or incubator: String templates , Vector API , simple programs with a main function like in C .

Some ops enhancements (no latency og GC while JNI execution , JNI foreign function & memory API , and a new class file API in preview)

Since Java 11, the javabinary is able to compile then launch a unique .javafile (JEP 330 ). Now, with the JEP 458: Launch Multi-File Source-Code Programs , you can do the same with multiples .javafiles. ench <-> English This can be useful, for example in a teaching approach, as an intermediate step between “I develop simple code in Java” and “I develop more complex code in Java, which requires setting up a build chain.”

An interesting point, not from a technical perspective, but from a developer relationship perspective: a return in preview of the Scoped values and of the Structured Concurrency API , already in incubator in Java 19. This is to get more feedback on the subject.

Another interesting point, in preview: the ability to write custom intermediate stream operation . A 8 years old demand ;-).

A small but useful enhancement: the ability to do some tests or statement in the constructor of a child class before calling the mother constructor with super(). I was very frustrated not to able to do this. Maybe possible in production in a few months, since it’s a preview feature. And the introduction of underscore as a keyword as preview in Java 21 is now production ready .

Final impressions and key takeaways

Quite a few changes from an ops standpoint (removal of obsolete features, faster JVM startup, new garbage collectors). But, as mentioned in the introduction, I’m not the best person to give an opinion on this matter.

So, what are, according to me, the main points to remember from these Java developments, from a development perspective, over the past twenty years?

Many things have been added to Java in response to a certain “hype” around other languages (the REPL, the ability to run a .java file directly from the Java binary without explicit compilation, the ability to write Java code without defining a class, static web server ). I am skeptical about their usefulness for serious server-side development. I can’t say if they have had a real impact on language learning.

There are API evolutions / assimilation of external APIs. Obviously, this doesn’t revolutionize anything, but if it can prevent each project from having its own little utility library, that’s not a bad thing.

Some new features, in theory, change very little, but make a real difference in everyday use(*). From my perspective, the syntactic heaviness that Java used to have compared to Python and/or Perl has disappeared (generics, enhanced for loop, autoboxing, pattern matching in switches, and even the very recent ‘_’).

Two theoretical and practical innovations have greatly changed Java since its beginnings: annotations and lambda functions/the Stream API.

I had never taken a step back to look at how the language and the JVM evolve. I observed that the removal of features takes time, which makes sense, through the long deprecation process (10 years for finally). Conversely, it takes 6 to 8 years for major evolutions to be definitively integrated (6 years for generics, 8 years for closures/lambda functions). It would be interesting to compare the time required for such evolutions to be integrated with the time needed in other languages. Additionally, Java only releases in a single variant, utilizing only the mechanism of preview features/experimental features/incubator modules. This is not necessarily the choice in all languages.

(*) Thanks to some colleagues, I took part in the Advent of Code event using Java, especially my rusty old Java. And since I had already completed a purely bibliographic study of Java’s developments, I knew that at times I could do things differently than using “old-school” code. Honestly, putting things into practice, especially through this Advent of Code event that allows you to focus on language and algorithms, makes all the difference compared to a purely bibliographic approach. From a distance, using String in switch, and using switch in expressions, may seem trivial. But in reality, it changes everything. I can’t wait to use all the annotations.

A man walking on the road towards a setting sun, with lines of code in the sky. The journey continues… - AI generated - January 4, 2024 at 2:40 PM

Some sources