17 March 2020 saw the release of JDK 14, per Java's newrelease cadence of six months. You can download this new JDK from AdoptOpenJDK website. The command to get Java version:

$ java -version
openjdk version "14" 2020-03-17
OpenJDK Runtime Environment AdoptOpenJDK (build 14+36)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 14+36, mixed mode, sharing)

In this article, we'll take a look at the new features and improvements introduced in this release.

JEP 305: Pattern Matching for instanceof (Preview)

This is a preview language feature, JEP 305 enhance Java with pattern matching for the instanceof operator. There is no need for a separate declaration and a cast, as instanceof can be used with an identifier as a local variable if instanceof expression is true.

JEP305.java
package com.dariawan.jdk14;

public class JEP305 {

    public static void main(String[] args) {
        Object obj1 = "Dariawan";
        
        // Before JDK 14
        if (obj1 instanceof String) {
            String str = (String) obj1;
            System.out.printf("String: %s\n", str);
        } else {
            System.out.printf("Not a string\n");
        }
        
        // Preview feature in JDK 14
        // instanceof, cast and bind variable in one line.
        if (obj1 instanceof String str) {            
            System.out.printf("String: %s\n", str);
        } else {
            System.out.printf("Not a string\n");
        }
        
        Object obj2 = " ";
        // work for &&, not for ||
        if (obj2 instanceof String str && str.isBlank()) {            
            System.out.printf("It's a String, and blank");
        } else {
            System.out.printf("It's not a String or not blank");
        }
    }
}
                    

JEP 343: Packaging Tool (Incubator)

JEP 343 aims to create a simple packaging tool for self-contained Java applications. This tool, jpackage is based on the JavaFX javapackager tool that was removed in Oracle JDK 11. We will have more detail about this tool in separate article.

JEP 345: NUMA-Aware Memory Allocation for G1

Non-uniform Memory Access (NUMA) architecture is common in systems with multiple processors. NUMA is a way of configuring cluster of microprocessor into a multiprocessing system, by assigning each processor is a specific local memory exclusively for its own use. In this setup, a processor can access local memory much faster than non-local memory (which having more latency).

JEP 345 aims to improve G1 performance on large systems by implementing NUMA-aware memory allocation. G1's heap is arranged as a group of fixed-size regions. The regions will be evenly spread across the total number of available NUMA nodes when the JVM is initialized and the +XX:+UseNUMA option is specified, .

JEP 349: JFR Event Streaming

JDK Flight Recorder(JFR) is a tool for collecting diagnostic and profiling data about the JVM itself, and the application running in the JVM. It is integrated into the JVM and can be used even in heavily loaded production environments with almost no performance overhead.

JEP 349 is enhancements for existing JFR to expose it's data for continuous monitoring. This allowing the streaming of JFR data in real-time, without the need to dump the recorded events to storage and parse it later. So in short, it will open the door for profiling, analysis, or debugging in real time, compared to collection and post collection step as before.

Please find related article: Java 11 - Flight Recorder (JEP 328)

JEP 352: Non-Volatile Mapped Byte Buffers

In JEP 352, FileChannel API is improved to support creating MappedByteBuffer instances on non-volatile memory (NVM) or persistent memory. This feature ensures that any changes which might still be in the cache are written back to memory, and only supported in Linux/x64 and Linux/AArch64 platforms.

JEP 358: Helpful NullPointerExceptions

In JEP 358, NullPointerExceptions generated by the JVM will give greater usability by precisely pointing out which variable was null. To enable this feature, add -XX:+ShowCodeDetailsInExceptionMessages option.

For example, let's take a look again my NullPointerExample in article Java 8 Optional Overview With Examples. I bring it as JEP358NullPointerExample, please refer to class Department and Employee in the same article (copied into com.dariawan.jdk14.dto package).

JEP358NullPointerExample.java
package com.dariawan.jdk14;

import com.dariawan.jdk14.dto.Employee;
import java.time.LocalDate;
import java.time.Month;

public class JEP358NullPointerExample {
    
    public static void main(String[] args) {
        Employee emp = new Employee();
        emp.setId(1001);
        emp.setName("Clark Kent");
        emp.setBirthDate(LocalDate.of(1974, Month.JUNE, 18));
 
        System.out.println(emp.getDepartment().getName());
    }
}
                    

