Search

Chapter 4. Major differences between Red Hat build of OpenJDK 17 and Red Hat build of OpenJDK 21

download PDF

Before migrating your Java applications from Red Hat build of OpenJDK 17 or earlier to Red Hat build of OpenJDK 21, familarize yourself with the changes in version 21. These changes might require that you reconfigure your existing Red Hat build of OpenJDK installation before you migrate to version 21.

Most of your source code should already work in Red Hat build of OpenJDK 21. However, Red Hat build of OpenJDK 21 includes some additional features that can help to make your source code more robust and secure. Users are advised to familarize with these new features and upgrade their applications to use this additional functionality.

Due to version changes in the Common Locale Data Repository (CLDR), OpenJDK 20 and later versions cannot parse some date and time strings that were created in OpenJDK 19 or earlier. Later JDK versions support a “loose match” mechanism that can fix some of these issues. However, if you experience issues parsing date and time strings, consider using the -Djava.locale.providers=COMPAT parameter when launching your application and consider migrating these strings to the newer CLDR version.

Note

Red Hat does not provide builds of OpenJDK with 32-bit support. In OpenJDK 21, Windows 32-bit x86 support is also now deprecated upstream. This feature will be removed in a future release. For more information, see JEP 449: Deprecate the Windows 32-bit x86 Port for Removal.

4.1. UTF-8 character set used by default

From Red Hat build of OpenJDK 21 onward, UTF-8 is the default character set when using the APIs for reading and writing files and for processing text. In earlier releases, if no character set was passed as an argument, the character set was based on the runtime environment and depended on the specific method being called. To ensure that your applications are not expecting a different character encoding, review your application source code and environment.

For more information, see JEP 400: UTF-8 by Default.

4.2. Z Garbage Collector improvements

Red Hat build of OpenJDK 21 extends the Z Garbage Collector (ZGC) to maintain separate allocation regions (that is, generations) for young and old objects. This enhancement allows ZGC to collect young objects more frequently, which helps to improve application performance.

You can enable generational ZGC by specifying the -XX:+UseZGC and -XX:+ZGenerational JVM options at startup.

For more information, see JEP 439: Generational ZGC.

4.3. Warnings about the dynamic loading of agents

Red Hat build of OpenJDK 21 issues warnings when agents are loaded dynamically into a running JVM. The dynamic loading of agents will be disallowed by default in a future release. If your applications currently rely on the dynamic loading of agents into the JVM, consider updating your application code to use a different approach.

For more information, see JEP 451: Prepare to Disallow the Dynamic Loading of Agents.

4.4. Finalization deprecated for future removal

Red Hat build of OpenJDK 21 deprecates the finalization feature, which is used for performing cleanup operations before an object is destroyed. The finalization feature is planned to be removed in a future release.

Red Hat build of OpenJDK 21 still enables finalization by default. To facilitate early testing, you can disable finalization by setting the --finalization=disabled command-line option.

If you are maintaining libraries and applications that rely on finalization, consider migrating to other resource management techniques, such as cleaners or the try-with-resources statement. Due to this change, maintainers of libraries and applications are advised to test their code as soon as possible.

For more information, see JEP 421: Deprecate Finalization for Removal.

4.5. Internet address resolution SPI

Red Hat build of OpenJDK 21 includes a service provider interface (SPI) for host name and address resolution that supports the use of different resolver providers. From Red Hat build of OpenJDK 21 onward, the InetAddress API uses a service loader to locate a resolver provider. Similar to previous releases, if no provider is found, the JDK uses the built-in implementation.

For more information, see JEP 418: Internet-Address Resolution SPI.

4.6. Simple web server

Red Hat build of OpenJDK 21 includes a jwebserver command-line tool that you can use to start a simple web server. The aim of this feature is to support prototyping, ad-hoc coding, and testing, especially for an educational purpose.

The jwebserver tool supports the HTTP 1.1 protocol and serves static files only. The simple web server does not support the secure HTTPS protocol and is not suitable for production services.

