Java versions

I include only features that interest me.

Quick summary:

  • JDK 26 (in development)

    • JEP 517 HTTP/3 support in HttpClient

  • JDK 25 (September 2025) LTS

    • JEP 520 JFR Method Timing & Tracing

    • JEP 514 Ahead-of-Time Command-Line Ergonomics

    • JEP 513 Flexible Constructor Bodies allowing expressions before super(…​) or this(…​)

    • JEP 512 Compact Source Files & Instance Main Methods

    • JEP 506 Scoped Values (ThreadLocal replacement)

  • JDK 24 (March 2025)

    • JEP 485 Stream Gatherers

    • JEP 483 Ahead-of-Time Class Loading & Linking

  • JDK 23 (September 2024)

    • JEP 467 Markdown Documentation Comments using ///

  • JDK 22 (March 2024)

    • JEP 458 Launch Multi-File Source-Code Programs

    • JEP 456 Unnamed Variables & Patterns using _

  • JDK 21 (September 2023) LTS

    • JEP 444 Virtual Threads

    • JEP 441 Pattern Matching for switch supporting null, case Long l → …​, and when s.equalsIgnoreCase("YES") →

    • JEP 440 Record Patterns

    • JEP 431 Sequenced Collections

  • JDK 18 (March 2022)

    • JEP 413 Code Snippets in JavaDoc using @snippet tag

    • JEP 400 UTF-8 by Default

  • JDK 17 (September 2021) LTS

    • JEP 409 Sealed Classes & Interfaces

  • JDK 16 (March 2021)

    • JEP 395 Records

    • JEP 394 Pattern Matching for instanceof supporting if (obj instanceof String s && s.length() > 5)

  • JDK 15 (September 2020)

  • JDK 14 (March 2020)

    • JEP 358 Helpful NullPointerExceptions

JDK 26

In development

JEP 517: HTTP/3 for the HTTP Client API

Update the HTTP Client API to support the HTTP/3 protocol, so that libraries and applications can interact with HTTP/3 servers with minimal code change.

To send a request using HTTP/3, you must opt-in to using it.

var client = HttpClient.newBuilder()
                       .version(HttpClient.Version.HTTP_3)
                       .build();

JDK 25 LTS

September 2025

JEP 520: JFR Method Timing & Tracing

Extend the JDK Flight Recorder (JFR) with facilities for method timing and tracing via bytecode instrumentation.

It introduces two new JFR event: jdk.MethodTiming and jdk.MethodTrace.

$ java -XX:StartFlightRecording:jdk.MethodTrace#filter=java.util.HashMap::resize,filename=recording.jfr ...
$ jfr print --events jdk.MethodTrace --stack-depth 20 recording.jfr

In practice, configuration files are rather used.

$ java '-XX:StartFlightRecording:method-timing=::<clinit>,filename=clinit.jfr' ...
$ jfr view method-timing clinit.jfr

A filter can also name an annotation (including custom ones).

$ jcmd <pid> JFR.start method-timing=@jakarta.ws.rs.GET

JEP 514: Ahead-of-Time Command-Line Ergonomics

Make it easier to create ahead-of-time caches, which accelerate the startup of Java applications, by simplifying the commands required for common use cases.

Simplifies JEP 483: Ahead-of-Time Class Loading & Linking the two-step workflow into a single step:

$ java -XX:AOTCacheOutput=app.aot -cp app.jar com.example.App ...

A production run that uses the AOT cache is started the same way as before:

$ java -XX:AOTCache=app.aot -cp app.jar com.example.App ...

JEP 513: Flexible Constructor Bodies

In the body of a constructor, allow statements to appear before an explicit constructor invocation, i.e., super(…​) or this(…​). Such statements cannot reference the object under construction, but they can initialize its fields and perform other safe computations. This change allows many constructors to be expressed more naturally. It also allows fields to be initialized before they become visible to other code in the class, such as methods called from a superclass constructor, thereby improving safety.

class Employee extends Person {

    String officeID;

    Employee(..., int age, String officeID) {
        if (age < 18  || age > 67)
            // Now fails fast!
            throw new IllegalArgumentException(...);
        super(..., age);
        this.officeID = officeID;
    }
}

JEP 512: Compact Source Files and Instance Main Methods

Evolve the Java programming language so that beginners can write their first programs without needing to understand language features designed for large programs.

  • Allow main methods to omit the infamous boilerplate of public static void main(String[] args).

  • Introduce a compact form of source file that lets developers get straight to the code.

  • Add a new class in the java.lang package that provides basic line-oriented I/O methods for beginners.

