Discover Azul's high-performance Java platform providing faster speed, startup, & efficiency without code changes
Support
Blog chevron_right Java

Java 15 – 64 JDK 15 New Features and APIs

How quickly six months pass.  It only seems like yesterday we were talking about JDK 14, records, pattern matching for instanceof and helpful NullPointerExceptions, yet here we are with JDK 15 delivered and ready to go.  As usual, I’ll look at all the new features and APIs included in this release.

As a release, it must be said that JDK 15 is not one of the bigger ones.  Although there are 14 JDK Enhancement Proposals (JEPs), several of these are simply changes from experimental to product features, extending preview features without change or deprecation and removal of features.

We’ll break things down by language, APIs, JVM and other.

Java 15 Language Updates

Sealed Classes

The most significant new feature in JDK 15, and the only change to the language, is the introduction of sealed classes as a preview feature. Preview features first appeared in JDK 12 and allowed for new functionality to be included in the JDK without adding it to the Java SE specification (this applies to the Java language, Java Virtual Machine, and Java SE APIs).

By doing this, feedback from users can be gathered and changes made where necessary or even, if deemed appropriate, the feature could be deleted. To use a preview feature, it is essential to enable them both at compile time and runtime using the --enable-preview flag (the --release flag must also be used at compile time).

Java is an object-oriented language, one of the main features of which is inheritance.  The idea is straightforward: common state and behaviour can be abstracted to a super-class and shared (through inheritance) by sub-classes.  This eliminates the need to reproduce the instance variables or methods of the super-class in each sub-class.  Prior to JDK 15, the only control a developer had over inheritance was the final modifier.  This prevents any class from extending the one using the modifier.  The only other way to control inheritance is to make a class or its constructors package-private, limiting possible sub-classes to those in the same package.  This approach is sometimes too coarse, which is where sealed classes come in.

Sealed classes (explained in detail in JEP 360) provide a fine-grained mechanism that allows a developer to restrict which other classes or interfaces may extend them.  You can think of final classes as the ultimate sealed class since no other classes can extend them.

The syntax for sealed classes extends the existing way of defining a class by adding the sealed and permits reserved identifiers. This is important because they are not new keywords so they can still be used as variable names and not break existing code.

A sealed class can be defined like this:

public sealed class Foo permits A, B, C { ... }

Only classes A, B, and C can extend class Foo, and they must be in the same package or module. The permits part of the definition can be omitted if A, B, and C are in the same compilation unit (typically file).

Each permitted sub-class must have its inheritance capabilities explicitly specified.  In our simple example:

  • Class A could be defined as final so that no further inheritance is allowed.
  • Class B could be defined as sealed, permitting a closed set of classes to inherit from it in the same ways as Foo does.
  • Class C could be defined as non-sealed, which reverts it to be open and allows any class to inherit from it. As non-sealed contains a hyphen, it has been made a reserved word in Java (the first with a hyphen).  Variable names may not include a hyphen, so there is no impact on backwards compatibility.
public non-sealed class C { ... }

The Java reflection system has been updated to include sealed classes.  The java.lang.Class class has two new methods, isSealed() and permittedSubclasses (which returns an array of ClassDesc objects).

Although the JEP title is Sealed classes, this feature can be used for the inheritance of interfaces as well.

Sealed classes are part of Project Amber, the goal of which is to explore and incubate smaller, productivity-oriented Java language features.  They will also help another feature under consideration for inclusion in Java: pattern matching for switch.  With sealed classes, it will be possible to eliminate the need for a default case and enable the compiler to perform more extensive checks.

Records

Records were introduced as a preview feature in JDK 14 (JEP 359).  In JDK 15, Records are now in a second preview (JEP 384).  A variety of changes have been included based on user feedback.

One of the clarifications in JDK 15 is, “The implicitly declared fields corresponding to the record components of a record class are final and moreover are not modifiable via reflection (doing so will throw IllegalAccessException).”  Java finally has (if you’ll excuse the pun) truly final fields.