To run the simple web server, you can use the following command:

$ jwebserver

By default, files are served from the current directory. If the requested resource is a directory that contains an index file, the index file is served.

In addition to the jwebserver tool, this feature also includes an API to support enhanced request handling that can be used to return HTTP headers.

For example:

jshell> var h = HttpHandlers.handleOrElse(r -> r.getRequestMethod().equals("PUT"),
   ...> new SomePutHandler(), new SomeHandler());
jshell> var f = Filter.adaptRequest("Add Foo header", r -> r.with("Foo", List.of("Bar")));
jshell> var s = HttpServer.create(new InetSocketAddress(8080),
   ...> 10, "/", h, f);
jshell> s.start();

For more information, see JEP 408: Simple Web Server.

4.7. Sequenced collections

Red Hat build of OpenJDK 21 introduces a sequenced collections feature that adds new interfaces to represent ordered collections, where each collection has a range of elements with a well-defined encounter order. This feature unifies how each ordered collection accesses its first, last, and other specific elements.

SequencedCollection interface

The SequencedCollection interface provides methods for adding, getting, or removing the first and last elements in the collection and for getting a reverse-ordered view of the collection:

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();
}

The SequencedCollection interface is implemented by the SortedSet, NavigableSet, LInkedHashSet, List, and Deque interfaces.

SequencedMap interface

The SequencedMap interface provides methods for getting or removing entries at either end of the collection:

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();
}

The SequencedMap interface is implemented by the SortedMap, NavigableMap, and LInkedHashMap interfaces.

For more information, see JEP 431: Sequenced Collections.

4.8. Pattern matching for switch statements

Red Hat build of OpenJDK 21 enhances the Java programming language with pattern matching for switch statements. This enhancement enables you to test switch expressions against different patterns that each have a specific action, to ensure that complex data-oriented queries are expressed concisely and safely.

You can implement a switch statement that is based on the class of an object.

For example:

switch (obj) {
		case Integer i -> String.format("int %d", i);
		case String s  -> String.format("String %s", s);
		default        -> obj.toString();
	};

You can also implement more complex switch statements by nesting a when guard inside the case block of the switch statement.

For example:

switch (response) {
    	case null -> { }
    	case String s when s.equalsIgnoreCase("YES") -> {
        	System.out.println("You got it");
    	}
    	case String s when s.equalsIgnoreCase("NO") -> {
        	System.out.println("Shame");
    	}
    	case "n", "N" -> {
        	System.out.println("Shame");
    	}
    	case String s -> {
        	System.out.println("Sorry?");
    	}
}

If your applications use switch statements, ensure that these statements are up to date.

For more information, see JEP 441: Pattern Matching for switch.

4.9. Key encapsulation mechanism API

Red Hat build of OpenJDK 21 introduces an API for a key encapsulation mechanism (KEM). KEM is an encryption technique for securing symmetric keys by using public key cryptography. The KEM API facilitates the use of public key cryptography and helps to improve security when handling secrets and messages.

For more information, see JEP 452: Key Encapsulation Mechanism API.

4.10. Code snippets in Java API documentation

Red Hat build of OpenJDK 21 includes a @snippet tag for the Javadoc tool’s standard doclet. The @snippet tag helps to simplify the inclusion of example source code in API documentation.

For example:

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

Markup tags

You can use the @snippet tag in combination with markup tags, such as @highlight, to modify the styling of the code. For example, the following code snippet uses the @highlight tag to highlight the println method name in the API documentation:

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

External files

You can also use the @snippet tag in combination with external files that contain the source code.

For example:

/**
 * The following code shows how to use {@code Optional.isPresent}:
 * {@snippet file="ShowOptional.java" region="example"}
 */

In the preceding example, ShowOptional.java is a file that contains the following code:

public class ShowOptional {
	void show(Optional<String> v) {
    	// @start region="example"
    	if (v.isPresent()) {
        	System.out.println("v: " + v.get());
    	}
    	// @end
	}
}