void main() {
    IO.println("Hello, World!");
}

JEP 506: Scoped Values

Introduce scoped values, which enable a method to share immutable data both with its callees within a thread, and with child threads. Scoped values are easier to reason about than thread-local variables. They also have lower space and time costs, especially when used together with virtual threads (JEP 444) and structured concurrency (JEP 505).

Problems with thread-local variables:

  • Unconstrained mutability — Every thread-local variable is mutable

  • Unbounded lifetime — Once a thread’s copy of a thread-local variable is set via the set method, the value to which it was set is retained for the lifetime of the thread, or until code in the thread calls the remove method.

  • Expensive inheritance — The overhead of thread-local variables may be worse when using large numbers of threads, because thread-local variables of a parent thread can be inherited by child threads. (A thread-local variable is not, in fact, local to one thread.)

class Framework {

    private static final ScopedValue<FrameworkContext> CONTEXT
                        = ScopedValue.newInstance();    // (1)

    void serve(Request request, Response response) {
        var context = createContext(request);
        where(CONTEXT, context)                         // (2)
                   .run(() -> Application.handle(request, response));
    }

    public PersistedObject readKey(String key) {
        var context = CONTEXT.get();                    // (3)
        var db = getDBConnection(context);
        db.readKey(key);
    }

}

JDK 24

March 2025

JEP 485: Stream Gatherers

Enhance the Stream API to support custom intermediate operations. This will allow stream pipelines to transform data in ways that are not easily achievable with the existing built-in intermediate operations.

jshell> Stream.of(1,2,3,4,5,6,7,8,9).gather(new WindowFixed(3)).toList()
$1 ==> [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

Built-in gatherers in the java.util.stream.Gatherers class:

  • fold is a stateful many-to-one gatherer which constructs an aggregate incrementally and emits that aggregate when no more input elements exist.

  • mapConcurrent is a stateful one-to-one gatherer which invokes a supplied function for each input element concurrently, up to a supplied limit.

  • scan is a stateful one-to-one gatherer which applies a supplied function to the current state and the current element to produce the next element, which it passes downstream.

  • windowFixed is a stateful many-to-many gatherer which groups input elements into lists of a supplied size, emitting the windows downstream when they are full.

  • windowSliding is a stateful many-to-many gatherer which groups input elements into lists of a supplied size. After the first window, each subsequent window is created from a copy of its predecessor by dropping the first element and appending the next element from the input stream.

JEP 483: Ahead-of-Time Class Loading & Linking

Improve startup time by making the classes of an application instantly available, in a loaded and linked state, when the HotSpot Java Virtual Machine starts. Achieve this by monitoring the application during one run and storing the loaded and linked forms of all classes in a cache for use in subsequent runs. Lay a foundation for future improvements to both startup and warmup time.

To create a cache takes two steps. First, run the application once, in a training run, to record its AOT configuration, in this case into the file app.aotconf:

$ java -XX:AOTMode=record -XX:AOTConfiguration=app.aotconf \
-cp app.jar com.example.App ...

Second, use the configuration to create the cache, in the file app.aot:

$ java -XX:AOTMode=create -XX:AOTConfiguration=app.aotconf \
-XX:AOTCache=app.aot -cp app.jar

To check if your JVM is correctly configured to use the AOT cache, you can add the option -XX:AOTMode=on to the command line:

$ java -XX:AOTCache=app.aot -XX:AOTMode=on \
-cp app.jar com.example.App ...

JDK 23

September 2024

JEP 467: Markdown Documentation Comments

Enable JavaDoc documentation comments to be written in Markdown rather than solely in a mixture of HTML and JavaDoc @-tags.

/// Returns a hash code value for the object. This method is
/// supported for the benefit of hash tables such as those provided by
/// [java.util.HashMap].
///
/// The general contract of `hashCode` is:
///
///   - Whenever...
///   - ..
///
/// @implSpec
/// As far as is reasonably practical, the `hashCode` method defined
/// by class `Object` returns distinct integers for distinct objects.
///
/// @return  a hash code value for this object.
/// @see     java.lang.Object#equals(java.lang.Object)
/// @see     java.lang.System#identityHashCode

JDK 22

March 2024

JEP 458: Launch Multi-File Source-Code Programs

Enhance the java application launcher to be able to run a program supplied as multiple files of Java source code. This will make the transition from small programs to larger ones more gradual, enabling developers to choose whether and when to go to the trouble of configuring a build tool.

// file MainApplication.java
public class MainApplication {
    public static void main(String[] args) {
        Person p = new Person("Billy", "Korando");
        System.out.println("Hello, " + p.toString() + "!");
    }
}
// file Person.java
record Person(String fName, String lName) {
    public String toString(){
        return fName + " " + lName;
    }
}
$ java MainApplication.java
Hello Billy Korando!

JEP 456: Unnamed Variables & Patterns

Enhance the Java programming language with unnamed variables and unnamed patterns, which can be used when variable declarations or nested patterns are required but never used. Both are denoted by the underscore character, _.

try (var _ = ScopedContext.acquire()) {    // Unnamed variable
    ... no use of acquired resource ...
} catch (Exception _) { ... }
...stream.collect(Collectors.toMap(
    String::toUpperCase,
    _ -> "NODATA"))    // Unnamed variable

JDK 21 LTS

JEP 444: Virtual Threads

  • Preview: 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.

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    IntStream.range(0, 10_000).forEach(i -> {
        executor.submit(() -> {
            Thread.sleep(Duration.ofSeconds(1));
            return i;
        });
    });
}  // executor.close() is called implicitly, and waits

