Mohamed Taman

Chief Solutions Architect

CEO @SiriusXI | Java Champion | Oracle ACE Alumni | Jakarta EE Ambassador | Author | trainer | speaker.

The Hidden Gems of Java 19

Highlights the previews and incubators for pattern matching for switch expressions, record expressions, virtual threads, structured concurrency, and the Vector API. And a selection of small enhancements, plus thousands of 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

Introduction.

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

|

|

|

1st Prev JDK 17

3rd Prev JDK 19

2nd Prev JDK 18

Pattern matching for the switch. This is a feature that has already gone through two rounds of previews.

switch (obj) {
  case String s && s.length() > 8 -> System.out.println(s.toUpperCase());
  case String s                   -> System.out.println(s.toLowerCase());

  case Integer i                  -> System.out.println(i * i);

  default -> {}
}

mohamed_taman -- -bash

JEP 427: Pattern Matching for switch

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

@_tamanm

|

|

|

  • To improve the readability of this feature, Java 19 changed the syntax.

  • In Java 17 and Java 18, the syntax was to write String s && s.length() > 0; now, in Java 19, instead of &&, you must use the easier-to-read keyword when.

switch (obj) {
  case String s when s.length() > 8 -> System.out.println(s.toUpperCase());
  case String s                     -> System.out.println(s.toLowerCase());

  case Integer i                    -> System.out.println(i * i);

  default -> {}
}

mohamed_taman -- -bash

JEP 427: Pattern Matching for switch

Therefore, the previous example would be written in Java 19 as the following:

@_tamanm

|

|

|

What’s also new is that the keyword when is a contextual keyword; therefore, it has a meaning only within a case label.

Continue

@_tamanm

|

|

|

JEP 405: Record Patterns

1st Prev JDK 19

private void print(Object object) {
  
  if (object instanceof Point point) {
    System.out.println("object is a point, x = " + point.x() 
                                      + ", y = " + point.y());
  }
  // else ...
}

mohamed_taman -- -bash

Record and type patterns can be nested to allow for robust, declarative, and modular data processing.

> Assume you have defined the following Point record:

record Point(int x, int y) { }

> You also have a print() method that can print any object, including positions:

You might have seen this notation before; it was introduced in Java 16 as pattern matching for instanceof.

@_tamanm

|

|

|

JEP 405: Record pattern for instanceof

private void print(Object object) {
  if (object instanceof Point(int x, int y)) {
    System.out.println("object is a point, x = " + x + 
                                        ", y = " + y);
  } 
  // else ...
}

mohamed_taman -- -bash

As of Java 19, JEP 405 allows you to use a new feature called a record pattern

> This new addition allows you to write the previous code as follows:

Now can match on Point(int x, int y) and can then access their x and y fields directly.

Continue

@_tamanm

|

|

|

JEP 405: Record pattern with switch

private void print(Object object) {
  switch (object) {
    case Point point
        -> System.out.println("object is a point, x = " + point.x() 
                                             + ", y = " + point.y());
    // other cases ...
  }
}

mohamed_taman -- -bash

Previously with Java 17, you could also write the original example as a switch statement.

> You can now also use a record pattern in the switch statement:

private void print(Object object) {
  switch (object) {
    case Point(int x, int y) 
        -> System.out.println("object is a point, x = " + x + 
                                               ", y = " + y);
    // other cases ...
  }
}

mohamed_taman -- -bash

Continue

@_tamanm

|

|

|

JEP 405: Nested record patterns - instanceof

private void print(Object object) {
  if (object instanceof Line(Point(int x1, int y1), Point(int x2, int y2))) {
      
      System.out.println("object is a Line, x1 = " + x1 + ", y1 = " + y1 
                                       + ", x2 = " + x2 + ", y2 = " + y2);
  }
  // else ...
}

mohamed_taman -- -bash

It is now possible to match nested records with pattern matching for instanceof.

> Here’s another example that defines a second record, Line, with a start Point and a destination point, as follows:

public record Line(Point from, Point to) {}

> The print() method can now use a record pattern to print all the path’s x and y coordinates easily:

Continue

@_tamanm

|

|

|

JEP 405: Nested record patterns - switch

