SoFunction
Updated on 2025-05-22

Share 29 practical features worth mastering from JDK9 to JDK21

The pace of evolution of Java has accelerated significantly since JDK9, and the pace of release of a new version every six months has brought a lot of new features to Java.

These include "star features" such as eye-catching virtual threads and record types, and there are also many small functions that are not very noticeable but equally practical.

This article has compiled 29 practical features worth mastering from JDK9 to JDK21 to help you write more concise, efficient and secure Java code.

JDK 9 Modularity and API Enhancement

1. Collection factory method: create an immutable collection with one line of code

Before JDK9, creating small immutable collections was quite cumbersome, and now only one line of code is required:

// Old wayList<String> list = (("Java", "Kotlin", "Scala"));
Map<String, Integer> map = (new HashMap<String, Integer>() {{
    put("Java", 1995);
    put("Kotlin", 2011);
    put("Scala", 2004);
}});

// JDK9 methodList<String> list = ("Java", "Kotlin", "Scala");
Set<String> set = ("Java", "Kotlin", "Scala");
Map<String, Integer> map = (
    "Java", 1995,
    "Kotlin", 2011,
    "Scala", 2004
);

For more key-value pairs, you can use()

Map<String, Integer> largeMap = (
    ("Java", 1995),
    ("Kotlin", 2011),
    ("Scala", 2004),
    ("Groovy", 2003)
    // ...There can be more entries);

Practical scenarios: Constant definition, configuration collection, test data preparation, API returns immutable results.

2. Private interface method: Interface code reuse is no longer awkward

JDK8 introduces the default interface method, and JDK9 further allows the interface to have private methods to facilitate multiplexing of code inside the interface:

public interface FileProcessor {
    // Public abstract method    void process(Path path);
    
    // Default method    default void processFile(String fileName) {
        validateFileName(fileName); // Reuse private methods for verification        process((fileName));
        log("Processed file: " + fileName);
    }
    
    default void processDirectory(String dirName) {
        validateFileName(dirName); // Reuse the same verification logic        try (Stream<Path> paths = ((dirName))) {
            (this::process);
        } catch (IOException e) {
            handleException(e); // Reuse private methods to handle exceptions        }
        log("Processed directory: " + dirName);
    }
    
    // Private method - for default method use    private void validateFileName(String fileName) {
        if (fileName == null || ()) {
            throw new IllegalArgumentException("File name cannot be empty");
        }
    }
    
    // Private static method    private static void log(String message) {
        ("[" + () + "] " + message);
    }
    
    private void handleException(Exception e) {
        log("Error: " + ());
    }
}

Practical scenarios: API design, logical reuse of interface default method, framework development.

3. Stream API enhancement: Stream operations are more flexible

JDK9 adds several practical methods to the Stream API:

// 1. takeWhile - Get elements from scratch until the conditions are not met(2, 4, 6, 8, 9, 10, 12)
      .takeWhile(n -> n % 2 == 0) // Results: [2, 4, 6, 8]      .forEach(::println);

// 2. dropWhile - discard elements from scratch until the condition is not satisfied(2, 4, 6, 8, 9, 10, 12)
      .dropWhile(n -> n % 2 == 0) // Results: [9, 10, 12]      .forEach(::println);

// 3. ofNullable - Safely create single element streams and process null values(null).count(); // 0
("Java").count(); // 1

// 4. Iterate method overload - iteration with termination conditions// Old methods need to use limit or filter to limit(1, n -> n * 2)
      .limit(5)
      .forEach(::println);

// The new method is more direct(1, n -> n < 100, n -> n * 2)
      .forEach(::println); // 1, 2, 4, 8, 16, 32, 64

Practical scenarios: Data processing pipeline, complex condition filtering, and bounded data generation.

4. (): Streaming replication is no longer cumbersome

Before JDK9, copying data between streams required manual processing of buffers, and now only one line of code is required:

// Old way - lengthy and easy to make mistakestry (InputStream is = new FileInputStream("");
     OutputStream os = new FileOutputStream("")) {
    byte[] buffer = new byte[8192];
    int length;
    while ((length = (buffer)) > 0) {
        (buffer, 0, length);
    }
}