Native methods have now been explicitly prohibited from records since they could introduce behaviour that would be dependent on external state.

Records, since they can implement interfaces, will work with sealed types.  For example:

public sealed interface Car permits RedCar, BlueCar { ... }

public record RedCar(int w) implements Car { ... }

public record BlueCar(long w, int c) implements Car { ... }

The new JEP also introduces the concept of local records.  These are similar in idea to local classes enabling a record to be defined in a method and have its scope of use limited to that method.  All local records are implicitly static.  The rationale for that is that it prevents the methods of the record from accessing any of the state from the enclosing method.  This is different from local classes that are never static.  This may also lead to the inclusion of local enumerations and interfaces in the future.

How annotations are used for record components (the variables listed in the record definition) is described in more detail in this JEP.  A record component represents several different concrete artifacts, specifically an instance variable, a constructor parameter and an accessor method with a return value of that type.  If an annotation is applied to the record component, it will also be used in any of the concrete artifacts where applicable.  There is no way to restrict its use to a subset of where it can be used.

Records are a powerful new feature for the Java language; there may well be some more minor changes before they become part of the standard.

Class Libraries

In total, there are 82 new API elements in JDK 15.  For the purposes of the title of this post, I do not count the addition of constant values (e.g. RELEASE_15 in SourceVersion), so there are 52 new classes and methods.

Twenty-five of these are in the java.security.spec and java.security.interfaces packages and are part of JEP 339: Edwards-Curve Digital Signature Algorithm (see the Other section below).  There are four new classes, EdDSAParameterSpec, EdDSAPoint, EdDSAPrivateKeySpec and EdDSAPublicKeySpec.

Representing numbers in computers is often complex (again, if you’ll excuse the pun).  There are now two new methods in the java.lang.Math and java.lang.StrictMath classes, absExact(), one that takes an int, the other a long.  The only difference between these and the existing abs() methods is they will throw an ArithmeticException if the value is Integer.MIN_VALUE (or Long.MIN_VALUE) since these are both one too big to convert to a positive representation.

JDK 11 included JEP 309: Dynamic Class-file Constants and JDK 12 added JEP 334: JVM Constants API.  As part of this, the java.lang.constant.Constable interface was introduced.  In JDK 15, Boolean, Byte, Character and Short now implement this interface and include a describeConstable() method.  It’s not clear to me why these were left out of the original types, like Integer, Float and Long that had this since JDK 12.  The ConstantBoostraps class (also part of this feature) now has a explicitCast() method.

CharSequence has a new method, isEmpty().

The Class class includes three new methods in support of sealed and hidden classes: isHidden(), isSealed() and permittedSubclasses().  The last method is also added to the javax.lang.model.element.TypeElement class.

Further API support for hidden classes is included through the new defineHiddenClass() method on thejava.lang.invoke.MethodHandle.Lookup class and a new inner class, Lookup.ClassOption.

NullPointerException adds the fillInStackTrace() method, which overrides the one from the Throwable class.

The java.lang.reflect.AnnotationType class has three new methods: getAnnotation(), getAnnotations() and getDeclaredAnnotations().

There are a few changes in the java.nio packages.

  • CharBuffer now has an isEmpty() method
  • ServerSocketChannel and SocketChannel both now provide an open() method that takes a ProtocolFamily as a parameter.

java.text.DecimalFormatSymbols has two new methods supporting a monetary grouping separator (e.g. a comma): getMonetaryGroupingSeparator() and setMonetaryGroupingSeparator().

Although not a new API, JEP 373 provides a reimplementation of the legacy DatagramSocket API.  This uses simpler and more modern implementations that are easy to maintain and debug. The new code will be easy to adapt to work with virtual threads, currently being explored in Project Loom.  For developers, no code changes or recompilation is required.  Since a few edge cases exist that will not work in the same way with the new version

Java Virtual Machine Features

Hidden Classes