private void print(Object object) {
  switch (object) {
    case Line(Point(int x1, int y1), Point(int x2, int y2))
        -> System.out.println("object is a Line, x1 = " + x1 + ", y1 = " + y1 
                                            + ", x2 = " + x2 + ", y2 = " + y2);
    // other cases ...
  }
}

mohamed_taman -- -bash

> The print() method can be written as a switch statement:

It is now possible to match nested records with pattern matching with switch.

Thus, record patterns provide an elegant way to access a record’s elements after a type check.

Continue

3

Checkout Project Loom features.

@_tamanm

|

|

|

@_tamanm

|

|

|

JEP 425: Virtual Threads (Preview)

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 preview API.

The goals of this proposal include:

  • Enable easy troubleshooting, debugging, and profiling of virtual threads with existing JDK tools.

  • Enable existing code that uses the java.lang.Thread API to adopt virtual threads with minimal change.

  • Enable server applications written in the simple thread-per-request style to scale with near-optimal hardware utilization.
  • No intention to change the basic Java concurrency model.

  • No intention to remove the traditional threads implementation, or to silently migrate existing applications to use virtual threads.

@_tamanm

|

|

|

JEP 425: Virtual Threads (History)

One solution has been to use the reactive programming model with frameworks such as Project Reactor and RxJava.

Continue

public DeferredResult<ResponseEntity<?>> createOrder(
    CreateOrderRequest createOrderRequest, Long sessionId, HttpServletRequest context) {
  
  DeferredResult<ResponseEntity<?>> deferredResult = new DeferredResult<>();

  Observable.just(createOrderRequest)
      .doOnNext(this::validateRequest)
      .flatMap(
          request ->
              sessionService
                  .getSessionContainer(request.getClientId(), sessionId)
                  .toObservable()
                  .map(ResponseEntity::getBody))
      .map(
          sessionContainer ->
              enrichCreateOrderRequest(createOrderRequest, sessionContainer, context))
      .flatMap(
          enrichedRequest ->
              orderPersistenceService.persistOrder(enrichedRequest).toObservable())
      .subscribeOn(Schedulers.io())
      .subscribe(
          success -> deferredResult.setResult(ResponseEntity.noContent()),
          error -> deferredResult.setErrorResult(error));

  return deferredResult;
}

mohamed_taman -- -bash

@_tamanm

|

|

|

JEP 425: Virtual Threads (History)

Virtual threads will allow you to reimplement the reactive example quite simply as the following:

Continue

public void createOrder(
    CreateOrderRequest createOrderRequest, Long sessionId, HttpServletRequest context) {
  
  validateRequest(createOrderRequest);

  SessionContainer sessionContainer =
      sessionService
          .getSessionContainer(createOrderRequest.getClientId(), sessionId)
          .execute()
          .getBody();

  EnrichedCreateOrderRequest enrichedCreateOrderRequest =
      enrichCreateOrderRequest(createOrderRequest, sessionContainer, context);

  orderPersistenceService.persistOrder(enrichedCreateOrderRequest);
}

mohamed_taman -- -bash

@_tamanm

|

|

|

JEP 428: Structured Concurrency (1st Incubator)

1st Inc. JDK 19

  • Simplify multithreaded programming by introducing an API for structured concurrency
  • Structured Concurrency is an Incubator API.

The goals of this proposal include:

  • Promote a style of concurrent programming which can eliminate common risks arising from cancellation and shutdown, such as thread leaks and cancellation delays.

  • Improve the maintainability, reliability, and observability of multithreaded code.
  • 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.

@_tamanm

|

|

|

JEP 428: Structured Concurrency (History)

You could use the Java executable framework, to write concurrent application as in the following:

Continue

private final ExecutorService executor = Executors.newCachedThreadPool();

public Invoice createInvoice(int orderId, int customerId, String language) 
    throws InterruptedException, ExecutionException {
  
    Future<Customer> customerFuture =
        executor.submit(() -> customerService.getCustomerBy(customerId));

    Future<Order> orderFuture =
        executor.submit(() -> orderService.getOrderBy(orderId));

    Future<String> invoiceTemplateFuture =
        executor.submit(() -> invoiceTemplateService.getTemplateFor(language));

    Customer customer = customerFuture.get();
    Order order = orderFuture.get();
    String template = invoiceTemplateFuture.get();

    return invoice.generate(customer, order, template);
}

