From Java 17 to Java 21 And How To Do It: A Comprehensive API Comparison

Introduction

As we all know, Java is a constantly evolving programming language. With each new release, we get a plethora of new features, enhancements, and sometimes, a few deprecations. In this blog post, we will analyse the significant differences between Java 17 and Java 21 API. We will navigate through the changes, with a focus on deprecated features, new library additions, security enhancements, and performance improvements.

Released on September 19, 2023, Java 21 is celebrated for its comprehensive set of specifications that define the behaviour of the Java language, API, and virtual machine. It represents a Long Term Support (LTS) version, ensuring extended updates and support from various vendors, making it a pivotal release for developers and organizations alike.

Overview of Key API Changes from Java 17 to Java 21

Deprecated/Removed Features

One of the key aspects of Java’s evolution is the deprecation of features that are no longer relevant or have been replaced by more efficient alternatives. In the transition from Java 17 to Java 21, several features, methods and classes have been deprecated or removed. These are the following:

  1. ObjectOutputStream.PutField
  2. write(ObjectOutput)Marked for removal
  3. Enum.finalize()Deprecated and marked for removal
  4. Runtime.exec(String)Deprecated
  5. Runtime.exec(String, String[])Deprecated
  6. Runtime.exec(String, String[], File)Deprecated
  7. Runtime.runFinalization()Deprecated and marked for removal
  8. System.runFinalization()Deprecated and marked for removal
  9. ThreadDeathDeprecated and marked for removal
  10. ThreadGroup.allowThreadSuspension(boolean)Removed
  11. Thread.countStackFrames()Removed
  12. Thread.getId()Deprecated
  13. Thread.resume()Removed
  14. Thread.stop()Marked for removal
  15. Thread.suspend()Removed
  16. URL(String)Deprecated
  17. URL(String, String, String)Deprecated
  18. URL(String, String, int, String)Deprecated
  19. URL(String, String, int, String, URLStreamHandler)Deprecated
  20. URL(URL, String)Deprecated
  21. URL(URL, String, URLStreamHandler)Deprecated
  22. java.security.spec.PSSParameterSpec.DEFAULTDeprecated
  23. PSSParameterSpec(int)Deprecated
  24. MemoryMXBean.getObjectPendingFinalizationCount()Deprecated

Also the following Classes were removed

  1. ClassSpecializer.Factory
  2. ClassSpecializer.SpeciesData
  3. Compiler
  4. FdLibm.Cbrt
  5. FdLibm.Hypot
  6. FdLibm.Pow
  7. MLetContent
  8. MLet
  9. PrivateMLet
  10. MLetMBean
  11. javax.management.remote.rmi.RMIConnector.getMBeanServerConnection(Subject)
  12. javax.management.remote.RMIIIOPServerImpl
  13. javax.management.remote.JMXConnector.getMBeanServerConnection(Subject)

Replacements for Deprecated/Removed Features

Deprecated and Marked for Removal:

  • ObjectOutputStream.PutField (No Replacement): While there’s no direct replacement, consider using a custom serialization mechanism or a library like Kryo if object serialization is crucial.
  • write(ObjectOutput) (Potential Replacement): For custom serialization scenarios, explore alternatives like writeObject(Object) or writeUnshared(Object).
  • Enum.finalize(), Runtime.runFinalization(), System.runFinalization(), ThreadDeath (No Replacement): Finalization is deprecated and might be removed in future versions. Implement proper resource management (e.g., using try-with-resources) to avoid relying on finalization for cleanup.

Deprecated Methods with Alternatives:

  • Runtime.exec(String), Runtime.exec(String, String[]), Runtime.exec(String, String[], File): Use ProcessBuilder
    ProcessBuilder processBuilder = new ProcessBuilder(command, arguments);
    Process process = processBuilder.start();
    // Handle process output and errors as needed
  • Thread.getId() (Use getName(), prefer isAlive() for checking thread status):
    String threadName = Thread.currentThread().getName(); // Get current thread name
    boolean isThreadAlive = Thread.currentThread().isAlive(); // Check if thread is alive
  • URL(String), URL(String, String, String), etc. (Use URI or new URL(String, URLClassLoader)):
    For constructing URLs with a specific class loader:
    URL url = new URL("https://example.com/", MyClassLoader.class.getClassLoader());

Removed Classes/Methods (No Direct Replacements):

  • ClassSpecializer.Factory, ClassSpecializer.SpeciesData, Compiler, FdLibm.* (Math functions), MLet*, javax.management.remote.* (RMI-related classes): These are internal or legacy classes that are no longer supported. If you rely on these functionalities, you might need to explore alternative libraries or workarounds depending on your specific use case.

