JDK 23 Key Features — Markdown Javadoc and ZGC Generational Default

JDK 23’s Position

JDK 23, released on September 17, 2024, is a non-LTS release but one that made meaningful progress in both developer experience and GC performance. In particular, the finalization of Markdown Documentation Comments (JEP 467) marks a fundamental change in how Javadoc has been written for over 20 years.

This article covers JDK 23’s key JEPs with runnable examples.

Markdown Documentation Comments (JEP 467)

Traditional Javadoc was HTML tag-based. Writing a simple list required <ul> and <li> tags, and code blocks needed cumbersome syntax like <pre>{@code ...}</pre>. Starting with JDK 23, Javadoc comments can be written in Markdown.

Markdown Javadoc uses /// (three slashes) at the start of comments, distinguishing it from the traditional /** ... */ block comments.

import java.util.List;
import java.util.stream.Collectors;

/// A utility class for processing name lists.
///
/// ## Key Features
/// - Name filtering (by length)
/// - Uppercase conversion
/// - Sorting
///
/// ## Usage Example
/// ```
/// List<String> result = NameProcessor.filterAndSort(names, 3);
/// ```
///
/// @param names the list of names to process
/// @param minLength minimum character count
/// @return filtered and sorted list of names
public class NameProcessor {

    /// Filters names that meet the minimum length and returns a sorted list.
    ///
    /// **Note**: `null` elements are automatically excluded.
    public static List<String> filterAndSort(List<String> names, int minLength) {
        return names.stream()
            .filter(name -> name != null && name.length() >= minLength)
            .sorted()
            .collect(Collectors.toList());
    }

    public static void main(String[] args) {
        // Markdown Javadoc feature test
        List<String> names = List.of("Jo", "Alexander", "Elizabeth", "Al", "Christopher");
        List<String> result = filterAndSort(names, 5);
        System.out.println("Names with 5+ chars: " + result);
        // Output: Names with 5+ chars: [Alexander, Christopher, Elizabeth]

        List<String> all = filterAndSort(names, 1);
        System.out.println("All names (sorted): " + all);
        // Output: All names (sorted): [Al, Alexander, Christopher, Elizabeth, Jo]
    }
}

The key benefit of Markdown Javadoc is readability. Natural documentation can be written without HTML tags, and it renders directly in GitHub and IDE previews. Existing Javadoc tags like @param and @return can still be used, maintaining compatibility with existing workflows.

ZGC Generational Mode by Default (JEP 474)

ZGC is a low-latency GC officially introduced in JDK 15. JDK 21 added Generational mode, and JDK 23 made it the default.

Generational GC is based on the Weak Generational Hypothesis — “most objects die young.” Think of a supermarket’s fresh food section: it’s more efficient to check items with short shelf life (Young objects) frequently and inspect long-storage items (Old objects) occasionally.

import java.util.ArrayList;
import java.util.List;

public class ZgcGenerationalDemo {
    public static void main(String[] args) {
        // Scenario demonstrating ZGC Generational mode effect
        // Run: java -XX:+UseZGC ZgcGenerationalDemo

        // Young generation: most objects are created and quickly reclaimed here
        long startMemory = Runtime.getRuntime().freeMemory();

        // Create many short-lived objects (Young generation target)
        for (int i = 0; i < 1_000_000; i++) {
            String temp = "temp-object-" + i; // Immediately dereferenced -> reclaimed in Young GC
        }

        long afterYoung = Runtime.getRuntime().freeMemory();

        // Long-lived objects: promoted to Old generation
        List<String> longLived = new ArrayList<>();
        for (int i = 0; i < 10_000; i++) {
            longLived.add("long-lived-" + i); // Reference maintained -> promoted to Old generation
        }

        long afterOld = Runtime.getRuntime().freeMemory();

        System.out.println("=== ZGC Generational Mode Simulation ===");
        System.out.println("Initial free memory: " + formatBytes(startMemory));
        System.out.println("After Young objects: " + formatBytes(afterYoung));
        System.out.println("Old objects retained: " + formatBytes(afterOld));
        System.out.println("Long-lived object count: " + longLived.size());
        // Example output:
        // === ZGC Generational Mode Simulation ===
        // Initial free memory: 245.8 MB
        // After Young objects: 198.3 MB
        // Old objects retained: 196.1 MB
        // Long-lived object count: 10000
    }