JEP 441: Pattern Matching for switch

  • Preview: 17

Enhance the Java programming language with pattern matching for switch expressions and statements. Extending pattern matching to switch allows an expression to be tested against a number of patterns, each with a specific action, so that complex data-oriented queries can be expressed concisely and safely.

Improved enum constant case labels:

sealed interface Currency permits Coin {}
enum Coin implements Currency { HEADS, TAILS }
static void goodEnumSwitch1(Currency c) {
    switch (c) {
        case Coin.HEADS -> {    // Qualified name of enum constant as a label
            System.out.println("Heads");
        }
        case Coin.TAILS -> {
            System.out.println("Tails");
        }
    }
}
static void goodEnumSwitch2(Coin c) {
    switch (c) {
        case HEADS -> {
            System.out.println("Heads");
        }
        case Coin.TAILS -> {    // Unnecessary qualification but allowed
            System.out.println("Tails");
        }
    }
}

Patterns in switch labels:

static void patternSwitchTest(Object obj) {
    String formatted = switch (obj) {
        case Integer i -> String.format("int %d", i);
        case Long l    -> String.format("long %d", l);
        case Double d  -> String.format("double %f", d);
        case String s  -> String.format("String %s", s);
        default        -> obj.toString();
    };
}
static void testNew(Object obj) {
    switch (obj) {
        case String s when s.length() == 1 -> ...
        case String s                      -> ...
        ...
    }
}

Enhanced type checking:

record Point(int i, int j) {}
enum Color { RED, GREEN, BLUE; }
static void typeTester(Object obj) {
    switch (obj) {
        case null            -> System.out.println("null");
        // Beware of dominance of String over CharSequence!
        case CharSequence cs -> System.out.println("CharSequence");
        case String s        -> System.out.println("String");
        case Color c         -> System.out.println("Color: " + c.toString());
        case Point p         -> System.out.println("Record class: " + p.toString());
        case int[] ia        -> System.out.println("Array of ints of length" + ia.length);
        default              -> System.out.println("Something else");
    }
}

JEP 440: Record Patterns

  • Preview: 19

Enhance the Java programming language with record patterns to deconstruct record values. Record patterns and type patterns can be nested to enable a powerful, declarative, and composable form of data navigation and processing.

Pattern matching and records:

static void printSum(Object obj) {
    if (obj instanceof Point(int x, int y)) {
        System.out.println(x+y);
    }
}

Nested record patterns:

static void printColorOfUpperLeftPoint(Rectangle r) {
    if (r instanceof Rectangle(ColoredPoint(Point p, Color c),
                               ColoredPoint lr)) {
        System.out.println(c);
    }
}
static void printXCoordOfUpperLeftPointWithPatterns(Rectangle r) {
    if (r instanceof Rectangle(ColoredPoint(Point(var x, var y), var c),
                               var lr)) {
        System.out.println("Upper-left corner: " + x);
    }
}

JEP 431: Sequenced Collections

Introduce new interfaces to represent collections with a defined encounter order. Each such collection has a well-defined first element, second element, and so forth, up to the last element. It also provides uniform APIs for accessing its first and last elements, and for processing its elements in reverse order.

SequencedCollection:

interface SequencedCollection<E> extends Collection<E> {
    // new method
    SequencedCollection<E> reversed();
    // methods promoted from Deque
    void addFirst(E);
    void addLast(E);
    E getFirst();
    E getLast();
    E removeFirst();
    E removeLast();
}

SequencedSet:

interface SequencedSet<E> extends Set<E>, SequencedCollection<E> {
    SequencedSet<E> reversed();    // covariant override
}

