Blog

Java 14 | New Features

Key Takeaways

  • Switch expressions became permanent in Java 14; arrow labels work in both switch statements and switch expressions.
  • Records and pattern matching for instanceof were previews in Java 14 and became permanent in Java 16.
  • Text blocks were a second preview in Java 14; jpackage was an incubating tool.
  • Helpful NullPointerExceptions required an explicit runtime option in Java 14, while JFR event streaming enabled continuous event consumption.
  • Java 14 is end-of-life for general use. Choose a maintained JDK release and distribution for production.

Java 14 features included finalized switch expressions, previews of records and pattern matching for instanceof, a second preview of text blocks, and new packaging and monitoring capabilities. Released on March 17, 2020, Java 14 was a non-LTS release: its lasting significance is how those features developed into the Java you use now.

Java 14 Features: Final, Preview, Incubator, or Experimental?

A feature shipping in a JDK doesn’t necessarily mean its design is permanent. The official Java 14 release overview identifies the delivered JDK Enhancement Proposals, or JEPs; the table below separates their original status from later milestones.

Feature and Java 14 JEPStatus in Java 14Later milestone
Switch expressions — JEP 361Switch expressions became permanent in Java 14.No preview flags required.
Records — JEP 359Records were a preview feature in Java 14.Records became permanent in Java 16 under JEP 395.
Pattern matching for instanceof — JEP 305Pattern matching for instanceof was a preview feature in Java 14.Pattern matching for instanceof became permanent in Java 16 under JEP 394.
Text blocks — JEP 368Text blocks were a second preview in Java 14.Text blocks became permanent in Java 15 under JEP 378.
Helpful NullPointerExceptions — JEP 358Helpful NPE messages were delivered but disabled by default in Java 14.Helpful NPE messages became enabled by default in Java 15.
jpackage — JEP 343jpackage was an incubating tool in Java 14.jpackage became permanent in Java 16 under JEP 392.
JFR event streaming — JEP 349JFR event streaming was delivered as a non-preview feature in Java 14.Available without preview flags.
NUMA-aware G1 allocation — JEP 345NUMA-aware G1 allocation was a runtime enhancement in Java 14.Configuration and hardware matter.
ZGC on macOS and Windows — JEPs 364 and 365ZGC support on macOS and Windows was experimental in Java 14.ZGC became production-ready in Java 15 under JEP 377.

Preview language features are complete enough to evaluate but can change or disappear. Incubator APIs and tools provide non-final functionality for feedback; they aren’t the same mechanism as preview syntax. An experimental JVM feature has its own activation and support considerations.

How to Enable Java 14 Preview Features

Use JDK 14’s compiler and runtime to reproduce its preview behavior. According to the Java 14 preview-feature documentation, compilation requires --enable-preview together with the matching source release, and execution requires --enable-preview again.

Save this complete example as PreviewDemo.java:

record Person(String first, String last) { }

public class PreviewDemo {
    public static void main(String[] args) {
        Object obj = new Person("Ada", "Lovelace");
        if (obj instanceof Person person) {
            System.out.println(person.first());
        }
    }
}
# Historical reproduction: use JDK 14 for both commands.
javac --enable-preview --release 14 PreviewDemo.java
java --enable-preview PreviewDemo

Preview support is release-specific: a newer compiler’s --release 14 option does not recreate Java 14’s preview compiler. When moving forward, recompile your source for the target release rather than carrying old preview class files forward. On Java 16 and later, this example’s records and instanceof patterns require no preview flags.

Pattern Matching for instanceof

Pattern matching combines a type check with a variable binding, removing the separate cast. Before Java 14’s preview, you wrote:

// Fragment inside a method; obj is an Object parameter.
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println(s.length());
}

The preview syntax became:

// Fragment inside a method; requires preview support on Java 14.
if (obj instanceof String s) {
    System.out.println(s.length());
} else {
    // s is not in scope here.
}

The important rule is flow scoping: the variable is available where the compiler knows the pattern matched. It can also be used on the right side of &&, or after a negated check whose unsuccessful branch returns; its scope isn’t simply “inside the nearest braces.” A null reference does not match the String pattern.

The Java 14 pattern-matching guide illustrates scope extending beyond an early-return guard. Keep the type test close to the work that needs the narrowed type.

Switch Expressions

Java 14 finalized switch expressions, but an arrow does not make a switch an expression. A statement performs actions; an expression produces a value. Both forms can use arrow labels, which do not fall through to the next case.