    static String formatBytes(long bytes) {
        return String.format("%.1f MB", bytes / (1024.0 * 1024.0));
    }
}

Before JDK 23, the -XX:+ZGenerational flag was needed to use generational mode. From JDK 23, just specifying -XX:+UseZGC automatically applies generational mode. In terms of performance numbers, approximately 10% throughput improvement and 10-20% P99 pause time improvement have been reported.

Module Import Declarations (JEP 476, Preview)

Instead of importing packages with import java.util.*, this feature allows importing an entire module with a single line. It was introduced as a preview in JDK 23.

// JDK 23 Preview feature — compile: javac --enable-preview --source 23 ModuleImportDemo.java
// import module java.base; // One line to use all packages from java.base module

// Since it's a preview feature, the equivalent using traditional imports is shown
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class ModuleImportDemo {
    public static void main(String[] args) {
        // When Module Import is finalized, the 3 imports above become one line:
        // import module java.base;

        // Module import effect: java.util, java.util.stream, java.io, etc. all available
        List<String> frameworks = List.of("Spring", "Quarkus", "Micronaut", "Helidon");

        Map<Integer, List<String>> grouped = frameworks.stream()
            .collect(Collectors.groupingBy(String::length));

        System.out.println("Frameworks (grouped by name length):");
        grouped.forEach((len, names) ->
            System.out.println("  " + len + " chars: " + names));
        // Output:
        // Frameworks (grouped by name length):
        //   6 chars: [Spring]
        //   7 chars: [Quarkus, Helidon]
        //   10 chars: [Micronaut]
    }
}

Having 10 or 20 import lines in a Java project is common. When Module Import is finalized, a single import module java.base; line covers java.util, java.io, java.time, and other base packages, drastically reducing boilerplate.

Other Notable JEPs

Stream Gatherers (JEP 473, Second Preview): The gather() method that adds user-defined intermediate operations to the Stream API. It enables implementing window processing, stateful transformations, and more that are hard to express with map, filter, flatMap. This feature was finalized in JDK 24.

Implicitly Declared Classes (JEP 477, Third Preview): A feature for writing just void main() without class declarations. The goal is to lower Java’s entry barrier for education or scripting purposes. Finalized in JDK 25.

sun.misc.Unsafe Memory Access Methods Deprecation (JEP 471): Although warnings to “not use this” have been present for a long time, the memory access methods of sun.misc.Unsafe — which many libraries have used for performance — were officially deprecated. The alternative is the Foreign Function and Memory API (java.lang.foreign) finalized in JDK 22.

JDK 23 Performance Summary

ItemChange
ZGC throughput~10% improvement with generational default
ZGC P99 pause10-20% improvement
ZGC modeGenerational is default, non-generational maintained

Summary

FeatureStatusKey Points
Markdown JavadocFinal/// syntax for Markdown documentation comments
ZGC Generational DefaultFinalThroughput 10% up, P99 pause 10-20% down
Module ImportPreviewimport module java.base; for entire module import
Stream Gatherers2nd PreviewUser-defined Stream intermediate operations
Implicitly Declared Classes3rd PreviewWrite void main() without class declaration
Unsafe DeprecationFinalsun.misc.Unsafe memory methods deprecated

JDK 23 is non-LTS but brings many changes with direct practical impact. Markdown Javadoc can change documentation writing habits, and ZGC Generational default provides performance improvements without additional tuning. Module Import and Stream Gatherers, which appeared as previews, have been finalized in later versions — getting familiar with their syntax early is recommended.

Was this article helpful?