You can specify the location of the external code either as a class name by using the class attribute or as a relative file path by using the file attribute.

For more information, see JEP 413: Code Snippets in Java API Documentation.

4.11. Virtual threads

Red Hat build of OpenJDK 21 introduces virtual threads, which are lightweight threads that reduce the effort of writing, maintaining, and observing high-throughput concurrent applications. If your application uses more than a few thousand concurrent threads and your workload is not CPU-bound, the use of virtual threads can optimize your application.

Virtual threads support thread-local variables and interruptions. Virtual threads can also run any code that traditional threads can run. Therefore, you can migrate your source code to use only virtual threads by changing the way the threads are created.

The following example creates a virtual thread that executes a runnable instance:

Thread thread = Thread.ofVirtual().name("example").unstarted(runnable);

The following example shows how to create virtual threads by using a loop. Because virtual threads do not require pooling, no thread pool is created. If you want to limit concurrency, you can use semaphores instead.

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

The preceding example creates 10,000 virtual threads to run concurrently. The JDK can run the code on a small number of operating system threads or perhaps only one thread, which reduces the overhead.

For more information, see JEP 444: Virtual Threads.

4.12. Vector API

OpenJDK 16 initially introduced the Vector API as an incubating feature. Red Hat build of OpenJDK 21 includes several enhancements to the Vector API based on a sixth round of incubation. The Vector API expresses a wide range of vector computations that can be reliably compiled at runtime to optimal vector instructions on supported CPU architectures. This API achieves better performance levels than equivalent scalar computations.

The latest enhancements ensure the reliability and efficiency of the Vector API in all architectures while also ensuring a graceful degradation of the API in any architecture that does not support all the expected native instructions. Future enhancements to the Vector API might also include alignment with Project Valhalla by defining vector classes as primitive classes.

Note

Because the Vector API relates to implementation details, your application source code should not not require any updates due to this change.

For more information, see JEP 448: Vector API (Sixth Incubator).

4.13. Reimplementation of the core reflection functionality with method handles

Red Hat build of OpenJDK 21 includes a reimplementation of the java.lang.reflect.Method, java.lang.reflect.Constructor, and java.lang.reflect.Field classes on top of java.lang.invoke method handles. This reimplementation of the core reflection functionality helps to reduce the maintenance and development costs of the java.lang.reflect and java.lang.invoke APIs.

Note

Because this change relates to a reimplementation, your application source code should not require any updates.

For more information, see JEP 416: Reimplement Core Reflection with Method Handles.

4.14. Foreign Function and Memory API (Third preview)

Red Hat build of OpenJDK 21 includes a Foreign Function and Memory (FFM) API, which is a preview feature that enables Java programs to interoperate with code and data that is outside the Java runtime. The FFM API can efficiently invoke foreign functions that are outside the JVM and safely access foreign memory that the JVM does not manage.

The FFM API replaces the existing Java Native Interface (JNI) with a pure Java development model that provides a more efficient, cleaner, and safer way to call native libraries and process native data.

The FFM API defines classes and interfaces that enable client code in libraries and applications to perform the following tasks:

  • Control the allocation and deallocation of foreign memory by using the MemorySegment, Arena, and SegmentAllocator interfaces.
  • Manipulate and access structured foreign memory by using the MemoryLayout and VarHandle interfaces.
  • Call foreign functions by using the Linker, FunctionDescriptor, and SymbolLookup interfaces.

The FFM API is located in the java.lang.foreign package of the java.base module.

Note

The FFM API is an experimental addition to the Java language that might be subject to improvements in future versions, where no backwards compatibility is guaranteed.

For more information, see JEP 442: Foreign Function & Memory API (Third Preview).

4.15. Record patterns (Preview)

Red Hat build of OpenJDK 21 introduces record patterns, which is a preview feature that enhances the Java programming language to deconstruct record values. This feature allows declaration and assignment variables that use the instanceof operator.

Consider the following declaration for a record named Point with attributes x and y:

