Mohamed Taman

Chief Solutions Architect

Java Champion | CEO @SiriusXI | Oracle ACE Alumni | Jakarta EE Ambassador | Author | Trainer | Speaker.

Java 20 Hidden Gems

Highlights the preview and incubator for pattern matching for switch expressions, record patterns, virtual threads, structured concurrency, Scoped-Value, and the Vector API. Plus a selection of small enhancements, performance, security, and stability updates.

JDK is more than targeted JEPs.

@_tamanm

|

|

|

Publications

@_tamanm

|

|

|

You can catch me (🤝)

Here 👇

Or by googling 🕵 me

"Mohamed Taman"

@_tamanm

|

|

|

1

Java History.

2

Checkout Project Amber features.

3

Checkout Project Loom features.

5

Explore the new, improved Language APIs and others.

4

Checkout Project Panama features.

6

Farewell to APIs has gone forever.

Agenda

@_tamanm

|

|

|

1

Introduction

Java History  🥷

@_tamanm

|

|

|

@_tamanm

|

|

|

2

Checkout Project Amber features.

@_tamanm

|

|

|

@_tamanm

|

|

|

JEP 432: Record Patterns

1st Prev JDK 19

Object object = new Point(1, 2);

if (object instanceof Point(int x, int y)) {
    out.println("Object is a Point(" + x + 
    			           "," + y +")");
}

mohamed_taman -- -bash

Record patterns were first introduced in Java 19. A record pattern can be used with instanceof or switch to access the fields of a record without casting and calling accessor methods.

> Assume you have defined the following Point record:

record Point(int x, int y) { }

> With the record pattern, you can write an        instanceof as follows:

2nd Prev JDK 20

switch (object) {
  case Point(int i, int j) -> 
       out.println("Object is a Point(" + i + "," + j+")");

  // other cases ...
  default -> throw new IllegalStateException("Unexpected value: " + object);
 }

mohamed_taman -- -bash

> With the record pattern, you can write an        switch expression as follows:

@_tamanm

|

|

|

JEP 432: Record pattern in (Java 20)

interface Box<T> { }
record RoundBox<T>(T t1, T t2) implements Box<T> { }
record TriangleBox<T>(T t1, T t2, T t3) implements Box<T> { }

mohamed_taman -- -bash

What is new in Java 20 for record patterns? There are three changes:

> Consider a generic interface, Box<T>, and two implementing records, RoundBox<T> and TriangleBox<T>, which contain two and three values of type T, respectively:

Continue

  • Inference of type arguments for generic record patterns
  • Record patterns in for loops
  • Removal of support for named record patterns

@_tamanm

|

|

|

JEP 432: Inference of type arguments for generic record patterns

Box<String> box = new RoundBox<>("Hello", "World");
printBoxInfo(box);

public static <T> void printBoxInfo(Box<T> box) {
    if (box instanceof RoundBox<T>(var t1, var t2)) {
        System.out.println("RoundBox: " + t1 + ", " + t2);
    } else if (box instanceof TriangleBox<T>(var t1, var t2, var t3)) {
        System.out.println("TriangleBox: " + t1 + ", " + t2 + ", " + t3);
    }
}

mohamed_taman -- -bash

You can check which concrete implementation a given Box<T> object is by using the following printBoxInfo() method:

> With Java 20, the compiler can infer the T type, so you may omit it from the instanceof checks and the method signature as well:

public static void printBoxInfo(Box box) {
    if (box instanceof RoundBox(var t1, var t2)) {
        System.out.println("RoundBox: " + t1 + ", " + t2);
    } else if (box instanceof TriangleBox(var t1, var t2, var t3)) {
        System.out.println("TriangleBox: " + t1 + ", " + t2 + ", " + t3);
    }
}

mohamed_taman -- -bash

Continue

Also, with Java 20, the parameter type can be omitted from switch statements. You will see this shortly in the discussion of switch pattern enhancements.

@_tamanm

|

|

|

JEP 432: Record Patterns in - for loops

List<Point> points = ...