// JDK9 method - simple and cleartry (InputStream is = new FileInputStream("");
     OutputStream os = new FileOutputStream("")) {
    (os); // One line of code is done}

Practical scenarios: File copy, network data transmission, stream processing.

5. Improved Process API: It's easier to manage system processes

JDK9 has greatly enhanced the Process API, making the interaction between Java programs and system processes more powerful:

// Get the current processProcessHandle current = ();
("Current PID: " + ());

// Get process information().user().ifPresent(user -> ("User: " + user));
().commandLine().ifPresent(cmd -> ("Command: " + cmd));
().startInstant().ifPresent(start -> ("Start time: " + start));
().totalCpuDuration().ifPresent(cpu -> ("CPU time: " + cpu));

// List all child processes().forEach(child -> ("Child PID: " + ()));

// List all processes()
    .filter(ph -> ().command().isPresent())
    .forEach(ph -> (() + ": " + ().command().get()));

// Start and wait for the process to completeProcessBuilder pb = new ProcessBuilder("ls", "-l");
Process process = ();
ProcessHandle handle = ();
boolean terminated = ().thenAccept(p -> 
    ("Process " + () + " terminated")
).isDone();

Practical scenarios: System management tools, daemons, executing external commands, monitoring applications.

JDK 10 Local variable inference

6. Local variable type inference (var): Say goodbye to long variable declarations

JDK10 introduces local variable type inference, usingvarKeywords let the compiler infer variable types:

// Old way - type duplicate and verboseHashMap<String, List<Customer>> customersByCity = new HashMap<>();
BufferedReader reader = new BufferedReader(new FileReader(""));
URLConnection connection = new URL("").openConnection();

// JDK10 method - simple and clearvar customersByCity = new HashMap<String, List<Customer>>();
var reader = new BufferedReader(new FileReader(""));
var connection = new URL("").openConnection();

// Especially useful in for loopsfor (var entry : ()) {
    var city = ();
    var customers = ();
    // ...
}

Things to note

  • varOnly used for local variables, not fields, method parameters, or return types
  • Variables must be initialized when declared
  • Do not overuse, type should be declared explicitly when it is not obvious

Best Practices

// Good usage - clear typevar customers = new ArrayList<Customer>();
var entry = ("key", "value");

// Use of avoidance - Type is unclearvar result = getResult(); // The return type is not obviousvar x = 1; // Recommended explicit declaration of basic types

Practical scenarios: Variables in complex generic types, anonymous classes, and lambda expressions.

7. Copying methods for collections that cannot be modified: Collection conversion is safer

JDK10 has added a collection frameworkcopyOfMethod, create a copy of the collection that cannot be modified:

// Original collectionList<String> original = new ArrayList<>(("Java", "Kotlin", "Scala"));
Set<Integer> originalSet = new HashSet<>((1, 2, 3));
Map<String, Integer> originalMap = new HashMap<>(("one", 1, "two", 2));

// Create an unmodified copyList<String> copy = (original);
Set<Integer> copiedSet = (originalSet);
Map<String, Integer> copiedMap = (originalMap);

// Modifying the original collection will not affect the copy("Groovy");
(original); // [Java, Kotlin, Scala, Groovy]
(copy);     // [Java, Kotlin, Scala]

// An exception will be thrown when trying to modify the copytry {
    ("Clojure"); // Throw UnsupportedOperationException} catch (UnsupportedOperationException e) {
    ("Cannot modify immutable copy");
}

and()different,()A brand new collection will be created. If the original collection is already unmodified, it may directly return to the original collection instead of the copy.

Practical scenarios: Defensive programming, return a secure collection copy, create a constant collection.

JDK 11 Long-term Support Edition Feature Enhancement

8. New String method: text handling is handy

JDK11 adds several practical methods to the String class:

// 1. lines() - split string by lineString multiline = "Java\nKotlin\nScala";
()
    .map(String::toUpperCase)
    .forEach(::println);

// 2. strip(), stripLeading(), stripTrailing() - Remove whitespace charactersString text = "  Hello World  ";
(">" + () + "<");          // >Hello World<
(">" + () + "<");   // >Hello World  <
(">" + () + "<");  // >  Hello World<

// The difference between strip() and trim(): strip() recognizes more Unicode whitespace charactersString unicodeWhitespace = "\u2005Hello\u2005";
(">" + () + "<");   // >⠀Hello⠀<
(">" + () + "<");  // >Hello<