Running it without -XX:+ShowCodeDetailsInExceptionMessages option:

$ java --enable-preview com.dariawan.jdk14.JEP358NullPointerExample
Exception in thread "main" java.lang.NullPointerException
        at com.dariawan.jdk14.JEP358NullPointerExample.main(JEP358NullPointerExample.java:53)

That's the details you get before Java 14, and without those option. And now running it with -XX:+ShowCodeDetailsInExceptionMessages option:

$ java --enable-preview -XX:+ShowCodeDetailsInExceptionMessages com.dariawan.jdk14.JEP358NullPointerExample
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "com.dariawan.jdk14.dto.Department.getName()" because the return value of "com.dariawan.jdk14.dto.Employee.getDepartment()" is null
        at com.dariawan.jdk14.JEP358NullPointerExample.main(JEP358NullPointerExample.java:53)

JEP 359: Records (Preview)

To enhance the Java language to compact the class declaration syntax with record. For a simple class, we need to write a lot of low-value, repetitive code: constructors, setters/getters, equals(), hashCode(), toString(), etc. Yes, there are third party library like lombok helping us with this repetitive "tasks", but record is another game changer.

For example, let's convert Department and Employee as records

Department.java
package com.dariawan.jdk14.records;

public record Department (
    Integer id, 
    String name) {
}
                    

Employee.java
package com.dariawan.jdk14.records;

import java.time.LocalDate;

public record Employee(
    Integer id,
    String name,
    LocalDate birthDate,
    Department department) {    
}
                    

Then create JEP359Records test class:

JEP359Records.java
package com.dariawan.jdk14;

import com.dariawan.jdk14.records.Employee;
import com.dariawan.jdk14.records.Department;
import java.time.LocalDate;
import java.time.Month;

public class JEP359Records {
 
    public static void main(String[] args) {
        Employee emp = new Employee(101, "Janice Leah Abigail", 
                LocalDate.of(1997, Month.MAY, 7),
                new Department(1, "IT - Application"));
        System.out.println(emp);
    }
}
                    

And when we running it:

$ java --enable-preview com.dariawan.jdk14.JEP359Records
Employee[id=101, name=Janice Leah Abigail, birthDate=1997-05-07, department=Department[id=1, name=IT - Application]]

It's very straightforward!

JEP 361: Switch Expressions (Standard)

Switch Expressions, was a preview language feature in JDK 12 and JDK 13. Based on JEP 361, It's now a standard language feature in JDK 14, means we can use it without a need to specify option --enable-preview. Please check following tutorials:

JEP 362: Deprecate the Solaris and SPARC Ports

In JEP 362 Solaris/SPARC, Solaris/x64, and Linux/SPARC ports is deprecated and will be removed in a future release.

JEP 363: Remove the Concurrent Mark Sweep (CMS) Garbage Collector

CMS Garbage Collected was deprecated in Java 9, and with JEP 363 — it's removed in Java 14.

$ java -XX:+UseConcMarkSweepGC com.dariawan.jdk14.JEP363
OpenJDK 64-Bit Server VM warning: Ignoring option UseConcMarkSweepGC; support was removed in 14.0
Remove the Concurrent Mark Sweep (CMS) GC

Besides CMS, there are two other GCs: ZGC (since Java 11 — only in Linux/x64) and Shenandoah (since Java 12), and both are still in experimental stage.

JEP 364: ZGC on macOS (Experimental)

ZGC was introduced in Java 11, but it was only supported Linux/X64. JEP 364 bring it to macOS.

JEP 365: ZGC on Windows (Experimental)

Similar for Windows platform, JEP 365 bring ZGC to Windows.

JEP 366: Deprecate the ParallelScavenge + SerialOld GC Combination

Based on JEP 366, the combination of the Parallel Scavenge (called ParallelScavenge) and Serial Old garbage collection (called SerialOld) algorithms is deprecated due to little use and large maintenance effort.

$ java -XX:-UseParallelOldGC com.dariawan.jdk14.JEP366
OpenJDK 64-Bit Server VM warning: Option UseParallelOldGC was deprecated in version 14.0 and will likely be removed in a future release.