// Old way Java 19
for (Point p : points) {
  System.out.printf("(%d, %d)%n", p.i(), p.j());
}


// New way Java 20
for (Point(int i, int j) : points) {
  System.out.printf("(%d, %d)%n", i, j);
}

mohamed_taman -- -bash

Starting with Java 20, you can also specify a record pattern in the for loop and then access x and y directly (the same as you can with instanceof and switch)

> Here’s another example if you have a list of points of type record() and you want to do some             operations on them, such as to print them to the console, you can do that as follows:

Continue

@_tamanm

|

|

|

JEP 432: Removal of support for named record patterns

Object object = new Point(1, 2);

// 1. Pattern matching for instanceof
if (object instanceof Point p) {
    System.out.println("object is a Point, p.i = " + p.i() + ", p.j = " + p.j());
}

// 2. Record pattern
if (object instanceof Point(int i, int j)) {
    System.out.println("object is a Point, i = " + i + ", j = " + j);
}

// 3. Named record pattern
if (object instanceof Point(int i, int j) p) {
    System.out.println("object is a Point, p.i = " + p.i() + ", p.j = " + p.j() +
                                                 ", i = " + i + ", j = " + j);
}

mohamed_taman -- -bash

In Java 19, there were three ways to perform pattern matching on a record, as follows:

In the third variant, a named record pattern, there are two ways to access the fields of the record: via p.i() and p.j() or via the i and j variables. This variant was determined to be superfluous and was removed in Java 20.

Continue

1st Prev JDK 17

3rd Prev JDK 19

2nd Prev JDK 18

This is a feature that has already gone through three rounds of previews.

switch (obj) {
  case String s when
              s.length() > 5 -> System.out.println(s.toUpperCase());
  case String s              -> System.out.println(s.toLowerCase());
  case Integer i             -> System.out.println(i * i);
  case Point(int i, int j)   -> System.out.println("i= " + i + ", j= " + j);
  default                    -> throw new 
				IllegalStateException("Unexpected: " + object);
}

mohamed_taman -- -bash

JEP 433: Pattern Matching for switch

First appearing in Java 17, and until Java 19 pattern matching for switch allows you to write code like the following:

@_tamanm

|

|

|

3rd Prev JDK 19

4th Prev JDK 20

Two enhancements were added in Java 20 with the fourth preview of JEP 433:

record Point(int i, int j) {}
sealed interface Shape permits Rectangle, Circle {}
record Rectangle(Point topLeft, Point bottomRight) implements Shape {}
record Circle(Point center, int radius) implements Shape {}

mohamed_taman -- -bash

JEP 433: Pattern Matching for switch

> Consider the following sealed interface called Shape with the implementations Rectangle     and Circle of type record:

@_tamanm

|

|

|

Continue

  • MatchException for exhaustive switch (rather than an IncompatibleClassChangeError)

  • Inference of type arguments for generic record patterns in switch expressions and statements.

Throw MatchException for exhaustive switch (rather than an IncompatibleClassChangeError)

JEP 433: MatchException for exhaustive switch

@_tamanm

|

|

|

Continue

public void printShapeInfo(Shape shape) {
  switch (shape) {
    case Circle(var center, var radius) ->
         System.out.println("Circle: Center = " + center + 
    			            ", Radius = " + radius);
	
    case Rectangle(var topLeft, var bottomRight) ->
         System.out.println("Rectangle: Top left = " + topLeft +
  			                ", Bottom right = " + bottomRight);
  }
}

mohamed_taman -- -bash

jshell> var circle = new Circle(new Point(10, 10), 50)
circle ==> Circle[center=Point[i=10, j=10], radius=50]

jshell> printShapeInfo(circle);
Circle: Center = Point[i=10, j=10], Radius = 50

mohamed_taman -- -bash

jshell> sealed interface Shape permits Rectangle, Circle, Oval {}
|  modified interface Shape

jshell> record Oval(Point center, int width, int height) implements Shape {}
|  created record Oval

mohamed_taman -- -bash

