Overview of New Features in Java 19

Java 19 has just been released — September 20, 2022 — as a short-term support release on the six-month cadence the Java platform moved to starting with Java 9. It is not an LTS release, but that does not make it uninteresting: Java 19 ships 7 JEPs, several of them highly anticipated by the Java community. Virtual Threads, in particular, represent one of the most significant changes to the Java concurrency model in years.

The headline features span concurrency, pattern matching, native interop, and platform reach. Three features arrive as preview (complete but seeking feedback), one as an incubator module (early-stage API), and the rest are fully stable additions. Because several require flags to enable, we will also cover exactly how to turn them on — both on the command line and in Maven.

This article walks through each major Java 19 feature with code examples and explains the preview and incubator status so you know what you can try today and what to expect in future releases.

Virtual Threads — JEP 425 (Preview)

Virtual threads are lightweight threads managed entirely by the JVM, not by the operating system. Unlike the platform threads Java has always had (which map 1-to-1 to OS threads), virtual threads are cheap — you can create millions of them without exhausting system resources. The JVM schedules them on a small pool of carrier OS threads behind the scenes.

The practical benefit is that you can write straightforward blocking code — the kind every Java developer already knows — and get the throughput of an async or reactive architecture without the callback complexity. When a virtual thread blocks on I/O (a database call, an HTTP request, a file read), the JVM parks it and reuses the underlying OS thread for other work. No async frameworks required.

// Starting a virtual thread directly
Thread vThread = Thread.ofVirtual().start(() -> {
    System.out.println("Hello from: " + Thread.currentThread());
});
vThread.join();

// High-throughput executor backed by virtual threads
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (int i = 0; i < 10_000; i++) {
        executor.submit(() -> handleRequest());
    }
} // executor shuts down, all tasks complete

Virtual Threads require --enable-preview in Java 19. Compile with javac --enable-preview --release 19 MyApp.java and run with java --enable-preview MyApp.

Structured Concurrency — JEP 428 (Incubator)

Structured Concurrency is a new API for treating a group of related concurrent tasks as a single unit of work. The core idea: tasks that are forked together should be joined together, and if one subtask fails, the others are cancelled automatically. This eliminates the common bugs that arise when managing multiple Futures manually — leaked threads, forgotten cancellations, and tangled error handling.

The API works naturally with Virtual Threads, making it straightforward to fan out concurrent sub-requests and collect the results at a single join point.

import jdk.incubator.concurrent.StructuredTaskScope;

try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Future<User>  user  = scope.fork(() -> fetchUser(userId));
    Future<Order> order = scope.fork(() -> fetchOrder(orderId));

    scope.join();           // waits for both
    scope.throwIfFailed();  // rethrows any exception

    return new Response(user.resultNow(), order.resultNow());
}

As an incubator module, Structured Concurrency lives in a separate module. Enable it with --add-modules jdk.incubator.concurrent at both compile and run time.

Record Patterns — JEP 405 (Preview)

Record Patterns extend pattern matching to let you destructure record components inline inside instanceof checks and switch expressions. Instead of matching a type and then calling accessor methods manually, you can bind the record components directly in the pattern.

Nested record patterns are supported too, making it easy to deconstruct deeply nested data structures in a single, readable expression.

record Point(int x, int y) {}

Object obj = new Point(3, 4);

// Destructure directly — no manual calls to .x() or .y()
if (obj instanceof Point(int x, int y)) {
    System.out.println("x=" + x + ", y=" + y);
}

// Nested record patterns
record Line(Point start, Point end) {}
if (obj instanceof Line(Point(int x1, int y1), Point(int x2, int y2))) {
    System.out.printf("(%d,%d) → (%d,%d)%n", x1, y1, x2, y2);
}

Requires --enable-preview --release 19 to compile and --enable-preview to run.

Pattern Matching for switch — JEP 427 (Third Preview)

This is the third preview of Pattern Matching for switch, first introduced in Java 17 and refined based on developer feedback across releases. Java 19 brings further refinements to guarded patterns and the semantics around null handling and exhaustiveness.

When combined with sealed interfaces, the compiler can verify that a switch expression is exhaustive — it will produce a compile error if you miss a permitted subtype. This makes your code safer as your type hierarchy evolves.

sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double w, double h) implements Shape {}
record Triangle(double base, double height) implements Shape {}

static double area(Shape shape) {
    return switch (shape) {
        case Circle c    -> Math.PI * c.radius() * c.radius();
        case Rectangle r -> r.w() * r.h();
        case Triangle t  -> 0.5 * t.base() * t.height();
    };
    // No default needed — compiler verifies Shape is exhaustively covered
}