// 3. isBlank() - Check whether the string is blank("  ".isBlank());     // true
("".isBlank());       // true
(" a ".isBlank());    // false

// 4. repeat() - repeat stringString star = "*";
((10));    // **********
("=".repeat(20));     // ====================

Practical scenarios: Process user input, parse text files, and build formatted output.

9. New Files method: One step to read and write files

JDK11 adds several convenient methods to the Files class:

// Read the file as StringString content = ((""));

// Write String to file((""), "Hello Java 11!");

// Use the specified encodingString content = ((""), StandardCharsets.UTF_8);
((""), "Logged at: " + (), 
                 StandardCharsets.UTF_8);

// Write string collectionList<String> lines = ("Line 1", "Line 2", "Line 3");
((""), lines);

Practical scenarios: Read configuration files, generate reports, logging, and fast file I/O.

10. Standard HTTP Client: Modern Network Request

JDK11 upgrades the HTTP Client from the incubation module to a standard API, providing a modern HTTP client:

// Create an HTTP clientHttpClient client = ()
    .connectTimeout((10))
    .build();

// Build GET requestHttpRequest request = ()
    .uri(("/users/octocat"))
    .header("User-Agent", "Java 11 HttpClient")
    .GET()
    .build();

// Send requests synchronously and receive JSON responsesHttpResponse<String> response = (request, 
    ());

("Status code: " + ());
("Body: " + ());

// POST request exampleHttpRequest postRequest = ()
    .uri(("/post"))
    .header("Content-Type", "application/json")
    .POST(("{"name": "Java"}"))
    .build();

// Asynchronously send requests(postRequest, ())
    .thenApply(HttpResponse::body)
    .thenAccept(::println)
    .join();

// Handle JSON responses (requires JSON library)(request, ())
    .thenApply(HttpResponse::body)
    .thenApply(body -> {
        // Use Jackson or Gson to parse JSON        return body;
    })
    .thenAccept(::println)
    .join();

Practical scenarios: RESTful API calls, microservice communication, network crawlers, data integration.

Improvements to JDK 12 language and library

11. (): Chain string processing

JDK12 added a String classtransformMethod, supports chain function conversion:

// Traditional wayString original = "hello, world!";
String result = ();
result = (0, 5);
result = result + "...";

//Use transform methodString result = "hello, world!"
    .transform(String::toUpperCase)
    .transform(s -> (0, 5))
    .transform(s -> s + "...");
(result); // HELLO...

// Complex conversionString parsed = "{ "name": "John", "age": 30 }"
    .transform(json -> {
        // parse JSON        // Simplify this, you should actually use Jackson, etc.        return (("name") + 7, ("age") - 3);
    })
    .transform(String::trim)
    .transform(String::toUpperCase);

(parsed); // JOHN

Especially useful for multi-step conversion, improving code readability.

Practical scenarios: Data conversion chain, complex string processing, functional data processing pipeline.

12. Compact Number Formatting: Readability representation of numbers

JDK12 introduces compact digital formatting capabilities that can format large numbers into more readable forms:

// Create a short format formatterNumberFormat shortFormatter = (
    , );

// Format numbers((1000));       // 1K
((1500));       // 2K (rounded)((1000000));    // 1M
((1000000000)); // 1B

// Long formatNumberFormat longFormatter = (
    , );
((1000));        // 1 thousand
((1000000));     // 1 million

// Format of other languagesNumberFormat germanFormatter = (
    , );
((1000));      // 1.000

NumberFormat chineseFormatter = (
    , );
((1000));     // 1,000((1000000));  // 1 million
// Custom accuracy(1);
((1234));       // 1.2K
((1500));       // 1.5K

Practical scenarios: User interface display, dashboard development, data visualization, and international applications.

JDK 14 Friendly Error Message and Language Improvements

13. Friendly NullPointerException: Say goodbye to the nightmare of null pointer debugging

JDK14 enhances NullPointerException, and the exception message will accurately indicate which variable is null:

// Suppose there is such a codeUser user = null;
String city = ().getCity();

Before JDK14 you will get a simple message:

Exception in thread "main" 
    at (:5)

In JDK14 and after, exception messages become very specific:

Exception in thread "main" : 
  Cannot invoke "()" because "user" is null
    at (:5)