JEP 367: Remove the Pack200 Tools and API

The pack200 and unpack200 tools, and the Pack200 API in the java.util.jar package were deprecated for removal in Java SE 11. And with JEP 367, this is completely removed in JDK 14.

JEP 368: Text Blocks (Second Preview)

This was started as JEP 355, a preview feature in Java 13, and JEP 368 is the second preview for this feature. Text blocks making it easier for you to work with multi line string literals, avoiding the need for most escape sequences. Text blocks also give greater control on how to format your strings and improve the readability of your code.

In this release, there are two new escape sequences:

  • \ at the end-of-line will suppress the line termination.
  • \s will resulting a single space.

Check following code:

JEP368TextBlocks.java
package com.dariawan.jdk14;

public class JEP368TextBlocks {
    
    public static void main(String[] args) {
        String html1 = """
                      <html>
                          <body>
                              <p>Text Blocks as in Java 13</p>
                          </body>
                      </html>
                      """;
        System.out.println(html1);
        
        String html2 = """
                      <html>
                          <body>\
                              <p>Text Blocks as in Java\s14</p>\
                          </body>
                      </html>
                      """;
        System.out.println(html2);
    }
}
                    

And the result is:

$ java --enable-preview com.dariawan.jdk14.JEP368TextBlocks
<html>
    <body>
        <p>Text Blocks as in Java 13</p>
    </body>
</html>

<html>
    <body>        <p>Text Blocks as in Java 14</p>    </body>
</html>

Also find related posting for JDK 13 release: Java 13 - Text Blocks (JEP 355)

JEP 370: Foreign-Memory Access API (Incubator)

Although until now the Java API does not provide a satisfactory solution for accessing foreign memory, Accessing foreign memory is not something new in JVM world. This is done by many Java libraries and programs, like Ignite, mapDB, memcached, and Netty's ByteBuf API.

JEP 370 introduces an API to allow Java programs to efficiently access native memory segments out of JVM heap space (off-heap). There are three main interfaces:

  • MemorySegment: models a contiguous region of memory with given bounds.
  • MemoryAddress: encodes an offset within a given MemorySegment, basically a pointer.
  • MemoryLayout: used to describe the contents of a memory segment in a language neutral fashion.

And a class that used in our example:

  • MemoryHandles: provides several factory methods for constructing and combining memory access var handles.
JEP370OffHeap.java
package com.dariawan.jdk14;

import jdk.incubator.foreign.MemoryHandles;
import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.MemoryAddress;
import java.lang.invoke.VarHandle;
import java.nio.ByteOrder;

public class JEP370OffHeap {

    public static void main(String[] args) {

        VarHandle intHandle = MemoryHandles.varHandle(int.class, ByteOrder.nativeOrder());

        try (MemorySegment segment = MemorySegment.allocateNative(100)) {
            MemoryAddress base = segment.baseAddress();
            for (int i = 0; i <= 25; i++) {
                var rebase = base.addOffset(i * 4);
                // set value i into the foreign memory
                intHandle.set(rebase, i);
                // print memory address and the value from foreign memory
                System.out.printf("%s: %s\n", rebase, intHandle.get(rebase));
            }
        }
    }
}
                    

You need to add module jdk.incubator.foreign to try this feature. For my case, in Maven's pom.xml I added these arg(s) in maven-compiler-plugin:

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> <configuration> <release>14</release> <compilerArgs> <arg>--enable-preview</arg> <arg>--add-modules</arg> <arg>jdk.incubator.foreign</arg> </compilerArgs> </configuration> </plugin>

And when running it:

>java --enable-preview --add-modules jdk.incubator.foreign com.dariawan.jdk14.JEP370OffHeap
WARNING: Using incubator modules: jdk.incubator.foreign
MemoryAddress{ region: MemorySegment{ id=0x4fa41ae5 limit: 100 } offset=0x0 }: 0
MemoryAddress{ region: MemorySegment{ id=0x4fa41ae5 limit: 100 } offset=0x4 }: 1
MemoryAddress{ region: MemorySegment{ id=0x4fa41ae5 limit: 100 } offset=0x8 }: 2
MemoryAddress{ region: MemorySegment{ id=0x4fa41ae5 limit: 100 } offset=0xc }: 3
MemoryAddress{ region: MemorySegment{ id=0x4fa41ae5 limit: 100 } offset=0x10 }: 4
MemoryAddress{ region: MemorySegment{ id=0x4fa41ae5 limit: 100 } offset=0x14 }: 5
MemoryAddress{ region: MemorySegment{ id=0x4fa41ae5 limit: 100 } offset=0x18 }: 6
MemoryAddress{ region: MemorySegment{ id=0x4fa41ae5 limit: 100 } offset=0x1c }: 7
MemoryAddress{ region: MemorySegment{ id=0x4fa41ae5 limit: 100 } offset=0x20 }: 8
MemoryAddress{ region: MemorySegment{ id=0x4fa41ae5 limit: 100 } offset=0x24 }: 9
MemoryAddress{ region: MemorySegment{ id=0x4fa41ae5 limit: 100 } offset=0x28 }: 10
MemoryAddress{ region: MemorySegment{ id=0x4fa41ae5 limit: 100 } offset=0x2c }: 11
MemoryAddress{ region: MemorySegment{ id=0x4fa41ae5 limit: 100 } offset=0x30 }: 12
MemoryAddress{ region: MemorySegment{ id=0x4fa41ae5 limit: 100 } offset=0x34 }: 13
MemoryAddress{ region: MemorySegment{ id=0x4fa41ae5 limit: 100 } offset=0x38 }: 14
MemoryAddress{ region: MemorySegment{ id=0x4fa41ae5 limit: 100 } offset=0x3c }: 15
MemoryAddress{ region: MemorySegment{ id=0x4fa41ae5 limit: 100 } offset=0x40 }: 16
MemoryAddress{ region: MemorySegment{ id=0x4fa41ae5 limit: 100 } offset=0x44 }: 17
MemoryAddress{ region: MemorySegment{ id=0x4fa41ae5 limit: 100 } offset=0x48 }: 18
MemoryAddress{ region: MemorySegment{ id=0x4fa41ae5 limit: 100 } offset=0x4c }: 19
MemoryAddress{ region: MemorySegment{ id=0x4fa41ae5 limit: 100 } offset=0x50 }: 20
MemoryAddress{ region: MemorySegment{ id=0x4fa41ae5 limit: 100 } offset=0x54 }: 21
MemoryAddress{ region: MemorySegment{ id=0x4fa41ae5 limit: 100 } offset=0x58 }: 22
MemoryAddress{ region: MemorySegment{ id=0x4fa41ae5 limit: 100 } offset=0x5c }: 23
MemoryAddress{ region: MemorySegment{ id=0x4fa41ae5 limit: 100 } offset=0x60 }: 24
Exception in thread "main" java.lang.IndexOutOfBoundsException: Out of bound access on segment MemorySegment{ id=0x4fa41ae5 limit: 100 }; new offset = 100; new length = 4
        at jdk.incubator.foreign/jdk.internal.foreign.MemorySegmentImpl.checkBounds(MemorySegmentImpl.java:199)
        at jdk.incubator.foreign/jdk.internal.foreign.MemorySegmentImpl.checkRange(MemorySegmentImpl.java:178)
        at jdk.incubator.foreign/jdk.internal.foreign.MemoryAddressImpl.checkAccess(MemoryAddressImpl.java:84)
        at java.base/java.lang.invoke.VarHandleMemoryAddressAsInts.checkAddress(VarHandleMemoryAddressAsInts.java:50)
        at java.base/java.lang.invoke.VarHandleMemoryAddressAsInts.set0(VarHandleMemoryAddressAsInts.java:85)
        at java.base/java.lang.invoke.VarHandleMemoryAddressAsInts0/0x0000000800ba5840.set(Unknown Source)
        at java.base/java.lang.invoke.VarHandleGuards.guard_LI_V(VarHandleGuards.java:114)
        at com.dariawan.jdk14.JEP370OffHeap.main(JEP370OffHeap.java:53)

The IndexOutOfBoundsException happen as expected because we just allocated 100 in MemorySegment.allocateNative(100)

More in Package jdk.incubator.foreign.