Significant Changes in JDK 9 to 21

28 Feb 2024

Major New Functionality

  1. Java 9 introduces a new level of abstraction above packages, formally known as the Java Platform Module System (JPMS), or “Modules” for short.
  2. JAXB and JAX-WS are no longer bundled with JDK 11 and higher.
  3. The Garbage-First Garbage Collector (G1 GC) is the default garbage collector in JDK 9 and later releases.
  4. The use of the ‘var’ in variable declarations makes writing code simple and error-free.
  5. The switch statement was extended so that it can be used either as a statement or an expression. This is a preview language feature.
///////////////// old way /////////////////
int number =  3;
String result;

switch (number) {
    case  1:
        result = "one";
        break;
    case  2:
        result = "two";
        break;
    default:
        result = "many";
}
System.out.println(result); // Outputs "many"

///////////////// new way /////////////////

int number =  3;
String result = switch (number) {
    case  1 -> "one";
    case  2 -> "two";
    default -> "many";
};

System.out.println(result); // Outputs "many"

Then, Pattern Matching for switch Expressions and Statements has been introduced

public class Shape {
    enum Color { RED, GREEN, BLUE }

    record Circle(Color color, int radius) {}
    record Rectangle(Color color, int width, int height) {}

    public static void main(String[] args) {
        var shape = new Circle(Color.RED,  5);

        var description = switch (shape) {
            case Circle c && c.color() == Color.RED -> "Red circle";
            case Rectangle r && r.color() == Color.BLUE -> "Blue rectangle";
            case Circle c -> "Circle of color " + c.color();
            case Rectangle r -> "Rectangle of color " + r.color();
            default -> "Unknown shape";
        };

        System.out.println(description);
    }
}
  1. The NumberFormat added support for formatting a number in its compact form.
double number =  1000;
        
// Create a NumberFormat instance for the current locale
NumberFormat nf = NumberFormat.getCompactNumberInstance(new Locale("en", "US"), NumberFormat.Style.SHORT);
        
// Format the number
String formattedNumber = nf.format(number);
System.out.println(formattedNumber); // Outputs "1K"
  1. Text blocks were added to Java language, which provide developers with control over the format when desired.
String html = """
              <html>
                  <body>
                      <p>Hello, world!</p>
                  </body>
              </html>
              """;
  1. The implementation used by java.net.Socket and java.net.ServerSocket APIs was replaced with a simpler and more modern implementation that is easy to maintain and debug.
  2. Adding Records , which provides a compact syntax for declaring classes that are transparent holders for shallowly immutable data.
  3. Pattern Matching for instanceof that simplifies the instanceof-and-cast idiom.
if (obj instanceof String s) {
    System.out.println("The object is a String: " + s);
  1. Sealed Classes is a Java language preview feature. Sealed classes and interfaces restrict which other classes or interfaces may extend or implement them.
  2. The floating-point operations are now consistently strict, it means that the language adheres more closely to the IEEE 754 standard for floating-point arithmetic, which defines how floating-point numbers should be represented and operated on in computer systems.
public class FloatingPointComparisonExample {
    private static final double EPSILON =  1E-10; // Define a small threshold value

    public static boolean almostEqual(double a, double b) {
        return Math.abs(a - b) <= EPSILON;
    }

    public static void main(String[] args) {
        double x =  0.1 *  10;
        double y =  1.0;

        // Direct comparison using '==', which can be problematic, returns false
        boolean isEqualDirect = x == y;
        System.out.println("Direct comparison: " + isEqualDirect);

        // Relative comparison using epsilon, returns true
        boolean isAlmostEqual = almostEqual(x, y);
        System.out.println("Relative comparison: " + isAlmostEqual);
    }
}
  1. UTF-8 is now the default charset for the Java SE APIs. With this change, APIs that depend on the default charset will behave consistently across all implementations, operating systems, locales, and configurations.
  2. Virtual threads are introduced which are lightweight threads that reduce the effort of writing, maintaining, and debugging high-throughput concurrent applications.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class VirtualThreadExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newVirtualThreadExecutor();

        executor.submit(() -> {
            // Simulate a long-running task
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Task completed");
        });

        executor.shutdown();
    }
}

In this example, an ExecutorService is created using Executors.newVirtualThreadExecutor(), which creates a pool of virtual threads. A task is submitted to the executor, simulating a long-running operation. Because virtual threads are lightweight, this allows for a high degree of concurrency without the overhead of creating many OS-managed threads.

  1. introducing Sequenced collections which are 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 is an interface that represents a sequenced collection, provides uniform APIs for accessing its first and last elements, and for processing its elements in reverse order.

Enhancements and New Concepts

Strong Encapsulation in the JDK

Some tools and libraries use reflection to access parts of the JDK that are meant for internal use only. This use of reflection negatively impacts the security and maintainability of the JDK. To aid migration, JDK 9 through JDK 16 allowed this reflection to continue, but emitted warnings about illegal reflective access. However, JDK 17 and later is strongly encapsulated, so this reflection is no longer permitted by default.

For more details, refer to: Jdeps Tool

Changes to Garbage Collection

The Garbage-First Garbage Collector (G1 GC) is the default garbage collector in JDK 9 and later releases.
A low-pause collector such as G1 GC should provide a better overall experience, for most users, than a throughput-oriented collector such as the Parallel GC, which is the JDK 8 default.

Use CLDR Locale Data by Default

Starting in JDK 9, the Unicode Consortium’s Common Locale Data Repository (CLDR) data is enabled as the default locale data, so that you can use standard locale data without any further action.

On JDK 8, if you don’t explicitly set the locale, the system’s default locale will be used. On JDK 9 and later, if you don’t specify the locale, the CLDR data will be used by default. This means that the way dates are formatted for different locales might change slightly due to updates in the CLDR data.

So, you have to define JVM system property for setting the Locale default (user.language=de, user.country=DE)

Full support for containers

The JVM now recognizes constraints set by container control groups. Both memory and CPU constraints can be used to manage Java applications directly in containers, these include:

  • Adhering to memory limits set in the container
  • Setting available CPUs in the container
  • Setting CPU constraints in the container