Unlike sealed classes, hidden classes (JEP 371) are a JVM rather than a language-level feature.  One of the specific goals of this feature is not to make any changes to the Java language.

Languages compiled into bytecodes, including Java, often use bytecodes that are generated at runtime, rather than fixing the implementation at compile time.  An example of this is Lambda expressions, introduced in JDK 8.  These represent the implementation of the single abstract method of a functional interface.  Rather than restricting the compilation strategy to the simple use of an equivalent anonymous inner class, the compiler uses the invokedynamic bytecode, added to the JVM in JDK 7.  At runtime, the JVM will generate a dynamic class that implements the functional interface and delivers the concrete implementation of the abstract method.

These dynamically created classes are mostly intended to be bound to the static class in which they are used.  Although Lambda expressions can be assigned to a variable and reused, a very common use-case is within a stream.  There is no need for the generated bytecode to be exposed outside the class where it is used.  In this way, a dynamically generated class has three properties which are not possible to reflect in the existing class design accurately:

  • Non-discoverability: Such classes do not need to be identified by a name so that they can be located by other classes.
  • Access Control: The access control of the static class may need to be extended to include the dynamically generated one.
  • Lifecycle: These dynamically generated classes may only be required for a short period compared to the lifetime of the static class that uses it. Memory usage can be reduced by decoupling the lifecycle of dynamic classes from the static class.

Some workarounds, such as per-class class loaders, have been used in the past, but these are not ideal.

The existing Lookup API is extended to support hidden classes that can only be accessed by reflection. A hidden class is not discoverable by the JVM during bytecode linkage, nor by programs making explicit use of class loaders.  Hidden classes can be unloaded independently of the class that uses them.  This allows them to be garbage collected in the usual way since there is no longer any references to them.

Other Java 15 Features

JEP 339: Edwards-Curve Digital Signature Algorithm (EdDSA).  This is a new algorithm for implementing digital signatures. It is intended to provide additional security and better performance for digital signatures.

JEP 378: Text Blocks.  This feature is no longer in preview and is now included in the Java SE specification as part of the Java Language Specification.  These changes are primarily in Chapter 3, Lexical Structure.

JEP 372: Remove the Nashorn Scripting Engine.  This was deprecated for removal in JDK 11 and is now no longer part of OpenJDK.

JEP 374: Disable and deprecate biased locking.  Biased locking uses complex code that is costly to maintain. The original performance benefits were most apparent to legacy code that used old-style collection classes that synchronize on every access.  Removal of this feature will reduce the maintenance burden on the OpenJDK team.

JEP 381: Removal of the Solaris and SPARC ports of OpenJDK. New features being developed, like Valhalla and Loom, require complex processor-specific code.  Removing these ports will make implementation and maintenance of these features simpler.

JEP 375: Pattern matching for instanceof.  No changes to JDK 14; this is an extension to the time available to gather feedback. Technically, then, this does not count as a change.

JEP 377: ZGC: A Scalable Low-Latency Garbage Collector.  This collector was introduced in JDK 11 as an experimental feature (requiring the –XX:+UnlockExperimentalVMOptions command line flag to activate).  In JDK 15, it is now considered ready to be made a production feature.

JEP 379: Shenandoah: A Low-Pause-Time Garbage Collector.  The same as for ZGC, except it was initially introduced in JDK 12.

(Incidentally, if you are looking for a low-latency garbage collector, why not have a look at Zing, which also includes the Falcon replacement for the C2 JIT compiler and ReadyNow technology for warmup time reduction).

JEP 385: Deprecate RMI activation for removal.  This is an obsolete part of remote method invocation (RMI) that has been optional since JDK 8.

Again, for the purposes of the title of this post, I do not count things that are deprecated or removed or JEP 375, which contains no changes.

Conclusions

Although the number of new features and APIs in JDK 15 is relatively small compared to other releases, there are several interesting and useful enhancements to the Java platform.  This release continues to show that the six-month release cadence works well and will ensure that Java remains the most popular programming platform on the planet.