Undoubtedly, JDK-11 was an import milestone but once the dust settled, another target appeared on the horizon, JDK-17, the next LTS release. But between those, the avalanche of new releases and features was unleashed.
JDK 12
JDK-12 didn't have too many features packed into it nonetheless it includes considerable number of improvements, especially to G1 garbage collector.
JEP-334: JVM Constants API: introduces an API to model nominal descriptions of key class-file and run-time artifacts, in particular constants that are loadable from the constant pool.
JEP-230: Microbenchmark Suite: adds a basic suite of microbenchmarks to the JDK source code, and make it easy for developers to run existing microbenchmarks and create new ones.
JEP-189: Shenandoah: A Low-Pause-Time Garbage Collector (experimental): adds a new garbage collection (GC) algorithm named Shenandoah which reduces GC pause times by doing evacuation work concurrently with the running Java threads. Pause times with Shenandoah are independent of heap size, meaning you will have the same consistent pause times whether your heap is 200 MB or 200 GB. As we know, Shenandoah was improved in the latter releases and got backported to JDK-11.
JEP-344: Abortable Mixed Collections for G1: makes G1 mixed collections abortable if they might exceed the pause target.
JEP-346: Promptly Return Unused Committed Memory from G1: enhances the G1 garbage collector to automatically return Java heap memory to the operating system when idle.
JEP-341: Default CDS Archives: enhances the JDK build process to generate a class data-sharing (CDS) archive, using the default class list, on 64-bit platforms. In scope of CDS efforts, it modified the JDK build to run
java -Xshare:dump
(it needed manual execution) after linking the image and keeps the resulting CDS archive in the lib/server directory, so that it is becomes a part of the resulting image.JDK-8205516: JFR tool: introduces a new JDK Flight Recorder command line tool, jfr (which later was backported to JDK-11).
JDK-8188147: Compact Number Formatting support: a new class CompactNumberFormat has been added to the standard library that formats a decimal number in its compact form. Please check out this excellent blog post to learn more.
JDK-8210838: Override javax.crypto.Cipher.toString(): the Cipher's toString() method now returns a string containing the transformation, mode, and provider of the Cipher
The String class had gotten a number of new methods:
The Class class was also not left out:
The VarHandle minor improvements:
- String toString() (was overridden to return compact description)
- Optional<String> describeConstable()
Very handy addition to InputStream:
The Files utility class got just one new method:
Several useful overloaded forms of the existing methods were added to (already monstrous) CompletionStage class:
- CompletionStage<T> exceptionallyAsync(Function<Throwable, ? extends T> fn)
- CompletionStage<T> exceptionallyAsync(Function<Throwable, ? extends T> fn, Executor executor)
- CompletionStage<T> exceptionallyCompose(Function<Throwable, ? extends CompletionStage<T>> fn)
- CompletionStage<T> exceptionallyComposeAsync(Function<Throwable, ? extends CompletionStage<T>> fn)
- CompletionStage<T> exceptionallyComposeAsync(Function<Throwable, ? extends CompletionStage<T>> fn, Executor executor)
Probably, the most practical (from the developer's standpoint) addition to the standard library was introduction of the teeing collector, available via Collectors utility class:
- static <T,R1,R2,R> Collector<T,?,R> teeing(Collector<? super T,?,R1> downstream1, Collector<? super T,?,R2> downstream2, BiFunction<? super R1,? super R2,R> merger): returns a Collector that is a composite of two downstream collectors. Let us consider an overly simplified but illustrative example below when there is a need to find the shortest and longest (by length) string in the stream:
final Stream<String> stream = Stream.of( "Tom", "Samantha", "John" ); var minMaxByLength = stream .collect( Collectors.teeing( Collectors.minBy(Comparator.comparing(String::length)), Collectors.maxBy(Comparator.comparing(String::length)), (shortest, longest) -> new String[] { shortest.orElse(""), longest.orElse("") } ) );
The result of this code snippet is:
Shortest = Tom, Longest = Samantha
By all means, this rather small change would significantly reduce the amount of the boilerplate code involved while dealing with Java Stream APIs.
- static <T,R1,R2,R> Collector<T,?,R> teeing(Collector<? super T,?,R1> downstream1, Collector<? super T,?,R2> downstream2, BiFunction<? super R1,? super R2,R> merger): returns a Collector that is a composite of two downstream collectors. Let us consider an overly simplified but illustrative example below when there is a need to find the shortest and longest (by length) string in the stream:
A whole new package, java.lang.constant was introduced in scope of the JEP-334: JVM Constants API implementation. Consequently, some classes got new methods as well.
Double got:
Integer got:
Float got:
Enum got:
- new class Enum.EnumDesc<E extends Enum<E>>, a nominal descriptor for an enumeration constant
- Optional<<Enum.EnumDesc<E>> describeConstable()
Besides the changes we have talked about, JDK-12 has delivered quite a number of the security enhancements, notably:
- JDK-8148188: Enhance the security libraries to record events of interest (JFR)
- JDK-8212605: Pure-Java implementation of AccessController.doPrivileged
- JDK-8212261: Add SSLSession accessors to HttpsURLConnection and SecureCacheResponse
- java.net.SecureCacheResponse was enriched with Optional<SSLSession> getSSLSession()
- java.net.ssl.HttpsURLConnection was enriched with Optional<SSLSession> getSSLSession()
JDK 13
Just when the excitement after JDK-12 release faded away, the JDK-13 was ready to come along. By all means, it was a minor one from the features perspective.
JEP-351: ZGC: Uncommit Unused Memory: enhances ZGC to return unused heap memory to the operating system. It continues the work started in JDK-11, still keeping the ZGC under experimental label.
JEP-350: Dynamic CDS Archives: extends application class-data sharing to allow the dynamic archiving of classes at the end of Java application execution. The archived classes will include all loaded application classes and library classes that are not present in the default, base-layer CDS archive. A shared archive will be dynamically created when an application exits if the
-XX:ArchiveClassesAtExit
option is specified.$ java -Xshare:dump -XX:ArchiveClassesAtExit=app.jsa -cp app.jar AppMain
Once the dynamic archive is ready, the application could be run using it:
$ java -XX:SharedArchiveFile=app.jsa -cp app.jar AppMain
Mostly every new JDK release brings improvements into application class-data sharing, promising substantial reduction of the application startup times.
JEP-353: Reimplement the Legacy Socket API: replaces the underlying implementation used by the java.net.Socket and java.net.ServerSocket APIs with a simpler and more modern implementation that is easy to maintain and debug. The new implementation will be easy to adapt to work with user-mode threads, a.k.a. fibers, currently being explored in Project Loom.
Interestingly, the old implementation was kept in the JDK and could be switched to using
-Djdk.net.usePlainSocketImpl=true
system property.JDK-8220050: Deprecate -XX:-ThreadLocalHandshakes: deprecates
-XX:-ThreadLocalHandshakes
so that thread-local handshakes will be guaranteed to be available.JDK-8221431: Support for Unicode 12.1: adds support of the latest Unicode Standard version 12.1.
JDK-5029431: Add absolute bulk put and get methods: the buffer types in java.nio package now define absolute bulk
get
andput
methods to transfer contiguous sequences of data without regard to or effect on the buffer position.The java.nio.Buffer also had a new method:
The java.nio.MappedByteBuffer got one improvement:
The String class got a few new methods:
But that is not it, the String also got a new hashCode() optimization, see please JDK-8221836: Avoid recalculating String.hash when zero for more details.
Last but not least, a large chunk of the improvements went into Shenandoah GC (not to mention numerous bug fixes):
- JDK-8221766: Load-reference barriers for Shenandoah
- JDK-8222185: Shenandoah should report "committed" as capacity
- JDK-8222186: Shenandoah should not uncommit below minimum heap size
- JDK-8223759: Shenandoah should allow arbitrarily low initial heap size
- JDK-8223767: Shenandoah fails to build on Solaris x86_64
- JDK-8224584: Shenandoah: Eliminate forwarding pointer word
- JDK-8225048: Shenandoah x86_32 support
- JDK-8225229: Shenandoah: trim down default number of GC threads
- JDK-8225046: Shenandoah metrics logs refactoring
- JDK-8224584: Shenandoah: Eliminate forwarding pointer word
- JDK-8221750: Shenandoah: Enable ThreadLocalHandshake by default
- JDK-8221507: Implement JFR events for Shenandoah
- JDK-8217343: Shenandoah control thread should be able to run at critical priority
- JDK-8217016: Shenandoah: Streamline generation of CAS barriers
A fair amount of the security enhancements in JDK-13 is certainly worth checking out.
JDK 14
Going further, the JDK-14 kept the steady pace of innovation and was bundled with quite a useful set of changes. Let us take a look at the most interesting ones.
JEP-345: NUMA-Aware Memory Allocation for G1: improves G1 performance on large machines by implementing NUMA-aware memory allocation.
JEP-363: Remove the Concurrent Mark Sweep (CMS) Garbage Collector: brings the end to the life of the Concurrent Mark Sweep (CMS) garbage collector.
JEP-352: Non-Volatile Mapped Byte Buffers: adds new JDK-specific file mapping modes so that the FileChannel API can be used to create MappedByteBuffer instances that refer to non-volatile memory.
JEP-349: JFR Event Streaming: exposes JDK Flight Recorder data for continuous monitoring.
JEP-361: Switch Expressions: extends
switch
so it can be used as either a statement or an expression, and so that both forms can use either traditionalcase ... :
labels (with fall through) or newcase ... ->
labels (with no fall through), with a further new statement for yielding a value from aswitch
expression. These changes will simplify everyday coding, and prepare the way for the use of pattern matching in switch. There are quite a few important considerations to keep in mind.First, a new form of switch label,
"case L ->"
, was introduced. Second, the code to the right of a"case L ->"
switch label is restricted to be an expression, a block, or a throw statement. The block is pretty much simplified version of the good oldswitch
statement, please notice absence of explicitbreak
and optionaldefault
case.final Character command = (char)System.in.read(); switch (command) { case 'A' -> { // ... } case 'B', 'C', 'D' -> { // ... } case 'E' -> throw new UnsupportedOperationException("Unsupported: " + command); }
The expression form is a new and exceptionally useful form of
switch
statement, the example is below.final Callable<?> callable = switch (command) { case 'A' -> new CommandA(); case 'B', 'C', 'D' -> new CommandOthers(); default -> new CommandHelp(); };
Contrary to the
switch
statement, the cases of aswitch
expression must be exhaustive: for all possible values there must be a matching label, hence the presence of thedefault
case (obviously throwing an exception is another valid option). In the case when the code blocks are needed, a newyield
statement was introduced to yield a value for the enclosing expression, for example:final Callable<?> callable = switch (command) { case 'A' -> { System.out.println("Executing command A"); yield new CommandA(); } case 'B', 'C', 'D' -> { System.out.println("Executing command B, C or D"); yield new CommandOthers(); } default -> { throw new UnsupportedOperationException("Unsupported: " + command); } };
Please note, rather than being a keyword,
yield
is a restricted identifier (much likevar
). Thebreak
statement andyield
statement facilitate easy disambiguation betweenswitch
statements andswitch
expressions. For more details, please check out this excellent Definitive Guide To Switch Expressions post.JEP-358: Helpful NullPointerExceptions: improves the usability of NullPointerExceptions generated by the JVM by describing precisely which variable was
null
. Undoubtedly, from the developer's standpoint, this feature alone deserves immediate switch to JDK-14. The following code snippet is going to convince you that it is worth it.package com.example; import java.util.Map; public class NpeStarter { public static void main(String[] args) { Map.Entry<String, String> entry = null; if (entry.getKey().equals("key")) { // ... } } }
What you get on pre-JDK-14 releases is faceless NullPointerException, where you have yet to figure out what exactly (
entry
?entry.getKey()
?) isnull
.Exception in thread "main" java.lang.NullPointerException at com.example.NpeStarter.main(NpeStarter.java:8)
The arrival of JDK-14 changes that completely:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.util.Map$Entry.getKey()" because "entry" is null at com.example.NpeStarter.main(NpeStarter.java:8)
JDK-8234863: Increase default value of MaxInlineLevel: this small change went mostly unnoticeable but very likely has a beneficial impact on real world applications. It was also backported to JDK-11.
New methods found the way into PrintStream class:
The brand new Serial annotation had been introduced to indicates that an annotated field or method is part of the serialization mechanism defined by the Java Object Serialization Specification.
The NullPointerException class was enhanced to reflect the changes introduced by JEP-358: Helpful NullPointerExceptions:
- String getMessage() (override)
In scope of JDK-8225339: Optimize HashMap.keySet()/HashMap.values()/HashSet toArray() methods , the class as stable as java.util.HashSet, got some neat (but not reflected in javadocs) overrides as well.
Probably, StrictMath class had benefited the most in this release:
Parallel GC got much better in JDK-14, a few notable improvements are mentioned below:
The JDK-14 was not without the security enhancements, the most disruptive of those was probably the removal of the java.security.acl
APIs (see please JDK-8191138).
JDK 15
Arguably, the JDK-15 was a major one for the reasons that many experimental and preview features had finally graduated to mainstream and became ready for production use. Let us start from those.
JEP-377: ZGC: A Scalable Low-Latency Garbage Collector (Production): changes the Z Garbage Collector from an experimental feature into a product feature. ZGC could enabled via the
-XX:+UseZGC
command line option.JEP-379: Shenandoah: A Low-Pause-Time Garbage Collector (Production): changes the Shenandoah garbage collector from an experimental feature into a product feature. Shenandoah GC could enabled via the
-XX:+UseShenandoahGC
command line option.JEP-339: Edwards-Curve Digital Signature Algorithm (EdDSA): implements cryptographic signatures using the Edwards-Curve Digital Signature Algorithm (EdDSA) as described by RFC 8032. A number of new classes had been added into java.security.spec and java.security.interfaces packages:
JEP-378: Text Blocks: adds text blocks to the Java language. A text block is a multi-line string literal that avoids the need for most escape sequences, automatically formats the string in a predictable way, and gives the developer control over the format when desired. The text block is denoted with triple double quotes
"""
(fat delimiters).var text = """ This is a multi-line text block """;
From language usability perspective, this long-awaited feature dramatically simplifies dealing with string literals for day-to-day developers, as well as makes code much more readable. There are tons of details and examples buried in the JEP (escaping, indentation, ...) so I would highly recommend reading it, not to forget Definitive Guide To Text Blocks post.
JEP-373: Reimplement the Legacy DatagramSocket API: replaces the underlying implementations of the java.net.DatagramSocket and java.net.MulticastSocket APIs with simpler and more modern implementations that are easy to maintain and debug. The new implementations will be easy to adapt to work with virtual threads, currently being explored in Project Loom. This is a follow-on to JEP-353, which already reimplemented the legacy Socket API and was delivered in JDK-13.
The old implementation was kept in the JDK and could be switched to using
-Djdk.net.usePlainDatagramSocketImpl=true
system property.JEP-371: Hidden Classes: introduces hidden classes, which are classes that cannot be used directly by the bytecode of other classes. Hidden classes are intended for use by frameworks that generate classes at run time and use them indirectly, via reflection. A hidden class may be defined as a member of an access control nest, and may be unloaded independently of other classes.
A normal class is created by invoking ClassLoader::defineClass, whereas a hidden class is created by invoking Lookup::defineHiddenClass. The JEP emphasizes that a hidden class is not anonymous. The only way for other classes to use a hidden class is indirectly, via its Class object. The hidden class cannot be used directly by bytecode instructions in other classes because it cannot be referenced nominally, that is, by name. Hidden class cannot enclose other classes and is unable to use itself as a field type, return type, or parameter type in its own field and method declarations.
A hidden class is not discoverable by the JVM during bytecode linkage, nor by programs making explicit use of class loaders.
Methods of hidden classes are not shown in stack traces by default. They represent implementation details of language runtimes, and are never expected to be useful to developers diagnosing application issues. However, they can be included in stack traces via the options
-XX:+UnlockDiagnosticVMOptions -XX:+ShowHiddenFrames
. For the StackWalker API, stack frames for hidden classes should be included by a JVM implementation only if the SHOW_HIDDEN_FRAMES option is set.Last but not least, the LambdaMetaFactory had been updated to use the new hidden classes Lookup APIs, which include:
- MethodHandles.Lookup defineHiddenClass(byte[] bytes, boolean initialize, MethodHandles.Lookup.ClassOption... options) throws IllegalAccessException (a hidden class can be created as a member of an existing nest by passing the NESTMATE option)
- Class<?> ensureInitialized(Class<?> targetClass) throws IllegalAccessException
JEP-374: Disable and Deprecate Biased Locking: disables biased locking by default, and deprecate all related command-line options.
The java.lang.Class had further support for hidden classes:
CharBuffer had one method added:
The CharSequence had a new method:
NullPointerException got an implementation of:
- Throwable fillInStackTrace() (override)
Two new methods were added to the java.lang.Math:
And to the java.lang.StrictMath consequently:
The java.lang.reflect.AnnotatedType interface had three new methods:
The java.util.NoSuchElementException class got a couple of additional constructors
The java.util.TreeMap got own implementation of:
- V compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) (override)
- V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction) (override)
- V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) (override)
- V merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction) (override)
- V putIfAbsent(K key, V value) (override)
The java.text.DecimalFormatSymbols had new methods to support monetary grouping separator:
A number of enhancements to java.nio APIs.
A number of gaps related to JEP-334: JVM Constants API were addressed:
Besides JEP-339 (EdDSA), there are quite a few security enhancements in JDK-15, the ones that deserve mentioning are:
- JDK-8241325: Enhance SunJCE provider to support SHA-3 based Hmac algorithms
- JDK-8242145: New System Properties to configure the TLS signature schemes
JDK 16
If JDK-15 qualified as a major one then JDK-16 could be easily stamped as huge, packed with new language features, tooling and GC improvements. Let us dig right in.
JEP-387: Elastic Metaspace: returns unused HotSpot class-metadata (i.e., metaspace) memory to the operating system more promptly, reduce metaspace footprint, and simplify the metaspace code in order to reduce maintenance costs.
JEP-376: ZGC: Concurrent Thread-Stack Processing: moves ZGC thread-stack processing from safepoints to a concurrent phase.
JEP-386: Alpine Linux Port: ports the JDK to Alpine Linux, and to other Linux distributions that use musl as their primary C library, on both the x64 and AArch64 architectures.
JEP-388: Windows/AArch64 Port: ports the JDK to Windows/AArch64.
JEP-390: Warnings for Value-Based Classes: designates the primitive wrapper classes as value-based and deprecate their constructors for removal, prompting new deprecation warnings. Provide warnings about improper attempts to synchronize on instances of any value-based classes in the Java Platform.
JEP-396: Strongly encapsulate JDK internals by default: strongly encapsulates all internal elements of the JDK by default, except for critical internal APIs such as
sun.misc.Unsafe
. Allows end users to choose the relaxed strong encapsulation that has been the default since JDK-9. The impact of this JEP may vary and we have talked about it in JEP-396 and You article.JEP-380 Unix-Domain Socket Channels: adds Unix-domain (AF_UNIX) socket support to the socket channel and server-socket channel APIs in the java.nio.channels package. Extends the inherited channel mechanism to support Unix-domain socket channels and server socket channels.
Unix-domain sockets are used for inter-process communication (IPC) on the same host. They are similar to TCP/IP sockets in most respects but are more secure and more efficient than TCP/IP loopback connections. The supporting API changes in the standard library include:
- new java.net.UnixDomainSocketAddress class
- new java.net.StandardProtocolFamily enumeration constant UNIX
JEP-395: Records: enhances the Java programming language with records, which are classes that act as transparent carriers for immutable data. Records can be thought of as nominal tuples. Records are a new kind of class in the Java language, designed specifically to model plain immutable data aggregates.
record Person(String name, int age) { Person { if (age <= 0) { throw new IllegalArgumentException("The age could not be negative"); } } }
Interestingly, records facilitated the relaxation of certain Java language constraints. Before JDK-16, an inner class was not allowed to declare a static member (unless the member is a constant variable). Starting from JDK-16 it is allowed by the inner class to declare members that are either explicitly or implicitly static, in particular - a member of the type record class. For example:
public class Outer { class Inner { record Person(String name, int age) { Person { if (age <= 0) { throw new IllegalArgumentException("The age could not be negative"); } } } } }
Local record classes are a particular case of nested record classes and could be used in place for complex transformations or/and for storing intermediate results. How the records support looks from the perspective of the API changes?
- new abstract class java.lang.Record (the common superclass of all record classes)
- new class java.lang.reflect.RecordComponent
- new class java.lang.runtime.ObjectMethods
- new java.lang.annotation.ElementType enumeration constant: RECORD_COMPONENT
- new Class methods:
The positive impact of this change on the Java programming language is tremendous. Looking into the future, the pattern matching on records is already scheduled for JDK-18.
JEP-394: Pattern Matching for instanceof: enhances the Java programming language with pattern matching for the
instanceof
operator. Pattern matching allows common logic in a program, namely the conditional extraction of components from objects, to be expressed more concisely and safely.final Number n = ...; if (n instanceof Long l) { // Use l as Long ... } else if (n instanceof Integer i) { // Use i as Integer ... }
Or in extended form, when the
if
conditionals may rely on the matched pattern and narrow it further along:final Long MIN_VALUE = 1000L; if (n instanceof Long l && l >= MIN_VALUE) { // Use l as Long ... }
Together with records, this feature makes very strong case for having comprehensive pattern matching support in Java, with augmented
switch
statement coming in JDK-17 (as preview feature).For more insights, please check out Pattern Matching in Java, Type Pattern Matching with instanceof and Java 16 Pattern Matching Fun articles.
JEP-392:Packaging Tool: provides the
jpackage
tool, for packaging self-contained Java applications. The supported platform-specific package formats are:- Linux:
deb
andrpm
- macOS:
pkg
anddmg
- Windows:
msi
andexe
Under the hood,
jpackage
tool prepares an application image as input to the platform-specific packaging tool that it invokes in its final step. An application image contains both the files comprising the application as well as the JDK runtime image that will run it. By default, thejpackage
tool invokes the jlink tool to create the runtime image.In addition to the command-line,
jpackage
is accessible via the ToolProvider API under the name of "jpackage".- Linux:
JDK-8255616: Removal of experimental features AOT and Graal JIT
JDK-8247536: Support for pre-generated java.lang.invoke classes in CDS static archive
JDK-8254723: Add diagnostic command to write Linux perf map file, very useful change for using JVM along with
perf record
, you just have to pass-XX:+UnlockDiagnosticVMOptions -XX:+DumpPerfMapAtExit -XX:+PreserveFramePointer
command line flags at startup.HTTP Client API got some improvements:
DateTimeFormatterBuilder had gotten one new method:
LogRecord from java.util.logging had some additions:
The java.nio had some APIs enhancements:
Multiple practical improvements went into Stream APIs
- List<T> toList()
var numbers = Stream .of(4.0, 16.0, 256.0) .map(Math::sqrt) .toList();
- <R> Stream<R> mapMulti(BiConsumer<? super T,? super Consumer<R>> mapper)
var numbers = Stream .of(2.0, 8.0, 16.0) .<Double>mapMulti((number, consumer) -> { consumer.accept(number); consumer.accept(Math.pow(number, 2)); }) .toList();
Would return a list of:
[2.0, 4.0, 8.0, 64.0, 16.0, 256.0]
- IntStream mapMultiToInt(BiConsumer<? super T,? super IntConsumer> mapper)
- LongStream mapMultiToLong(BiConsumer<? super T,? super LongConsumer> mapper)
- DoubleStream mapMultiToDouble(BiConsumer<? super T,? super DoubleConsumer> mapper)
- List<T> toList()
Many security enhancements were baked into JDK-16, just to mention a couple:
- JDK-8172366: Support SHA-3 based signatures
- JDK-8254709: TLS support for the EdDSA signature algorithm
- JDK-8242068: Signed JAR support for RSASSA-PSS and EdDSA
JDK 17: Mostly There
Nonetheless the JDK-17 is not out yet, it has entered Rampdown Phase Two, meaning its feature set is frozen and no further JEPs will be targeted to this release. In the upcoming part we are going to cover it right when its release is announced. Stay tuned!