This complete example preserves both forms and supplies the previously missing enum context. Save it as SwitchDemo.java, compile with javac SwitchDemo.java, and run with java SwitchDemo.

public class SwitchDemo {
    enum Day {
        MONDAY, TUESDAY, WEDNESDAY, THURSDAY,
        FRIDAY, SATURDAY, SUNDAY
    }

    public static void main(String[] args) {
        Day day = Day.WEDNESDAY;

        // Statement: performs an action.
        switch (day) {
            case MONDAY -> System.out.println("Monday");
            case TUESDAY, WEDNESDAY -> System.out.println("Hump days");
            case THURSDAY -> System.out.println("Almost Friday");
            case FRIDAY -> System.out.println("Friday");
            case SATURDAY, SUNDAY -> System.out.println("The weekend");
        }

        // Expression: supplies the value assigned to numLetters.
        int numLetters = switch (day) {
            case MONDAY, FRIDAY, SUNDAY -> 6;
            case TUESDAY -> 7;
            case THURSDAY, SATURDAY -> 8;
            case WEDNESDAY -> {
                int letters = day.name().length();
                yield letters;
            }
        };
        System.out.println(numLetters);
    }
}

A switch expression must be exhaustive, covering every possible selector value within its supported domain. This enum expression covers every constant, so it needs no explicit default; an integer or String selector generally needs one. In Java 14, a null enum selector still throws an NPE.

A block arm uses yield to supply its result; throwing an exception is also valid. Traditional colon labels remain available and can fall through, so prefer arrow labels unless you deliberately need that behavior. The switch-expression documentation explains both forms; our Java control-flow tutorial covers the surrounding fundamentals.

Records

The basic declaration remains concise:

// Complete top-level declaration; save as Person.java.
// Java 14 requires preview flags.
record Person(String first, String last) { }

For this declaration, the compiler provides private final component fields, a canonical constructor taking first and last, accessors named first() and last(), and implementations of equals(), hashCode(), and toString().

Records are nominal data carriers: their declared type matters, not just their component values. They are implicitly final, cannot explicitly extend another class, and cannot declare additional instance fields. You can add methods and customize construction, including validation through a compact constructor.

Records are shallowly immutable. Their component fields cannot be reassigned, but a referenced mutable list or array is not made immutable. Use defensive copies when your data model requires that guarantee. The Java 14 records documentation describes the generated members and restrictions; records are not interchangeable with arbitrary mutable classes.

Text Blocks

Text blocks first appeared as a preview in Java 13, not Java 14. Java 14’s second preview added two escape sequences: \s for a space and backslash followed by a line terminator to suppress that newline.

// Fragment inside a method; Java 14 requires preview flags.
String html = """
    <html>
        <body>
            <p>My HTML text</p>
        </body>
    </html>
    """;
System.out.print(html);

The opening triple-quote delimiter must be followed by a line terminator, optionally after whitespace. The compiler removes common incidental indentation while preserving relative indentation: here, the output starts with <html> at the left margin, while the nested elements remain indented.

The closing delimiter’s indentation participates in determining the stripped margin when it occupies its own line. This example ends with a newline; placing the closing delimiter immediately after the final content removes that final newline. Text blocks also normalize source line endings and strip trailing incidental whitespace—they aren’t raw strings. See the text-block programmer’s guide when whitespace is significant.

Helpful NullPointerExceptions

JEP 358 added messages that identify the failed operation and the null reference behind it. In Java 14, enable them explicitly with -XX:+ShowCodeDetailsInExceptionMessages; the option became enabled by default in Java 15.

Save this intentional failure as NpeDemo.java:

public class NpeDemo {
    static class A { B b; }
    static class B { C c; }
    static class C { int d; }

    public static void main(String[] args) {
        A a = new A();
        a.b.c.d = 41;
    }
}
javac -g NpeDemo.java
java -XX:+ShowCodeDetailsInExceptionMessages NpeDemo

The -g compiler option retains local-variable debug information. On a supporting HotSpot JVM, the diagnostic message identifies a.b as null:

java.lang.NullPointerException: Cannot read field "c" because "a.b" is null

Compare the documented defaults in the Java 14 launcher reference and Java 15 launcher reference. Treat the wording as diagnostic output, not a stable format to parse; an explicitly supplied exception message is returned unchanged.

Packaging Tool: jpackage

jpackage creates platform-specific application bundles, including a launcher and Java runtime. It does not ahead-of-time compile your application into native machine code.

Assuming input/app.jar already contains com.example.Main, this packaging fragment creates an application image:

# Packaging fragment: prepare input/app.jar before running.
jpackage --type app-image --name Example --input input --main-jar app.jar --main-class com.example.Main --dest dist

Installer formats include exe and msi on Windows, dmg and pkg on macOS, and deb and rpm on Linux. Build on the target operating system; jpackage does not cross-package for another OS, and installer formats can require additional platform tooling.

The Java 14 jpackage reference documents these options. Keep the input directory limited to distributable artifacts because every file in it is packaged. For new packaging pipelines, use a maintained JDK rather than Java 14’s incubator implementation.

Garbage Collection Improvements

NUMA-Aware Memory Allocation for G1

The original NUMA claim was real, but too broad: JEP 345 specifically delivered NUMA-aware allocation for G1 in Java 14. NUMA means memory access costs depend on which processor’s memory node holds the data.

G1 tries to allocate and retain young-generation objects on the same NUMA node across collections. The heuristics are enabled with -XX:+UseNUMA on configurations supporting NUMA; they are not a universal performance improvement for every machine or workload. The Java 14 release notes describe the allocation scope. Benchmark on the actual deployment topology before retaining a tuning flag.

Experimental ZGC Support on macOS and Windows

JEP 364 added experimental ZGC support on macOS; JEP 365 did the same on Windows. In Java 14, selecting ZGC required -XX:+UnlockExperimentalVMOptions -XX:+UseZGC.

ZGC became production-ready in Java 15, as documented in the Java 15 migration overview. That milestone is not a promise that every OS and architecture combination is supported by every distribution. Check your target JDK’s platform documentation and collector availability; Oracle publishes a JDK 25 certified-system matrix. For collector-selection basics, see garbage collection in Java.

JFR Event Streaming

JEP 349 extended JDK Flight Recorder with continuous event consumption through jdk.jfr.consumer. The RecordingStream API creates a recording stream for the current JVM, letting monitoring code consume events without a manual dump-file workflow.

Enable the events you need, register callbacks with onEvent, and choose start() for processing on the calling thread or startAsync() for a separate processing thread. Register an error handler and close the stream during shutdown.

This is not hard real-time delivery or a disk-free recording guarantee. Recording and consumer callbacks still have resource costs, and streaming can use the on-disk repository. Bound retention with setMaxAge or setMaxSize, keep callbacks lightweight, and measure monitoring overhead.

Other Changes and a Practical Upgrade Checklist

Java 14 also introduced the Foreign-Memory Access API as an incubator under JEP 370 and non-volatile mapped byte buffers under JEP 352. It removed the CMS garbage collector under JEP 363 and the Pack200 tools and API under JEP 367. Those removals matter when reviving old build scripts or JVM startup options.

  • Select support first. Choose a maintained distribution and release based on vendor policy, application compatibility, and update ownership.
  • Recompile historical previews. Don’t assume Java 14 preview class files are portable to a later JVM.
  • Review data semantics. Replacing a mutable class with a record is a design decision, not just a shorter declaration.
  • Recheck operational flags. Don’t copy Java 14’s experimental flags into a current deployment without checking the target JVM’s documentation.

Java 14 is useful history, not a recommended destination. To continue tracing these features into an LTS release, see our Java 17 feature overview.

FAQ

What are the new features in Java 14?

Java 14 finalized switch expressions and introduced previews of records and pattern matching for instanceof, plus a second preview of text blocks. It also delivered helpful NullPointerExceptions, JFR event streaming, incubating jpackage, NUMA-aware G1 allocation, and experimental ZGC support on macOS and Windows.

Which Java 14 features were preview or incubator features?

Records, pattern matching for instanceof, and text blocks were preview language features in Java 14. jpackage and the Foreign-Memory Access API were incubating functionality; experimental ZGC support was a separate JVM feature category.

How do I enable Java 14 preview features?

Using JDK 14, compile with javac --enable-preview --release 14 YourClass.java and run with java --enable-preview YourClass. Java 14 preview support requires the matching JDK release; a newer compiler’s --release 14 option does not recreate it.

When did records and pattern matching become permanent Java features?

Records and pattern matching for instanceof became permanent in Java 16 under JEPs 395 and 394, respectively. They no longer require preview flags when compiled for Java 16 or later.

Is Java 14 still supported?

As of October 17, 2026, Java 14 is end-of-life for general use and is not receiving ongoing updates in the main OpenJDK release line. Vendor-specific contractual terms can differ; choose a maintained LTS release and verify your distribution’s support policy for production.

Further Reading

Written by Sam Erickson

Software engineer. I write every article on StackChief myself. Author of Mastering the Tech Interview. More about me