mohamed_taman -- -bash

@_tamanm

|

|

|

JEP 428: Structured Concurrency (History)

  • How can you cancel other subtasks if an error occurs in one subtask?

As you have seen you can pass the three subtasks to the executor and wait for the partial results. It is easy to implement the basic task quickly, but consider these possible issues:

Continue

All these possible issues can be addressed, but the solution would require complex and difficult-to-maintain code.

  • How can you handle and recover from exceptions?
  • How can you cancel the subtasks if the invoice is no longer needed?
  • The new structured concurrency API improves the implementation, readability, and maintainability of code for requirements of this type.

The Solution:

@_tamanm

|

|

|

JEP 428: Structured Concurrency

Continue

Invoice createInvoice(int orderId, int customerId, String language)
    throws ExecutionException, InterruptedException {
  
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {

        Future<Customer> customerFuture = 
          scope.fork(() -> customerService.getCustomerBy(customerId));

        Future<Order> orderFuture = 
          scope.fork(() -> orderService.getOrderBy(orderId));

        Future<String> invoiceTemplateFuture = 
          scope.fork(() -> invoiceTemplateService.getTemplateFor(language));
        
        scope.join();              // Join all forks
        scope.throwIfFailed();     // ... and propagate errors

        Customer customer = customerFuture.resultNow();
        Order order = orderFuture.resultNow();
        String template = invoiceTemplateFuture.resultNow();

        // Here, both forks have succeeded, so compose their results
        return invoice.generate(customer, order, template);
    }
}

mohamed_taman -- -bash

Using the StructuredTaskScope class, you can rewrite the previous code as in the following:

4

Checkout Project Panama features.

@_tamanm

|

|

|

@_tamanm

|

|

|

JEP 424: Foreign Function & Memory API (Preview)

  • 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.
  • Java 19’s has promoted the new API from incubator to preview stage.

The goals of this proposal include:

  • Introduce a new API for easy access and handle memory segments and function calls.

1st Inc. JDK 17

2nd Inc. JDK 18

1st Prev. JDK 19

@_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 424: Foreign Function & Memory API (Preview)

@_tamanm

|

|

|

JEP 426: Vector API (Fourth Incubator)

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 fourth incubation.

The goals of this proposal include:

  • Along with the complementing vector mask compress operation, Java 19 adds the cross-lane vector operations of compress and expand.

  • The Foreign Function and Memory API preview defines improvements to loading and storing vectors to and from memory segments as part of the API proposed for JDK 19.

  • With the user model in the API, developers can use the HotSpot JVM’s autovectorizer to design sophisticated vector algorithms in Java that are more reliable and predictable.

4th Inc. JDK 19

1st Inc. JDK 16

2nd Inc. JDK 17

5

Explore the new, improved language APIs and others.

@_tamanm

|

|

|

@_tamanm

|

|

|

JEP 422: Linux/RISC-V port

  • RISC-V is a free, open source instruction set architecture that’s becoming increasingly popular, and now there’s a Linux JDK for that architecture with JEP 422. A wide range of language toolchains already supports this hardware instruction set.
  • Currently, the Linux/RISC-V port will support only one general-purpose 64-bit instruction set architecture with vector instructions: an RV64GV configuration of RISC-V. More may be supported in the future.

The HotSpot JVM subsystems that are supported with Java 19:

  • Template interpreter

  • C2 (server) JIT compiler

  • C1 (client) just-in-time (JIT) compiler

 JDK 19

  • All mainline garbage collectors, including ZGC and Shenandoah

@_tamanm

|

|

|

New system properties for System.out and System.err

> If that’s not what you want and you’d prefer to see output in UTF-8, add the following JVM options when calling the application:

If you run an existing application with Java 19, you may see ????? on the console instead of special characters. This is because, as of Java 19, the operating system’s default encoding is used for printing to System.out and System.err.

 JDK 19

-Dstdout.encoding=utf8 -Dstderr.encoding=utf8

> If you don’t want to do this each time the application launches, you can also define the following environment variable (it starts with an underscore) to set these parameters globally:

_JAVA_OPTIONS="-Dstdout.encoding=utf8 -Dstderr.encoding=utf8"

@_tamanm

|

|

|

New methods to create preallocated hash maps and hashsets

> To create a HashMap for 180 mappings:

Because the HashMap has a default load factor of 0.75 when it is initialized. This indicates that the HashMap gets rebuilt (also called rehashed) with double the size as soon as it is 75% filled. Thus, the new HashMap is initialized with a capacity of 180, can hold only 135 (180 × 0.75) mappings without being rehashed.

 JDK 19

// for 180 mappings: 180 / 0.75 = 240
Map<String, Integer> map = new HashMap<>(240);

> Java 19 makes it easier to create a HashMap that has the required mappings without fiddling with load factors by using the new static factory method newHashMap(int)

Map<String, Integer> map = HashMap.newHashMap(180);

> Similar labor-saving static factory methods have been created in Java 19. Here’s the complete set:

  • HashMap.newHashMap
  • LinkedHashMap.newLinkedHashMap
  • WeakHashMap.newWeakHashMap
  • HashSet.newHashSet
  • LinkedHashSet.newLinkedHashSet

@_tamanm

|

|

|

Additional date-time formats

> For example, the following creates a formatter that may format a date according to a locale, for example, “Feb 2022” in the US locale and “2022年2月” in the Japanese locale:

 JDK 19

DateTimeFormatter.ofLocalizedPattern("yMMM")
  • Java 19 brings new formats to the java.time.format.DateTimeFormatter and DateTimeFormatterBuilder classes.
  • In prior releases, only four predefined styles were available: FormatStyle.FULL, FormatStyle.LONG, FormatStyle.MEDIUM, and FormatStyle.SHORT.
  • Now you can specify a flexible style with the new DateTimeFormatter.ofLocalizedPattern(String requestedTemplate) method.

@_tamanm

|

|

|

Indify string concatenation changes to the order of operations (Bug Fix)

> For example, the following code now prints zoozoobar not zoobarzoobar:

 JDK 19

StringBuilder builder = new StringBuilder("zoo");
System.out.println("" + builder + builder.append("bar"));
  • In Java 19, the process of concatenating strings now evaluates each parameter and eagerly creates a string from left to right.
  • This fixes a bug in Java 9’s JEP 280, which introduced string concatenation techniques based on invokedynamic.

6

Farewell to APIs has gone forever.

@_tamanm

|

|

|

@_tamanm

|

|

|

Deprecation of Locale class constructors

Locale japanese = new Locale("ja"); // deprecated
Locale japan    = new Locale("ja", "JP"); // deprecated

Locale japanese1 = Locale.of("ja");
Locale japan1    = Locale.of("ja", "JP");

System.out.println("japanese  == Locale.JAPANESE = " + (japanese  == Locale.JAPANESE));
System.out.println("japan     == Locale.JAPAN    = " + (japan     == Locale.JAPAN));
System.out.println("japanese1 == Locale.JAPANESE = " + (japanese1 == Locale.JAPANESE));
System.out.println("japan1    == Locale.JAPAN    = " + (japan1    == Locale.JAPAN));

mohamed_taman -- -bash

> The following example shows the use of the factory method compared to the old constructor:

  • In Java 19, the public constructors of the Locale class were marked as deprecated. You should use the new static factory method Locale.of() to ensure only one instance per Locale configuration.

When you run this code, you will see that the objects supplied via the factory method are identical to the Locale constants, whereas those created with constructors logically are not.

 JDK 19

@_tamanm

|

|

|

Several java.lang.ThreadGroup methods degraded

> Now, the following methods have been decommissioned in Java 19:

In Java 14 and Java 16, many Thread and ThreadGroup methods were marked as deprecated for removal.

 JDK 19

  • ThreadGroup.destroy() invocations will be ignored.
  • ThreadGroup.isDestroyed() always returns false.
  • ThreadGroup.setDaemon() sets the daemon flag, but this has no effect.
  • ThreadGroup.getDaemon() returns the value of the unused daemon flags.
  • ThreadGroup.suspend(), resume(), and stop() throw an UnsupportedOperationException.

Questions?

Thanks for listening! 🧡

@_tamanm

|

|

|

Made with Slides.com