record Point(int x, int y) {}

Based on the preceding declaration, the following code can test whether a value is an instance of Point and also extract the x and y components from the value directly:

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

You can also use this feature in records that are nested inside other records. For example, consider the following declarations for a record named Rectangle with attributes of a ColoredPoint record with attributes of a Point record and Color enum:

record Point(int x, int y) {}
enum Color { RED, GREEN, BLUE }
record ColoredPoint(Point p, Color c) {}
record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}

Based on the preceding declarations, the following code can test whether a value is an instance of Rectangle and extract the color from the upper-left point:

if (r instanceof Rectangle(ColoredPoint ul, ColoredPoint lr)) {
     	System.out.println(ul.attribute());
}

For more information, see JEP 440: Record Patterns.

4.16. Unnamed patterns and variables (Preview)

Red Hat build of OpenJDK 21 introduces unnamed patterns and variables, which is a preview feature that enhances the Java programming language:

  • Unnamed patterns match a record component without stating the component’s name or type. Unnamed patterns help to improve the readability of record patterns by omitting unnecessary nested patterns.
  • Unnamed variables can be initialized but not used. Unnamed variables help to improve the maintainability of all code by identifying variables that must be declared (for example, in a catch clause) but which are not used.

You can denote unnamed patterns and unnamed variables by using an underscore (_) character. When a variable is not relevant and will not be used, you do not need to name the variable. In this situation, you can use an underscore character to denote that the variable exists but is not for use.

In the following example of a lambda expression, the parameter is irrelevant, which means that the JVM does not need to create a variable for this parameter:

...stream.collect(Collectors.toMap(String::toUpperCase, _ -> "NODATA"))

In the following example of a switch statement, the variables need to match the case but these variables will not be used:

switch (b) {
	case Box(RedBall _), Box(BlueBall _) -> processBox(b);
	case Box(GreenBall _)            	-> stopProcessing();
	case Box(_)                      	-> pickAnotherBox();
}

In the preceding example, the first two cases use unnamed pattern variables because their right-hand sides do not use the Box record’s component. The third case, which is new, uses the unnamed pattern to match a Box record with a null component.

For more information, see JEP 443: Unnamed Patterns and Variables (Preview).

4.17. String templates (Preview)

Red Hat build of OpenJDK 21 introduces string templates, which is a preview feature that enhances the Java programming language. String templates complement Java’s existing string literals and text blocks by coupling literal text with embedded expressions and template processors to produce specialized results.

The string templates feature includes an STR utility, which is a template processor that supports validation and transformation, helping to improve the security of Java programs that compose strings. The STR utility also facilitates code readability. You can compose strings by using other variables, attributes, or functions.

For example, consider the following template expression:

STR."\{username} access at \{req.date} from \{getLocation()}"

The preceding expression produces the following example string:

“Guest access at 12/1/2024 from 127.0.0.1”

The preview features are experimental additions to the Java language that might be subject to improvements in future versions, where no backwards compatibility is guaranteed.

For more information, see JEP 430: String Templates (Preview).

4.18. Unnamed classes and instance main() methods (Preview)

Red Hat build of OpenJDK 21 introduces unnamed classes and instance main() methods, which is a preview feature that enhances the Java programming language for an educational purpose.

Unnamed classes and instance main() methods enable students to start writing basic Java programs in a concise manner without needing to understand Java language features for large complex programs. Rather than use a separate dialect of Java, students can write streamlined declarations for single-class programs and seamlessly modify their programs to use more advanced features as their Java knowledge increases.

To simplify the source code and allow students to focus on their own code, Java allows students to launch instance main() methods that meet the following criteria:

  • Not static
  • Not required to be public
  • Not required to have a String[] parameter

For example:

class HelloWorld {
	void main() {
    		System.out.println("Hello, World!");
	}
}

To allow students to start writing code before learning the object orientation concept, Red Hat build of OpenJDK 21 also introduces unnamed classes to make the class declaration implicit. For example:

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