New Features

Java 21 introduces several new features

  • transferTo(OutputStream): Java 21 introduces the a method, facilitating efficient data transfer between input and output streams without the need for an intermediary buffer in user space, thereby improving I/O efficiency
  • FileInputStream & SequenceInputStream: Similar to `BufferedInputStream`, these classes now include the `transferTo(OutputStream)` method, streamlining the process of transferring data directly to an output stream.

Console and PrintStream Enhancements

Console

Transitioning from `final` to `sealed`, this change in the Console class introduces more controlled subclassing, allowing developers to extend functionality within a restricted hierarchy

PrintStream

The introduction of the `charset()` method in `PrintStream` allows developers to ascertain the charset used by the PrintStream, enhancing support for internationalization.

Serialization Improvements

  • InvalidClassException & InvalidObjectException: Java 21 enhances exception handling in serialization with new constructors in these exceptions, enabling more detailed error reporting by including a cause for the exception.
  • ObjectInputStream.GetField: The `get(String, Object)` method now throws `ClassNotFoundException`, improving error handling during deserialization by explicitly indicating class-related issues.

Reflection and Access Flags

  • Executable & Field: The addition of the `accessFlags()` method in reflective classes like `Executable` and `Field` allows developers to retrieve access flags of classes, methods, or fields at runtime, offering deeper insights into runtime behavior.
  • Parameter: Reflecting the changes in `Executable` and `Field`, the `Parameter` class now includes an `accessFlags()` method, further aligning with the enhanced reflective capabilities introduced in Java 21.

Unicode Enhancements in `Character` Class

  • Emoji Support: Java 21 significantly expands its support for Unicode, introducing methods like `isEmoji(int)`, `isEmojiComponent(int)`, and `isEmojiPresentation(int)` in the `Character` class. These methods enable applications to better handle, validate, and render emoji characters, catering to modern digital communication standards.

Foreign Function & Memory API (Preview)

  •  java.lang.foreign: Marked as a preview feature, this package is a significant addition in Java 21, laying the groundwork for improved interaction between Java programs and native code/libraries. This API simplifies the process of invoking foreign functions and managing native memory, promising to enhance performance and reduce the complexity of native code integration .

Usage Scenarios

Let’s see some specific usage scenarios where the new features and API changes introduced in Java 21 can be particularly beneficial. These scenarios will illustrate how developers can leverage these enhancements to address real-world development challenges.

Usage Scenario 1: Efficient Data Transfer in Network Applications

Scenario: A developer is working on a network application that requires the efficient transfer of large files from a server to multiple clients. The application needs to maximize throughput and minimize latency.