>  If you do the following in an IDE or jshell, Java will immediately warn that the switch statement in the                    printShapeInfo() method does not cover all possible values:

@_tamanm

|

|

|

Continue

jshell> var oval = new Oval(new Point(60, 60), 20, 10);
oval ==> Oval[center=Point[i=60, j=60], width=20, height=10]

jshell> printShapeInfo(oval)
|  attempted to call method printShapeInfo(Shape), which cannot be invoked until this error is corrected:
|      the switch statement does not cover all possible input values
|              switch (shape) {
|              ^---------------...

mohamed_taman -- -bash

$ javac --enable-preview -source 20 Shape.java Oval.java App.java
$ java --enable-preview Main

Exception in thread "main" java.lang.MatchException
        at App.main(App.java:56)

mohamed_taman -- -bash

>  What if you have a big program and each class is in a separate source file—and you       recompile only the changed classes and then start execution?

JEP 433: MatchException for exhaustive switch

JEP 433: Inference of type arguments for generic record patterns in switch expressions and statements.

@_tamanm

|

|

|

Continue

Box<String> box = ...

switch(box) {
  case RoundBox<String>(var t1, var t2) ->
       System.out.println("RoundBox: " + t1 + ", " + t2);

  case TriangleBox<String>(var t1, var t2, var t3) ->
       System.out.println("TriangleBox: " + t1 + ", " + t2 + ", " + t3);
  ...
}

mohamed_taman -- -bash

switch(box) {
  case RoundBox(var t1, var t2) ->
       System.out.println("RoundBox: " + t1 + ", " + t2);

  case TriangleBox(var t1, var t2, var t3) ->
       System.out.println("TriangleBox: " + t1 + ", " + t2 + ", " + t3);
  ...
}

mohamed_taman -- -bash

>  Starting with Java 20, you may omit the <String> type argument inside the switch      statement or expression:

3

Checkout Project Loom features.

@_tamanm

|

|

|

@_tamanm

|

|

|

JEP 429: Scoped Values

1st Inc. JDK 20

  • Introduce Scoped Values, which enable the sharing of immutable data within and across threads. They are preferred to thread-local variables, especially when using large numbers of virtual threads.
  • Scoped Values is an incubating API.

The goals of this proposal include:

  • Robustness — Ensure that data shared by a caller can be retrieved only by legitimate callees.

  • Comprehensibility — Make the shared data lifetime visible from the syntactic structure of code.

  • Ease of use — Provide a programming model to share data both within a thread and with child threads, to simplify reasoning about data flow.
  • To change the Java programming language.

  • Performance — Treat shared data as immutable to allow sharing by a large number of threads, and to enable runtime optimizations.

  • To require migration away from thread-local variables, or to deprecate the existing ThreadLocal API.

It is not a goal:

@_tamanm

|

|

|

Consider the following example of a web framework that uses the traditional ThreadLocal class.

Continue

class Server {

    final static ThreadLocal<User> CURRENT_USER = new ThreadLocal<>(); // (1)

    void serve(Request request, Response response) {
        var level     = (request.isAuthorized() ? ADMIN : GUEST);
        var user = new User(level);
        CURRENT_USER.set(user);                                        // (2)
        Application.handle(request, response);
    }
}

class DatabaseManager {
    DBConnection open() {
        var user = Server.CURRENT_USER.get();                          // (3)
        if (!user.canOpen()) throw new InvalidUserException();
        return new DBConnection(...);                                  // (4)
    }
}

mohamed_taman -- -bash

JEP 429: Scoped Values

@_tamanm

|

|

|

Here’s how to migrate the previous code from using ThreadLocal to the new, lightweight ScopedValue.

Continue

class Server {
    final static ScopedValue<User> CURRENT_USER = new ScopedValue<>();      // (1)

    void serve(Request request, Response response) {
        var level = (request. isAuthorized()? ADMIN : GUEST);
        var user  = new User(level);
      
        ScopedValue.where(CURRENT_USER, user)                               // (2)
                   .run(() -> Application.handle(request, response));       // (3)
    }
}

class DatabaseManager {
    DBConnection open() {
        var user = Server.CURRENT_USER.get();                               // (4)
        if (!user.canOpen()) throw new InvalidUserException();
        return new DBConnection(...);
    }
}

mohamed_taman -- -bash

JEP 429: Scoped Values

Together, where() and run() provide one-way data sharing from the server component to the data access component.

@_tamanm

|

|

|

JEP 436: Virtual Threads

1st Prev JDK 19

  • Introduce virtual threads to the Java Platform. Virtual threads are lightweight threads that dramatically reduce the effort of writing, maintaining, and observing high-throughput concurrent applications.
  • Virtual threads is a 2nd preview API, to allow more time to collect feedback.

Changes in this release:

  • A change to the ExecutorService, which now extends the AutoCloseable interface.

  • New methods in the Future interface: resultNow(), exceptionNow(), state().

  • Two new overloaded methods in the Thread class: join(Duration) and sleep(Duration).
  • Preparation for the removal of several ThreadGroup methods. Their implementations have been changed to do nothing.

2nd Prev JDK 20

@_tamanm

|

|

|

JEP 437: Structured Concurrency

1st Inc. JDK 19

  • Simplify multithreaded programming by introducing an API for structured concurrency
  • Structured Concurrency is a 2nd Incubating API, to allow time for further feedback collection.
  • The only change is an updated StructuredTaskScope class to support the inheritance of scoped values by threads created in a task scope. This streamlines the sharing of immutable data across all child threads.

  • Structured Concurrency treats multiple tasks running in different threads as a single unit of work, thereby streamlining error handling and cancellation, improving reliability, and enhancing observability.

2nd Inc. JDK 20

4

Checkout Project Panama features.

@_tamanm

|

|

|

@_tamanm

|

|

|

JEP 434: Foreign Function & Memory API

  • The Foreign Function and Memory API enables access to native memory (that is, memory outside the Java heap) and access to native code (usually C libraries) directly from Java.
  • With JEP 434, quite a few changes were made to the API.

Some changes in this release:

  • The MemorySegment and MemoryAddress abstractions are unified (memory addresses are now modeled by zero-length memory segments);

1st Inc. JDK 17

2nd Inc. JDK 18

1st Prev. JDK 19

2nd Prev. JDK 20

  • MemorySession has been split into Arena and SegmentScope to facilitate sharing segments across maintenance boundaries.
  • The sealed MemoryLayout hierarchy is enhanced to facilitate usage with pattern matching in switch expressions and statements (JEP 433), and

@_tamanm

|

|

|

The following examples store a string in off-heap memory, followed by a call to the C standard library’s strlen function to return the String length:

Continue

public class ForeignFunctionAndMemoryTest {
  public static void main(String[] args) throws Throwable {
    // 1. Get a lookup object for commonly used libraries
    SymbolLookup stdlib = Linker.nativeLinker().defaultLookup();

    // 2. Get a handle on the strlen function in the C standard library
    MethodHandle strlen = Linker.nativeLinker().downcallHandle(
        stdlib.lookup("strlen").orElseThrow(), 
        FunctionDescriptor.of(JAVA_LONG, ADDRESS));

    // 3. Convert Java String to C string and store it in off-heap memory
    MemorySegment str = implicitAllocator()
                          .allocateUtf8String("Happy Java Conference!");

    // 4. Invoke the foreign function
    long len = (long) strlen.invoke(str);

    System.out.println("len = " + len);
  }
}

mohamed_taman -- -bash

JEP 434: Foreign Function & Memory API

@_tamanm

|

|

|

The previous example with new changes:

Continue

public long getStringLength(String content) throws Throwable {
    
    // 1. Get a lookup object for commonly used libraries
    SymbolLookup stdlib = Linker.nativeLinker().defaultLookup();

    // 2. Get a handle on the strlen function in the C standard library
    MethodHandle strlen = Linker.nativeLinker().downcallHandle(
            stdlib.find("strlen").orElseThrow(),
            FunctionDescriptor.of(JAVA_LONG, ADDRESS));
  
    long len = 0;

    // 3. Convert Java String to C string and store it in off-heap memory
    try (Arena offHeap = Arena.openConfined()) {
        MemorySegment str = offHeap.allocateUtf8String(content);
      
        // 4. Invoke the foreign function
        len = (long) strlen.invoke(str);
    }
    // 5. Off-heap memory is deallocated at the end of try-with-resources
    // 6. Return the length.
    return len;
}

mohamed_taman -- -bash

JEP 434: Foreign Function & Memory API

@_tamanm

|

|

|

JEP 438: Vector API

3rd Inc. JDK 18

  • The Vector API defines vector computations that successfully compile at runtime to optimal vector instructions on supported CPU architectures, outperforming equivalent scalar computations.
  • Vector API is in a fifth incubation.

The changes in this release:

  • This JEP also clarifies that alignment with Project Valhalla is a critical part of completing the Vector API.

  • The implementation includes small set of bug fixes and performance enhancements.

  • This JEP proposes to re-incubate the API in JDK 20, with no changes in the API relative to JDK 19.

4th Inc. JDK 19

1st Inc. JDK 16

2nd Inc. JDK 17

5th Inc. JDK 20

5

Explore the new, improved language APIs and others.

@_tamanm

|

|

|

@_tamanm

|

|

|

Javac Warns about Type Casts in Compound Assignments

What will happen to the following at compile time?

Since JDK 20

short a = 30_000;
int b   = 45_000;

a += b;
System.out.println("a = "+ a);
a = a + b;
System.out.println(a);
a = 9464

LossyConversions.java:6: 
   error: incompatible types: 
   possible lossy conversion from int to short
		   a = a + b;
		         ^

Java 20 introduced a new compiler (javac) lint option called lossy-conversions that generates warnings about type casts in compound assignments (such as +=, *=, etc.) that could result in data loss as the following:

[mtaman]:~ javac -Xlint:lossy-conversions LossyConversions.java

LossyConversions.java:4: warning: [lossy-conversions] 
        implicit cast from int to short in compound assignment is possibly lossy
		a += b;
		     ^
1 warning

@_tamanm

|

|

|

New 'jmod --compress' Command Line Option

The jmod tool in Java SE allows developers to create JMOD archives, a new type of archive introduced in Java 9 that provides a modular way to package Java runtime components and their dependencies.

Since JDK 20

With Java 20, a new command line option --compress has been added to the jmod tool. This option specifies the compression level to use while creating the JMOD archive as the following:

  • The default value is zip-6.

  • The accepted values for the --compress option is zip-[0-9], where zip-0 indicates no compression and zip-9 provides the best compression.

  • By default, the jmod tool compresses the contents of the JMOD archive using the ZIP format.

@_tamanm

|

|

|

Grapheme Support in BreakIterator

>  Whereas the new implementation, the string will be broken into two graphemes:

"🇺🇸", "👨‍👩‍👧‍👦"

Java's java.text.BreakIterator follows the Extended Grapheme Clusters breaks defined in the Unicode Consortium's Standard Annex #29 for character boundary analysis.

This update may result in deliberate behavioral changes because the prior implementation primarily split at code point boundaries for most characters. For example, consider this string that includes the US flag and a grapheme for a 4-member family "🇺🇸👨‍👩‍👧‍👦":

>  With the old implementation, the previous 4-member family String will break at the code       point boundaries:

"🇺", "🇸", "👨", "(zwj)", "👩", "(zwj)", "👧", "(zwj)"‍, "👦"

Since JDK 20

Where (zwj) denotes ZERO WIDTH JOINER (U 200D).

@_tamanm

|

|

|

Java Documentation: Improved Preview API Page

The JavaDoc documentation now includes detailed information about the JEPs (Java Enhancement Proposals) related to the preview features on the Preview API page.

Since JDK 20

This gives developers a better understanding of the origin and purpose of these preview features.

Java 19 Documentation Preview List

Enhanced Java 20 documentation Preview List

@_tamanm

|

|

|

Throw MatchException for Exhaustive switch over enum Class without a label

This release has a change related to the switch expressions over an enum type.

Since JDK 20

This situation can only occur if a new enum constant is added to the enum class after the switch has been compiled. This change is necessary to treat exhaustive erroneous switches consistent with introducing pattern labels.

When you enable preview features with "--enable-preview", if the selector expression produces an unexpected enum constant value, a MatchException will be thrown instead of an IncompatibleClassChangeError.

6

Farewell to APIs has gone forever.

@_tamanm

|

|

|

@_tamanm

|

|

|

IdentityHashMap's Remove and Replace Methods Use Object Identity

However, when the default remove(Object key, Object value) and replace(K key, V oldValue, V newValue) methods were added to the Map interface in Java 8, they were not overridden in IdentityHashMap to use == instead of equals(), leading to a bug.

IdentityHashMap is a unique type of map that considers keys to be equal only if they are identical. The comparison using the == operator returns true rather than the equals() method.

This bug has been corrected after over eight years, which may suggest that IdentityHashMap is not widely used and may contain other bugs.

Since JDK 20

@_tamanm

|

|

|

Thread.suspend/resume/stop Changed to Throw UnsupportedOperationException

JDK 20 removes the ability to suspend or resume a thread using the Thread.suspend() and Thread.resume() methods. Those methods have been deprecated since JDK 1.2 and are changed to throw UnsupportedOperationException. These methods were prone to deadlock.

Since JDK 20

As part of this change, java.lang.ThreadDeath has been deprecated for removal.

Instead of using these methods, the corresponding methods in ThreadGroup should be used. However, in Java 19, these methods were also changed to throw UnsupportedOperationException.

Similarly, the ability to stop a thread using the Thread.stop() method has been removed due to its unsafe nature of causing java.lang.ThreadDeath exception.

@_tamanm

|

|

|

java.net.URL Constructors Are Deprecated

In this release, the java.net.URL constructors are deprecated. Instead, developers are encouraged to use java.net.URI for parsing or constructing URL.

Since JDK 20

Suppose an instance of java.net.URL is still needed to open a connection. In that case, java.net.URI can construct or parse the URL string by calling URI::create() and then calling URI::toURL() to create the URL instance:

> Old code :

URL url = new URL("https://blogs.oracle.com/authors/mohamed-taman");

> New code :

URL url = URI.create("https://blogs.oracle.com/authors/mohamed-taman").toURL();

@_tamanm

|

|

|

Security Related Deprecations

To enhance security, the TLS_ECDH_* cipher suites have been disabled by default. This has been done by adding "ECDH" to the jdk.tls.disabledAlgorithms security property in the java.security configuration file. These cipher suites lack forward-secrecy and are hardly ever used in practice.

Since JDK 20

Disabled TLS_ECDH_* Cipher Suites

Disabled DTLS 1.0

Throw error if default java.security file fails to load

the DTLS 1.0 protocol has been disabled by default due to its weakened security and lack of support for stronger cipher suites. To achieve this, the "DTLSv1.0" has been added to the jdk.tls.disabledAlgorithms security property in the java.security configuration file. If any attempt is made to use DTLSv1.0, it will result in an SSLHandshakeException.

Previously, when the default security configuration file conf/security/java.security failed to load, the JDK would fall back to using a hardcoded default configuration. However, in JDK 20, if the default configuration file fails to load, the JDK throws an InternalError instead of the hardcoded default configuration.

Questions?

Thanks for listening! 🧡

@_tamanm

|

|

|

The Hidden Gems of Java 20

By Mohamed Taman

The Hidden Gems of Java 20

Java is innovative with each release, and to become a creative engineer for your company, you should know what is new in your language. This session will sweep the dust off Java SE's 20 hidden gems, including new cool language features, compiler changes, library additions, and critical bug fixes. They're handy in your day-to-day work.

  • 355