To test this feature, ensure that you enable preview features. For example:

  • If you want to compile and then run the program, enter the following commands:

    javac --release 21 --enable-preview Main.java
    java --enable-preview Main
  • If you want to use the source code launcher, enter the following command:

    java --source 21 --enable-preview Main.java

For more information, see JEP 445: Unnamed Classes and Instance Main Methods (Preview).

4.19. Scoped values (Preview)

Red Hat build of OpenJDK 21 introduces scoped values, which is a preview feature that provides an API to support scoped value variables. Typically, a scoped value variable is declared as a final static field that is accessible from many methods.

Scoped values provide a safe and efficient way to share variables across methods without needing to use method parameters. A scoped value is comparable with an implicit method parameter that a method can use without having to declare the parameter within the method itself. Only the methods that have access to the scoped value object can access its data. Scoped values enable applications to pass data securely from a caller to a callee through other intermediate methods that do not declare a parameter for this data and cannot access it. Scoped values are a preferred alternative to thread-local variables, especially when using large numbers of virtual threads.

Consider the following example code where a callee method (bar) can use the same scoped value in a nested binding to send a different value to another callee method (baz):

private static final ScopedValue<String> X = ScopedValue.newInstance();

void main() {
	ScopedValue.where(X, "hello").run(() -> bar());
}

void bar() {
	System.out.println(X.get()); // prints hello
	ScopedValue.where(X, "goodbye").run(() -> baz());
	System.out.println(X.get()); // prints hello
}

void baz() {
	System.out.println(X.get()); // prints goodbye
}

In the preceding example, the main() method sets the scoped value X to hello and sends this value to the bar() method. The bar() method prints hello (the value of X) and then uses a nested binding to change the value of X and send the changed value to the baz() method. Once the baz() method returns, the rest of the bar() method still has the original hello value of X.

For more information, see JEP 446: Scoped Values (Preview).

4.20. Structured concurrency (Preview)

Red Hat build of OpenJDK 21 introduces structured concurrency, which is a preview feature that provides an API to treat groups of related tasks running in different threads as a single unit of work. This API simplifies concurrent programming and helps to streamline error handling and cancellation, improve readability, and enhance observability.

In the structured concurrency paradigm, tasks (that is, threads) can be subdivided into subtasks, which can also be nested into further subtasks. This type of task hierarchy enables both cancellation propagation (that is, canceling a task automatically cancels its subtasks) and better control of how tasks interact with each other.

For example:

Response handle() throws ExecutionException, InterruptedException {
	try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    	Supplier<String>  user  = scope.fork(() -> findUser());
    	Supplier<Integer> order = scope.fork(() -> fetchOrder());

    	scope.join()        	// Join both subtasks
         	.throwIfFailed();  // ... and propagate errors

    	// Here, both subtasks have succeeded, so compose their results
    	return new Response(user.get(), order.get());
	}
}

The preceding example first creates a scope and then forks this scope to create the subtasks. At any time, the original thread or any of the subtasks can request a shutdown of the scope, to cancel all unfinished tasks and prevent new subtasks from being created. The scope.join() method ensures that the scope must wait for all subtasks to finish running. The .get() method returns the results of each subtask.

Each subtask can also create its own StructuredTaskScope to divide the workload into further subtasks. In this situation, the additional subtasks are nested under their parent subtask within the overall hierarchy.

For more information, see JEP 453: Structured Concurrency (Preview).

4.21. Additional resources (or Next steps)

Red Hat logoGithubRedditYoutubeTwitter

Learn

Try, buy, & sell

Communities

About Red Hat Documentation

We help Red Hat users innovate and achieve their goals with our products and services with content they can trust.

Making open source more inclusive

Red Hat is committed to replacing problematic language in our code, documentation, and web properties. For more details, see the Red Hat Blog.

About Red Hat

We deliver hardened solutions that make it easier for enterprises to work across platforms and environments, from the core datacenter to the network edge.

© 2024 Red Hat, Inc.