SequencedMap:

interface SequencedMap<K,V> extends Map<K,V> {
    // new methods
    SequencedMap<K,V> reversed();
    SequencedSet<K> sequencedKeySet();
    SequencedCollection<V> sequencedValues();
    SequencedSet<Entry<K,V>> sequencedEntrySet();
    V putFirst(K, V);
    V putLast(K, V);
    // methods promoted from NavigableMap
    Entry<K, V> firstEntry();
    Entry<K, V> lastEntry();
    Entry<K, V> pollFirstEntry();
    Entry<K, V> pollLastEntry();
}

JDK 20

March 2023

JDK 19

September 2022

JDK 18

March 2022

JEP 413: Code Snippets in Java API Documentation

Introduce an @snippet tag for JavaDoc’s Standard Doclet, to simplify the inclusion of example source code in API documentation.

Markup tags define regions within the content of a snippet: @start, @end, @highlight, @replace, and @link.

Inline snippets: An inline snippet contains the content of the snippet within the tag itself.

/**
 * The following code shows how to use {@code Optional.isPresent}:
 * {@snippet :
 * if (v.isPresent()) {
 *     System.out.println("v: " + v.get());
 * }
 * }
 */

External snippets: An external snippet refers to a separate file that contains the content of the snippet.

/**
 * The following code shows how to use {@code Optional.isPresent}:
 * {@snippet file="ShowOptional.java" region="example"}
 */
public class ShowOptional {
    void show(Optional<String> v) {
        // @start region="example"
        if (v.isPresent()) {
            System.out.println("v: " + v.get());
        }
        // @end
    }
}

JEP 400: UTF-8 by Default

Specify UTF-8 as the default charset of the standard Java APIs. With this change, APIs that depend upon the default charset will behave consistently across all implementations, operating systems, locales, and configurations.

JDK 17 LTS

September 2021

JEP 409: Sealed Classes

  • Project Amber

  • Release: 17

  • Preview: 15

Enhance the Java programming language with sealed classes and interfaces. Sealed classes and interfaces restrict which other classes or interfaces may extend or implement them.

Exactly one of the modifiers final, sealed, and non-sealed must be used by each permitted subclass.

sealed interface Celestial
    permits Planet, Star, Comet { ... }

final class Planet implements Celestial { ... }
final class Star   implements Celestial { ... }
final class Comet  implements Celestial { ... }

JDK 16

March 2021

JEP 395: Records

  • Project Amber

  • Release: 16

Enhance the Java programming language with records, which are classes that act as transparent carriers for immutable data. Records can be thought of as nominal tuples.

record Range(int lo, int hi) {
    // Compact canonical validating constructor
    Range {
        if (lo > hi)  // referring here to the implicit constructor .parameters
            throw new IllegalArgumentException(String.format("(%d,%d)", lo, hi));
    }
}
record Rational(int num, int denom) {
    // Compact canonical normalizing constructor.
    Rational {
        int gcd = gcd(num, denom);
        num /= gcd;
        denom /= gcd;
    }
}

JEP 394: Pattern Matching for instanceof

  • Project Amber

  • Preview: 14

Enhance the Java programming language with pattern matching for the instanceof operator. Pattern matching allows common logic in a program, namely the conditional extraction of components from objects, to be expressed more concisely and safely.

if (obj instanceof String s) {
    // `String s` is visible here.
}
// `String s` is NOT visible here.
if (!(obj instanceof String s)) {
    // `String s` is NOT visible here.
} else {
    // `String s` is visible here.
}

JEP 392: Packaging Tool

Provide the jpackage tool, for packaging self-contained Java applications.

The supported platform-specific package formats are:

  • Linux: deb and rpm

  • macOS: pkg and dmg

  • Windows: msi and exe

jpackage --name myapp --input lib --main-jar main.jar --type pkg

JDK 15

September 2020

JEP 378: Text Blocks

  • Preview: 13

Add text blocks to the Java language. A text block is a multi-line string literal that avoids the need for most escape sequences, automatically formats the string in a predictable way, and gives the developer control over the format when desired.

final String html = """
                    {
                        "message": "Hello world"
                    }
                    """;

JDK 14

March 2020

JEP 358: Helpful NullPointerExceptions

Improve the usability of NullPointerExceptions generated by the JVM by describing precisely which variable was null.

Exception in thread "main" java.lang.NullPointerException:
        Cannot assign field "i" because "a" is null
    at Prog.main(Prog.java:5)

JDK 13

September 2019

JDK 12

March 2019