For more complex expressions:

("key").process().getNestedValue();

The enhanced NPE message will specify which part is null:

Exception in thread "main" : 
  Cannot invoke "()" because the return value of 
  "()" is null

Practical scenarios: Debug complex object chains, troubleshoot third-party library errors, and shorten problem location time.

14. Switch expression: a more concise branch processing

JDK14 officially released switch expressions (originally introduced as a preview feature in JDK12):

// Traditional switch statementString result;
DayOfWeek day = ().getDayOfWeek();

switch (day) {
    case MONDAY:
    case TUESDAY:
    case WEDNESDAY:
    case THURSDAY:
    case FRIDAY:
        result = "Weekday";
        break;
    case SATURDAY:
    case SUNDAY:
        result = "Weekend";
        break;
    default:
        result = "Invalid day";
        break;
}

// New switch expressionString result = switch (day) {
    case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Weekday";
    case SATURDAY, SUNDAY -> "Weekend";
    default -> "Invalid day";
};

// Complex expressions with code blocksint numLetters = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> {
        ("Six letters day");
        yield 6;
    }
    case TUESDAY -> {
        ("Seven letters day");
        yield 7;
    }
    case THURSDAY, SATURDAY -> {
        ("Eight letters day");
        yield 8;
    }
    case WEDNESDAY -> {
        ("Nine letters day");
        yield 9;
    }
    default -> {
        throw new IllegalStateException("Invalid day: " + day);
    }
};

Key advantages

  • Can return values ​​as expressions
  • Arrow grammar is simpler
  • Use commas to merge multiple cases
  • No break statement is required, eliminating common sources of errors
  • Exhaust checks to ensure that all situations are handled

Practical scenarios: State machine implementation, command processing, configuration analysis, and business logic dispatch.

15. Records: Data classes are no longer verbose

JDK14 introduced Records as a preview feature and was officially released in JDK16, providing a concise syntax for immutable data classes:

// Traditional POJO classpublic final class Employee {
    private final String name;
    private final int id;
    private final Department department;
    
    public Employee(String name, int id, Department department) {
         = name;
         = id;
         = department;
    }
    
    public String getName() { return name; }
    public int getId() { return id; }
    public Department getDepartment() { return department; }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != ()) return false;
        Employee employee = (Employee) o;
        return id ==  && 
               (name, ) && 
               (department, );
    }
    
    @Override
    public int hashCode() {
        return (name, id, department);
    }
    
    @Override
    public String toString() {
        return "Employee{" +
               "name='" + name + ''' +
               ", , department=" + department +
               '}';
    }
}

// Use Recordpublic record Employee(String name, int id, Department department) { }

Records automatically generates constructor, accessor, equals/hashCode and toString methods.

You can also add additional constructors, methods and static members to Record:

public record Point(int x, int y) {
    // Custom compact constructor    public Point {
        if (x < 0 || y < 0) {
            throw new IllegalArgumentException("Coordinates cannot be negative");
        }
    }
    
    // Reload constructor    public Point() {
        this(0, 0);
    }
    
    // Instance method    public double distance(Point other) {
        return (( - , 2) + 
                          ( - , 2));
    }
    
    // Static member    public static final Point ORIGIN = new Point(0, 0);
    
    // Static method    public static Point of(int x, int y) {
        return new Point(x, y);
    }
}

Practical scenarios: DTO object, API response model, message body, immutable data container, value object.

JDK 15-16 Text and Type Check Optimization

16. Text block: Multi-line strings no longer suffer

JDK15 officially released the text block function (previewed for the first time in JDK13), making multi-line strings simple and elegant:

// Traditional multi-line stringString json = "{\n" +
              "  "name": "John Doe",\n" +
              "  "age": 30,\n" +
              "  "address": {\n" +
              "    "street": "123 Main St",\n" +
              "    "city": "Anytown"\n" +
              "  }\n" +
              "}";

// Use text blocksString json = """
              {
                "name": "John Doe",
                "age": 30,
                "address": {
                  "street": "123 Main St",
                  "city": "Anytown"
                }
              }
              """;

// HTML exampleString html = """
              <html>
                <body>
                  <h1>Hello, World!</h1>
                </body>
              </html>
              """;

// SQL queryString query = """
               SELECT id, first_name, last_name
               FROM employees
               WHERE department_id = ?
               ORDER BY last_name, first_name
               """;

Text blocks also support string interpolation and format control:

// Use\avoid line wrapping at the end of the lineString compact = """
                 <html>\
                 <body>\
                 <p>Hello</p>\
                 </body>\
                 </html>\
                 """;

// Use with String::formattedString template = """
                  Dear %s,
                  
                  Your order #%d has been shipped on %s.
                  
                  Thank you,
                  Customer Service
                  """;
                  
String message = ("John", 12345, "2023-05-15");

Practical scenarios: SQL query, HTML/JSON/XML templates, code generation, multi-line text configuration.

17. instanceof pattern matching: type checking and conversion are combined into one

JDK16 officially released instanceof pattern matching simplifies type checking and conversion:

// Traditional wayif (obj instanceof String) {
    String s = (String) obj;
    if (() > 5) {
        (());
    }
}

// Use pattern matchingif (obj instanceof String s && () > 5) {
    (());
}

// Use in complex conditionsif (obj instanceof String s && () > 10 
    || obj instanceof List<?> list && () > 5) {
    // Use s or list}

// Use with switch (JDK17 preview feature)Object value = getValue();
switch (value) {
    case String s when () > 5 -> ("Long string: " + s);
    case String s -> ("Short string: " + s);
    case List<?> l -> ("List with " + () + " elements");
    default -> ("Unknown type");
}

Practical scenarios: Polymorphic object processing, type safety conversion, simplification of empty checking.

18. Foreign Memory Access API: Safe and efficient local memory operations

JDK16 introduced the Foreign Memory Access API (incubator stage), providing Java with secure and efficient local memory access capabilities:

// Allocate off-heap memorytry (Arena arena = ()) {
    // Allocate 100 bytes of local memory    MemorySegment segment = (100);
    
    // Write data    (new byte[] {1, 2, 3, 4, 5}, 0, segment, 0, 5);
    
    // Read data    byte value = (ValueLayout.JAVA_BYTE, 2); // Read the value of index 2    ("Value at index 2: " + value);   // Output 3    
    // Fill in memory segments    (segment, (byte) 10);
    
    // Use VarHandle to operate memory    VarHandle intHandle = ValueLayout.JAVA_INT.varHandle();
    (segment, 0, 42);
    int result = (int) (segment, 0);
    ("Integer value: " + result);     // Output 42    
    // Memory address operation    long address = ().toRawLongValue();
    ("Memory address: 0x" + (address));
}

This API has been improved in JDK17 and was officially released in JDK21. It provides a more powerful alternative to ByteBuffer for applications that need to process large amounts of data.

Practical scenarios: High-performance computing, big data processing, network applications, integration with local libraries.

Powerful features of JDK 17 long-term support version

19. Sealed Classes: Precisely control inheritance relationships

The sealed class officially released by JDK17 allows more precise control of which classes can inherit a class:

// Declare a sealed interface, allowing only specific classes to implement itpublic sealed interface Shape 
    permits Circle, Rectangle, Triangle {
    double area();
}

// The final implementation class is not allowed further inheritancepublic final class Circle implements Shape {
    private final double radius;
    
    public Circle(double radius) {
         = radius;
    }
    
    @Override
    public double area() {
        return  * radius * radius;
    }
}

// Classes that allow further inheritancepublic non-sealed class Rectangle implements Shape {
    private final double width;
    private final double height;
    
    public Rectangle(double width, double height) {
         = width;
         = height;
    }
    
    @Override
    public double area() {
        return width * height;
    }
}

// Restrict inheritance classespublic sealed class Triangle implements Shape 
    permits EquilateralTriangle, RightTriangle {
    // ...accomplish...}

// Allowed subclassespublic final class EquilateralTriangle extends Triangle {
    // ...accomplish...}

public final class RightTriangle extends Triangle {
    // ...accomplish...}

Sealed classes are particularly powerful when used in combination with switch pattern matching and record classes, and the compiler can perform exhaustive checks:

double calculateArea(Shape shape) {
    return switch (shape) {
        case Circle c ->  * () * ();
        case Rectangle r -> () * ();
        case Triangle t -> () * () / 2;
        // No default branch is needed, because the compiler knows that these are all possible implementations of Shape    };
}

Practical scenarios: Domain model design, type-safe API, state machine implementation, compiler inspection enhancement.

20. Enhanced pseudo-random number generator: more flexible and predictable random numbers

JDK17 introduces an enhanced pseudo-random number generator (PRNG) framework, providing more algorithms and better interfaces:

// Get the default random number generatorRandomGenerator random = ();
((100)); // Random number between 0-99
// Generator using specific algorithmsRandomGenerator xoroshiro = ("Xoroshiro128PlusPlus");
(());

// Using L32X64MixRandom - an algorithm that balances speed and qualityRandomGenerator fastRandom = ("L32X64MixRandom");
for (int i = 0; i < 5; i++) {
    ((1000));
}

// Create a reproducible random number sequence (using the same seed)RandomGenerator seeded = ("Xoshiro256PlusPlus");
((SplittableRandomGenerator) seeded).setSeed(42);
for (int i = 0; i < 5; i++) {
    ((100));
}

// Generate random streamsDoubleStream randomDoubles = ().doubles(1000);
(::println);

// View all available algorithms()
    .map(provider -> () + ": " + ())
    .sorted()
    .forEach(::println);

Practical scenarios: Scientific computing, simulation, game development, test data generation, encryption applications.

21. Vector API (Incubator): Performance-intensive computing

JDK17 introduces a vector API in the incubator stage, which supports SIMD (single instruction, multiple data) style operations, significantly accelerating specific types of calculations:

// Use IntVector to speed up array summingstatic int sumArrayVectorized(int[] a) {
    var species = IntVector.SPECIES_PREFERRED;
    var sum = (species);
    var i = 0;
    
    // Process the vectorized parts    for (; i <=  - (); i += ()) {
        var v = (species, a, i);
        sum = (v);
    }
    
    // Process the remaining elements    var result = ();
    for (; i < ; i++) {
        result += a[i];
    }
    
    return result;
}

// Vectorized matrix multiplicationstatic void multiplyMatricesVectorized(float[] a, float[] b, float[] c, int n) {
    var species = FloatVector.SPECIES_PREFERRED;
    int limit = ();
    
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            var sum = (species);
            int k = 0;
            
            // Use vector calculation            for (; k <= n - limit; k += limit) {
                var va = (species, a, i * n + k);
                var vb = (species, b, k * n + j);
                sum = ((vb));
            }
            
            //Accumulate vector results            float dotProduct = ();
            
            // Process the remaining elements            for (; k < n; k++) {
                dotProduct += a[i * n + k] * b[k * n + j];
            }
            
            c[i * n + j] = dotProduct;
        }
    }
}

JDK19 and JDK21 continue to improve the vector API, but as of JDK21 is still in the incubator stage.

Practical scenarios: Scientific computing, image processing, machine learning, signal processing, financial simulation.

JDK 18-19 Toolchain and API Enhancement

22. Simple Web Server: Quickly start static file service

JDK18 introduces a simple command-line HTTP server that can quickly start static file services:

// Command line usage// jwebserver -p 8000 -d /path/to/directory

// Use in the code:import .*;
import ;
import ;
import ;

public class SimpleFileServer {
    public static void main(String[] args) throws IOException {
        var server = (
            new InetSocketAddress(8000), 
            ("/path/to/directory"), 
            
        );
        ();
        ("Server started at http://localhost:8000");
    }
}

This feature is especially suitable for development and testing environments, such as fast previewing of static websites or testing API calls.

Practical scenarios: Front-end development, static website preview, local testing environment, prototype development.

23. Code snippets in JavaDoc: Better API documentation

JDK18 has introduced@snippetTags, allowing to add code examples with syntax highlighting in JavaDoc:

/**
 * This class provides utility methods for string operations.
 *
 * <p>Example usage:</p>
 * {@snippet :
 *   String result = ("hello");  // Returns "Hello"
 *   boolean isEmpty = (" ");       // Returns true
 * }
 */
public class StringUtils {
    // Class implementation omitted}

Enhanced documents also support highlighting, area marking, and error marking:

/**
 * Example of snippet with highlighting:
 * {@snippet :
 *   // @highlight region="important"
 *   String encoded = ().encodeToString(data);
 *   // @end
 *   
 *   // @highlight regex="data" type="bold"
 *   byte[] decoded = ().decode(encoded);
 *   
 *   // @replace regex="badPractice()" replacement="goodPractice()" type="error"
 *   result = badPractice();
 * }
 */

Practical scenarios: API documentation, open source projects, technical guides, tutorial writing.

24. Foreign Function & Memory API

JDK19 improves the external function interface in the incubator (officially released in JDK21), making it easier to interact with Java with local code:

// Define the interface for C library functionsimport .*;
import static .*;

public class LibCDemo {
    public static void main(String[] args) {
        // Get the linker for the C standard library        Linker linker = ();
        
        // Find printf function        SymbolLookup stdlib = ();
        MethodHandle printf = ("printf")
            .map(addr -> (
                addr,
                (JAVA_INT, ADDRESS),
                (1)
            ))
            .orElseThrow();
        
        // Prepare string parameters        try (Arena arena = ()) {
            MemorySegment cString = arena.allocateUtf8String("Hello from Java! Count: %d\n");
            
            // Call printf            try {
                (cString, 42);
            } catch (Throwable e) {
                ();
            }
        }
    }
}

This API removes most of the complexity of JNI and provides a safer and cleaner way to call native code.

Practical scenarios: Integration with C/C++ libraries, system programming, performance-critical applications, multilingual projects.

JDK 20-21 Modern Concurrency and Language Enhancement

25. Virtual Threads: Concurrency Revolution

JDK21 officially released virtual threads, which is a major change in Java concurrent programming:

// Create and start a single virtual thread(() -> {
    ("Running in virtual thread");
});

// Run multiple tasks using virtual threadtry (var executor = ()) {
    // Submit 1000 tasks, each running in an independent virtual thread    for (int i = 0; i < 1000; i++) {
        int taskId = i;
        (() -> {
            ("Task " + taskId + " running on " + ());
            // Simulate IO operations            try {
                ((100));
            } catch (InterruptedException e) {
                ().interrupt();
            }
            return taskId;
        });
    }
} // Automatically close the executor
// Create virtual threads in builderThreadFactory factory = ().name("worker-", 0).factory();
Thread worker = (() -> {
    // Task code});
();

//Use virtual threads to overwrite traditional blocking IO codevoid processFile(Path file) throws IOException {
    // This code will not block the platform thread when running in a virtual thread    try (var reader = (file)) {
        String line;
        while ((line = ()) != null) {
            processLine(line);
        }
    }
}

The main advantage of virtual threads is that they can create millions of lightweight threads without exhausting system resources. They are particularly suitable for IO-intensive applications, allowing synchronous code to achieve comparable scalability to asynchronous code.

Practical scenarios: High concurrent web server, microservice, data processing pipeline, crawler programs.

26. Structured Concurrency

Can manage the life cycle of asynchronous tasks

JDK21 introduces a structured concurrency API (preview feature), which simplifies error handling and resource management of multi-threaded code:

// Get users and their orders in parallelrecord User(int id, String name) {}
record Order(int id, double amount) {}
record UserWithOrders(User user, List<Order> orders) {}

UserWithOrders getUserWithOrders(int userId) {
    try (var scope = new ()) {
        // Execute two subtasks in parallel        Future<User> userFuture = (() -> fetchUser(userId));
        Future<List<Order>> ordersFuture = (() -> fetchOrders(userId));
        
        // Wait for all tasks to complete        ();
        // Check whether there are any exceptions in the subtask        (e -> new RuntimeException("Failed to fetch data", e));
        
        // Get results        return new UserWithOrders((), ());
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

// Use structured concurrency to process multiple API callstry (var scope = new ()) {
    var weatherFuture = (() -> callWeatherAPI());
    var trafficFuture = (() -> callTrafficAPI());
    var newsFuture = (() -> callNewsAPI());
    
    try {
        ();
        ();
        
        // All API calls are successful and the processing results are processed        var dashboard = createDashboard(
            (),
            (),
            ()
        );
        return dashboard;
    } catch (Exception e) {
        // There is an API call that fails and all tasks are cancelled        return createFallbackDashboard();
    }
}

Structured concurrency ensures that all child tasks are either completed or cancelled before the parent task exits, avoiding resource leaks and "forgotten" background tasks.

Practical scenarios: API aggregation, microservice communication, parallel data processing, complex asynchronous workflows.

27. Record Patterns: Deconstructing data is easier

The Record pattern officially released by JDK21 allows deconstructing records in pattern matching:

// Define some recordsrecord Point(int x, int y) {}
record Rectangle(Point topLeft, Point bottomRight) {}
record Circle(Point center, int radius) {}

// Use traditional methods to process recordsObject shape = new Rectangle(new Point(1, 2), new Point(5, 6));
if (shape instanceof Rectangle) {
    Rectangle r = (Rectangle) shape;
    Point topLeft = ();
    Point bottomRight = ();
    int width = () - ();
    int height = () - ();
    ("Rectangle with width " + width + " and height " + height);
}

// Destruct using Record modeif (shape instanceof Rectangle(Point(var x1, var y1), Point(var x2, var y2))) {
    int width = x2 - x1;
    int height = y2 - y1;
    ("Rectangle with width " + width + " and height " + height);
}

// Use in combination with switchString getDescription(Object shape) {
    return switch (shape) {
        case Rectangle(Point(var x1, var y1), Point(var x2, var y2)) ->
            "Rectangle from (%d,%d) to (%d,%d)".formatted(x1, y1, x2, y2);
        
        case Circle(Point(var x, var y), var r) ->
            "Circle at (%d,%d) with radius %d".formatted(x, y, r);
        
        default -> "Unknown shape";
    };
}

Record mode is particularly powerful when used in conjunction with nested mode, allowing easy handling of complex data structures.

Practical scenarios: Data conversion, JSON/XML parsing result processing, domain model operation, event processing.

28. String Templates: Safe and efficient string interpolation

JDK21 introduces string templates as a preview feature, providing a simpler and safer way than string concatenation:

// Traditional wayString name = "Alice";
int age = 30;
String message = "Hello, " + name + "! Next year, you'll be " + (age + 1) + ".";

// Use string templateString message = STR."Hello, {name}! Next year, you'll be {age + 1}.";

// Template with expressionString status = "active";
String message = STR."User status: {()} (set {()})";

// Format numbersdouble value = 1234.56789;
String formatted = STR."The value is {value%.2f}";  // "The value is 1234.57"

// Multi-line JSONString json = STR."""
    {
      "name": "{name}",
      "age": {age},
      "isAdult": {age >= 18},
      "contacts": [
        {generateContactsJson()}
      ]
    }
    """;

String templates are not only syntax concise, but also provide type safety and compile-time checking of expressions.

Practical scenarios: Generate JSON/XML/HTML, logging, message formatting, and SQL statement construction.

29. Sequenced Collections: Unified Collection Operations

JDK21 has been introducedSequencedCollectionSequencedSetandSequencedMapInterface, adds directional and sequential operations to the Java collection framework:

// Basic usage of serialized collectionsSequencedCollection<String> names = new ArrayList<>(("Alice", "Bob", "Charlie"));

// Get the first and last elementsString first = ();  // "Alice"
String last = ();    // "Charlie"

// Add elements to both ends("Zoe");
("David");
(names);  // [Zoe, Alice, Bob, Charlie, David]

// Create a reverse viewSequencedCollection<String> reversed = ();
(reversed);  // [David, Charlie, Bob, Alice, Zoe]

// Serialize MapSequencedMap<String, Integer> scores = new LinkedHashMap<>();
("Alice", 95);
("Bob", 85);
("Charlie", 90);

// Get the first and last entry<String, Integer> firstEntry = ();  // Alice=95
<String, Integer> lastEntry = ();    // Charlie=90

// Add entries to both ends("Zoe", 100);
("David", 80);

// Get a serialized view of keys or valuesSequencedCollection<String> keys = ();
SequencedCollection<Integer> values = ();

These interfaces unify sequential operations in the Java collection framework, making the API more consistent. Existing ordered collection classes such asArrayListLinkedHashSetandLinkedHashMapAll of these new interfaces are implemented.

Practical scenarios: Sets that maintain insertion order, FIFO/LIFO queue operation, bidirectional iteration, and ordered data processing.

Summarize

While maintaining backward compatibility, Java language continues to introduce new features to improve development efficiency and code quality. Mastering these new features not only improves development efficiency, but also writes more concise and robust code.

This is the article about the 29 practical features worth mastering in JDK9 to JDK21. For more relevant content on JDK practical features, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!