Requires --enable-preview --release 19 to compile and --enable-preview to run.

Foreign Function & Memory API — JEP 424 (Preview)

The Foreign Function & Memory API is a pure-Java replacement for JNI (Java Native Interface). JNI has always been the mechanism for calling native C libraries and managing off-heap memory, but it required writing C glue code and was notoriously error-prone. The FFM API handles all of that from Java, with safe memory lifecycle management via Arena.

Java 19 is the first preview of the FFM API in its current form (earlier incubator iterations appeared in Java 17 and 18 but with a different shape). The example below calls the C standard library’s strlen directly — no C code, no JNI boilerplate.

import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;

Linker linker = Linker.nativeLinker();
MethodHandle strlen = linker.downcallHandle(
    linker.defaultLookup().find("strlen").orElseThrow(),
    FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
);

try (Arena arena = Arena.ofConfined()) {
    MemorySegment str = arena.allocateUtf8String("Hello!");
    long len = (long) strlen.invoke(str);  // calls C strlen()
    System.out.println(len); // 6
} // arena closes here, memory is automatically freed

Requires --enable-preview --release 19 to compile and --enable-preview to run.

New Pre-Sized Collection Factory Methods

Java 19 adds static factory methods to HashMap, HashSet, and LinkedHashMap that accept the expected number of entries directly. Before this, if you knew you’d be inserting N elements you had to manually calculate the initial capacity — accounting for the default load factor of 0.75 — to avoid a resize and rehash. These new methods handle that calculation internally.

// Before: had to guess or over-allocate
Map<String, Integer> old = new HashMap<>(expectedSize * 4 / 3 + 1);

// Java 19: just say how many entries you expect
Map<String, Integer> map = HashMap.newHashMap(100);
Set<String>         set = HashSet.newHashSet(100);
Map<String, Integer> lhm = LinkedHashMap.newLinkedHashMap(100);

No flags required — these are stable additions to the core library. Particularly useful when building large in-memory lookup tables where predictable performance matters.

Linux/RISC-V Port — JEP 422

Java 19 adds a production-quality port of the JDK to the Linux/RISC-V instruction set architecture. RISC-V is an open, royalty-free ISA that has gained significant traction in embedded systems, development boards, and IoT hardware. This port targets the RV64GV configuration (64-bit base with the vector extension).

This is a platform port — there are no API changes, and it has no impact on code you write. It is relevant if you are deploying Java applications to RISC-V hardware running Linux, bringing the full JDK to a new category of devices.

How to Enable Preview Features

Several Java 19 features are in preview or incubator status. Here is the quick reference for enabling them.

# Compile with preview enabled
javac --enable-preview --release 19 MyApp.java

# Run with preview enabled
java --enable-preview MyApp

# Incubator modules (e.g. Structured Concurrency)
java --enable-preview --add-modules jdk.incubator.concurrent MyApp

For Maven projects, configure the compiler plugin:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <release>19</release>
        <compilerArgs>
            <arg>--enable-preview</arg>
        </compilerArgs>
    </configuration>
</plugin>

Frequently Asked Questions

What is the difference between Preview, Incubator, and GA features?

Preview: The feature is complete and fully implemented but is seeking developer feedback before finalization. Requires --enable-preview. The API may change in the next release based on feedback.

Incubator: An API in early development, delivered in a separate module outside the main Java SE specification. Requires --add-modules. More likely to change significantly or be removed than a preview feature.

GA (General Availability): Finalized and stable. No special flags needed. Covered by the Java SE specification.

Is Java 19 a Long-Term Support release?

No. Java 19 is a short-term support release. It receives six months of Premier Support from Oracle (until March 2023). For production deployments that require multi-year support, use the most recent LTS release. Java 19 is intended for developers who want to try the latest features on the six-month cadence.

Can I use Virtual Threads in production with Java 19?

Virtual Threads are in preview in Java 19, which means they require --enable-preview and the API may change in the next release. They are functionally complete and suitable for experimentation, performance testing, and providing feedback to the Java team. For stable, long-term production use, it is worth waiting for the GA release in a future version.

Conclusion

Java 19 is packed with forward-looking features that signal where the Java platform is heading. Virtual Threads in particular represent a major shift in how Java handles concurrency — writing blocking code that scales like async code is a genuinely new capability. Structured Concurrency, Record Patterns, and the continued refinement of Pattern Matching for switch round out a release that feels both practical and ambitious.

Enable preview features, download the JDK 19 early access builds, and try them out — the Java team is actively looking for feedback on these features before they finalize. This is the moment to shape what these APIs look like when they land as stable.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.