Java 21 Solution: Utilize the `transferTo(OutputStream)` method in classes such as `BufferedInputStream` for efficient data transfer. This method allows for direct transfer of bytes between streams, reducing the overhead of copying data to an intermediate buffer.

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class FileServer {

  public static void main(String[] args) throws IOException {
    try (ServerSocket serverSocket = new ServerSocket( /* port number */ );
         Socket clientSocket = serverSocket.accept();
         BufferedInputStream fileInput = new BufferedInputStream(new FileInputStream("largeFile.bin"));
         OutputStream clientOutput = clientSocket.getOutputStream()) {
      fileInput.transferTo(clientOutput);
      System.out.println("File transferred successfully to client.");
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

Benefits: This approach minimizes the I/O overhead and system resource utilization, leading to faster file transfers and improved application performance.

Usage Scenario 2: Advanced Debugging with Reflective Access Flags

Scenario: A development team is building a custom framework that relies heavily on reflection for dynamic operation. They need a way to debug and validate the access levels of various members (methods, fields, etc.) at runtime to ensure correct behavior.

Java 21 Solution: Use the `accessFlags()` method from reflective classes (`Method`, `Field`) to get detailed access level information. This can help in logging, debugging, or runtime validation of application components.

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class MyClassInspector {

  public static void main(String[] args) throws ClassNotFoundException {
    Class<?> clazz = Class.forName("MyClass"); // Replace "MyClass" with the actual class name
    Method[] methods = clazz.getDeclaredMethods();

    for (Method method : methods) {
      int flags = method.getModifiers();
      System.out.printf("Method: %s, Access Flags: %s%n", method.getName(), Modifier.toString(flags));
    }
  }
}

Benefits: This capability enhances debugging and introspection, allowing developers to programmatically check and handle access levels, leading to more robust and secure applications.

Usage Scenario 3: Internationalization and Emoji Support

Scenario: A social media platform aims to enhance its text processing capabilities to better support emojis, ensuring that user-generated content including emojis is correctly validated and displayed across different devices.

Java 21 Solution: Leverage the expanded Unicode and emoji support in the `Character` class to identify, validate, and process emojis within user content.

String userComment = "Hello world!";

userComment.codePoints().forEach(cp -> {
  if (Character.isEmoji(cp)) {
    System.out.printf("Found an emoji: %s%n", new String(Character.toChars(cp)));
  }
});

Benefits: By accurately processing emojis, the platform can enhance user experience, ensuring correct display and interaction with emojis across various devices and locales, thereby supporting global user engagement.  ### Conclusion  The scenarios outlined above demonstrate the practical application of Java 21’s new features in addressing common development challenges. Whether it’s through more efficient data handling, improved debugging and validation mechanisms, or enhanced support for global character sets and emojis, Java 21 provides developers with powerful tools to build more efficient, secure, and user-friendly applications.  As Java continues to evolve, staying abreast of these changes and understanding how to apply them in real-world contexts is crucial for developers looking to maximize their productivity and leverage the full power of the Java platform.

The Generational Z Garbage Collector (ZGC)

The Generational Z Garbage Collector (GenZGC) in JDK 21 represents a significant evolution in Java’s approach to garbage collection. It builds upon the Z Garbage Collector (ZGC) by introducing a generational approach, aiming to enhance application performance through more efficient memory management.

Generational ZGC leverages the “weak generational hypothesis,” which suggests that most objects die young. By dividing the heap into young and old regions, GenZGC can focus on the young region where most objects become unreachable, thereby optimizing garbage collection efficiency and reducing CPU overhead. This division allows for more frequent collection of short-lived objects while reducing the overhead of collecting long-lived objects. Internal performance tests have shown that Generational ZGC offers about a 10% improvement in throughput over its single-generation predecessors. A crucial advantage is its ability to mitigate allocation stalls, which significantly benefits high-throughput applications.

While JDK 21 introduces Generational ZGC, single-generation ZGC remains the default for now. Developers can opt into using Generational ZGC through JVM arguments. The plan is for Generational ZGC to eventually become the default. Tools like GC logging and JDK Flight Recorder (JFR) offer valuable insights into GC behavior and performance for those evaluating or transitioning to Generational ZGC.

Generational ZGC represents a significant step forward in Java’s garbage collection technology, offering improved throughput, reduced pause times, and enhanced overall application performance. Its design reflects a deep understanding of application memory management needs. As Java applications continue to grow in complexity and scale, the adoption of Generational ZGC could be a pivotal factor in achieving the performance goals of modern, high-demand applications.

Conclusion

The transition from Java 17 to Java 21 heralds a new era of Java development, characterized by significant improvements in performance, security, and developer-friendly features. The API changes and enhancements discussed above are just the tip of the iceberg, with Java 21 offering a wealth of other features and improvements designed to cater to the evolving needs of modern application development.  As developers, embracing Java 21 and leveraging its new features and improvements can significantly impact the efficiency, performance, and security of Java applications. Whether it’s through the enhanced I/O capabilities, improved serialization exception handling, or the new Unicode support in the `Character` class, Java 21 offers a compelling upgrade path from Java 17, promising to enhance the Java ecosystem for years to come.  In conclusion, the evolution from Java 17 to Java 21 is a testament to the ongoing commitment to advancing Java as a language and platform. By exploring and adopting these new features, developers can ensure their Java applications remain cutting-edge, secure, and performant in the face of future challenges.

Passionate Archer, Runner, Linux lover and JAVA Geek! That's about everything! Alexius Dionysius Diakogiannis is a Senior Java Solutions Architect and Squad Lead at the European Investment Bank. He has over 20 years of experience in Java/JEE development, with a strong focus on enterprise architecture, security and performance optimization. He is proficient in a wide range of technologies, including Spring, Hibernate and JakartaEE. Alexius is a certified Scrum Master and is passionate about agile development. He is also an experienced trainer and speaker, and has given presentations at a number of conferences and meetups. In his current role, Alexius is responsible for leading a team of developers in the development of mission-critical applications. He is also responsible for designing and implementing the architecture for these applications, focusing on performance optimization and security.

Leave a Reply

Your email address will not be published. Required fields are marked *

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