Mohamed Taman
Chief Solutions Architect, Owner/CEO of SiriusXI, a Java Champion, Oracle ACE, JCP member, Consultant, Speaker, and Author.
Mohamed Taman
Chief Solutions Architect
Java Champion | CEO @SiriusXI | Oracle ACE Alumni | Jakarta EE Ambassador | Author | Trainer | Speaker.
Highlights the preview and incubator for pattern matching for switch expressions, record patterns, virtual threads, structured concurrency, Scoped-Value, and the Vector API. Plus a selection of small enhancements, performance, security, and stability updates.
JDK is more than targeted JEPs.
@_tamanm
|
|
|
@_tamanm
|
|
|
Or by googling 🕵 me
"Mohamed Taman"
@_tamanm
|
|
|
Java History.
Checkout Project Amber features.
Checkout Project Loom features.
Explore the new, improved Language APIs and others.
Checkout Project Panama features.
Farewell to APIs has gone forever.
@_tamanm
|
|
|
@_tamanm
|
|
|
@_tamanm
|
|
|
@_tamanm
|
|
|
@_tamanm
|
|
|
1st Prev JDK 19
Object object = new Point(1, 2);
if (object instanceof Point(int x, int y)) {
out.println("Object is a Point(" + x +
"," + y +")");
}
mohamed_taman -- -bash
Record patterns were first introduced in Java 19. A record pattern can be used with instanceof or switch to access the fields of a record without casting and calling accessor methods.
> Assume you have defined the following Point record:
record Point(int x, int y) { }
> With the record pattern, you can write an instanceof as follows:
2nd Prev JDK 20
switch (object) {
case Point(int i, int j) ->
out.println("Object is a Point(" + i + "," + j+")");
// other cases ...
default -> throw new IllegalStateException("Unexpected value: " + object);
}
mohamed_taman -- -bash
> With the record pattern, you can write an switch expression as follows:
@_tamanm
|
|
|
interface Box<T> { }
record RoundBox<T>(T t1, T t2) implements Box<T> { }
record TriangleBox<T>(T t1, T t2, T t3) implements Box<T> { }
mohamed_taman -- -bash
What is new in Java 20 for record patterns? There are three changes:
> Consider a generic interface, Box<T>
, and two implementing records, RoundBox<T>
and TriangleBox<T>,
which contain two and three values of type T, respectively:
Continue
for
loops@_tamanm
|
|
|
Box<String> box = new RoundBox<>("Hello", "World");
printBoxInfo(box);
public static <T> void printBoxInfo(Box<T> box) {
if (box instanceof RoundBox<T>(var t1, var t2)) {
System.out.println("RoundBox: " + t1 + ", " + t2);
} else if (box instanceof TriangleBox<T>(var t1, var t2, var t3)) {
System.out.println("TriangleBox: " + t1 + ", " + t2 + ", " + t3);
}
}
mohamed_taman -- -bash
You can check which concrete implementation a given Box<T>
object is by using the following printBoxInfo()
method:
> With Java 20, the compiler can infer the T type, so you may omit it from the instanceof checks and the method signature as well:
public static void printBoxInfo(Box box) {
if (box instanceof RoundBox(var t1, var t2)) {
System.out.println("RoundBox: " + t1 + ", " + t2);
} else if (box instanceof TriangleBox(var t1, var t2, var t3)) {
System.out.println("TriangleBox: " + t1 + ", " + t2 + ", " + t3);
}
}
mohamed_taman -- -bash
Continue
Also, with Java 20, the parameter type can be omitted from switch statements. You will see this shortly in the discussion of switch pattern enhancements.
@_tamanm
|
|
|
List<Point> points = ...
// Old way Java 19
for (Point p : points) {
System.out.printf("(%d, %d)%n", p.i(), p.j());
}
// New way Java 20
for (Point(int i, int j) : points) {
System.out.printf("(%d, %d)%n", i, j);
}
mohamed_taman -- -bash
Starting with Java 20, you can also specify a record pattern in the for
loop and then access x and y directly (the same as you can with instanceof and switch)
> Here’s another example if you have a list of points of type record()
and you want to do some operations on them, such as to print them to the console, you can do that as follows:
Continue
@_tamanm
|
|
|
Object object = new Point(1, 2);
// 1. Pattern matching for instanceof
if (object instanceof Point p) {
System.out.println("object is a Point, p.i = " + p.i() + ", p.j = " + p.j());
}
// 2. Record pattern
if (object instanceof Point(int i, int j)) {
System.out.println("object is a Point, i = " + i + ", j = " + j);
}
// 3. Named record pattern
if (object instanceof Point(int i, int j) p) {
System.out.println("object is a Point, p.i = " + p.i() + ", p.j = " + p.j() +
", i = " + i + ", j = " + j);
}
mohamed_taman -- -bash
In Java 19, there were three ways to perform pattern matching on a record, as follows:
In the third variant, a named record pattern, there are two ways to access the fields of the record: via p.i()
and p.j()
or via the i
and j
variables. This variant was determined to be superfluous and was removed in Java 20.
Continue
1st Prev JDK 17
3rd Prev JDK 19
2nd Prev JDK 18
This is a feature that has already gone through three rounds of previews.
switch (obj) {
case String s when
s.length() > 5 -> System.out.println(s.toUpperCase());
case String s -> System.out.println(s.toLowerCase());
case Integer i -> System.out.println(i * i);
case Point(int i, int j) -> System.out.println("i= " + i + ", j= " + j);
default -> throw new
IllegalStateException("Unexpected: " + object);
}
mohamed_taman -- -bash
First appearing in Java 17, and until Java 19 pattern matching for switch allows you to write code like the following:
@_tamanm
|
|
|
3rd Prev JDK 19
4th Prev JDK 20
Two enhancements were added in Java 20 with the fourth preview of JEP 433:
record Point(int i, int j) {}
sealed interface Shape permits Rectangle, Circle {}
record Rectangle(Point topLeft, Point bottomRight) implements Shape {}
record Circle(Point center, int radius) implements Shape {}
mohamed_taman -- -bash
> Consider the following sealed interface called Shape with the implementations Rectangle and Circle of type record:
@_tamanm
|
|
|
Continue
MatchException for exhaustive switch (rather than an IncompatibleClassChangeError)
Throw MatchException for exhaustive switch (rather than an IncompatibleClassChangeError)
@_tamanm
|
|
|
Continue
public void printShapeInfo(Shape shape) {
switch (shape) {
case Circle(var center, var radius) ->
System.out.println("Circle: Center = " + center +
", Radius = " + radius);
case Rectangle(var topLeft, var bottomRight) ->
System.out.println("Rectangle: Top left = " + topLeft +
", Bottom right = " + bottomRight);
}
}
mohamed_taman -- -bash
jshell> var circle = new Circle(new Point(10, 10), 50)
circle ==> Circle[center=Point[i=10, j=10], radius=50]
jshell> printShapeInfo(circle);
Circle: Center = Point[i=10, j=10], Radius = 50
mohamed_taman -- -bash
jshell> sealed interface Shape permits Rectangle, Circle, Oval {}
| modified interface Shape
jshell> record Oval(Point center, int width, int height) implements Shape {}
| created record Oval
mohamed_taman -- -bash
> If you do the following in an IDE or jshell
, Java will immediately warn that the switch statement in the printShapeInfo()
method does not cover all possible values:
@_tamanm
|
|
|
Continue
jshell> var oval = new Oval(new Point(60, 60), 20, 10);
oval ==> Oval[center=Point[i=60, j=60], width=20, height=10]
jshell> printShapeInfo(oval)
| attempted to call method printShapeInfo(Shape), which cannot be invoked until this error is corrected:
| the switch statement does not cover all possible input values
| switch (shape) {
| ^---------------...
mohamed_taman -- -bash
$ javac --enable-preview -source 20 Shape.java Oval.java App.java
$ java --enable-preview Main
Exception in thread "main" java.lang.MatchException
at App.main(App.java:56)
mohamed_taman -- -bash
> What if you have a big program and each class is in a separate source file—and you recompile only the changed classes and then start execution?
@_tamanm
|
|
|
Continue
Box<String> box = ...
switch(box) {
case RoundBox<String>(var t1, var t2) ->
System.out.println("RoundBox: " + t1 + ", " + t2);
case TriangleBox<String>(var t1, var t2, var t3) ->
System.out.println("TriangleBox: " + t1 + ", " + t2 + ", " + t3);
...
}
mohamed_taman -- -bash
switch(box) {
case RoundBox(var t1, var t2) ->
System.out.println("RoundBox: " + t1 + ", " + t2);
case TriangleBox(var t1, var t2, var t3) ->
System.out.println("TriangleBox: " + t1 + ", " + t2 + ", " + t3);
...
}
mohamed_taman -- -bash
> Starting with Java 20, you may omit the <String>
type argument inside the switch statement or expression:
@_tamanm
|
|
|
@_tamanm
|
|
|
1st Inc. JDK 20
Robustness — Ensure that data shared by a caller can be retrieved only by legitimate callees.
Comprehensibility — Make the shared data lifetime visible from the syntactic structure of code.
To change the Java programming language.
Performance — Treat shared data as immutable to allow sharing by a large number of threads, and to enable runtime optimizations.
To require migration away from thread-local variables, or to deprecate the existing ThreadLocal
API.
It is not a goal:
@_tamanm
|
|
|
Consider the following example of a web framework that uses the traditional ThreadLocal
class.
Continue
class Server {
final static ThreadLocal<User> CURRENT_USER = new ThreadLocal<>(); // (1)
void serve(Request request, Response response) {
var level = (request.isAuthorized() ? ADMIN : GUEST);
var user = new User(level);
CURRENT_USER.set(user); // (2)
Application.handle(request, response);
}
}
class DatabaseManager {
DBConnection open() {
var user = Server.CURRENT_USER.get(); // (3)
if (!user.canOpen()) throw new InvalidUserException();
return new DBConnection(...); // (4)
}
}
mohamed_taman -- -bash
@_tamanm
|
|
|
Here’s how to migrate the previous code from using ThreadLocal
to the new, lightweight ScopedValue
.
Continue
class Server {
final static ScopedValue<User> CURRENT_USER = new ScopedValue<>(); // (1)
void serve(Request request, Response response) {
var level = (request. isAuthorized()? ADMIN : GUEST);
var user = new User(level);
ScopedValue.where(CURRENT_USER, user) // (2)
.run(() -> Application.handle(request, response)); // (3)
}
}
class DatabaseManager {
DBConnection open() {
var user = Server.CURRENT_USER.get(); // (4)
if (!user.canOpen()) throw new InvalidUserException();
return new DBConnection(...);
}
}
mohamed_taman -- -bash
Together, where()
and run()
provide one-way data sharing from the server component to the data access component.
@_tamanm
|
|
|
1st Prev JDK 19
A change to the ExecutorService
, which now extends the AutoCloseable
interface.
New methods in the Future
interface: resultNow()
, exceptionNow()
, state()
.
Thread
class: join(Duration)
and sleep(Duration).
ThreadGroup
methods. Their implementations have been changed to do nothing.2nd Prev JDK 20
@_tamanm
|
|
|
1st Inc. JDK 19
The only change is an updated StructuredTaskScope
class to support the inheritance of scoped values by threads created in a task scope. This streamlines the sharing of immutable data across all child threads.
2nd Inc. JDK 20
@_tamanm
|
|
|
@_tamanm
|
|
|
MemorySegment
and MemoryAddress
abstractions are unified (memory addresses are now modeled by zero-length memory segments);1st Inc. JDK 17
2nd Inc. JDK 18
1st Prev. JDK 19
2nd Prev. JDK 20
MemorySession
has been split into Arena
and SegmentScope
to facilitate sharing segments across maintenance boundaries.MemoryLayout
hierarchy is enhanced to facilitate usage with pattern matching in switch
expressions and statements (JEP 433), and@_tamanm
|
|
|
The following examples store a string in off-heap memory, followed by a call to the C standard library’s strlen
function to return the String
length:
Continue
public class ForeignFunctionAndMemoryTest {
public static void main(String[] args) throws Throwable {
// 1. Get a lookup object for commonly used libraries
SymbolLookup stdlib = Linker.nativeLinker().defaultLookup();
// 2. Get a handle on the strlen function in the C standard library
MethodHandle strlen = Linker.nativeLinker().downcallHandle(
stdlib.lookup("strlen").orElseThrow(),
FunctionDescriptor.of(JAVA_LONG, ADDRESS));
// 3. Convert Java String to C string and store it in off-heap memory
MemorySegment str = implicitAllocator()
.allocateUtf8String("Happy Java Conference!");
// 4. Invoke the foreign function
long len = (long) strlen.invoke(str);
System.out.println("len = " + len);
}
}
mohamed_taman -- -bash
@_tamanm
|
|
|
The previous example with new changes:
Continue
public long getStringLength(String content) throws Throwable {
// 1. Get a lookup object for commonly used libraries
SymbolLookup stdlib = Linker.nativeLinker().defaultLookup();
// 2. Get a handle on the strlen function in the C standard library
MethodHandle strlen = Linker.nativeLinker().downcallHandle(
stdlib.find("strlen").orElseThrow(),
FunctionDescriptor.of(JAVA_LONG, ADDRESS));
long len = 0;
// 3. Convert Java String to C string and store it in off-heap memory
try (Arena offHeap = Arena.openConfined()) {
MemorySegment str = offHeap.allocateUtf8String(content);
// 4. Invoke the foreign function
len = (long) strlen.invoke(str);
}
// 5. Off-heap memory is deallocated at the end of try-with-resources
// 6. Return the length.
return len;
}
mohamed_taman -- -bash
@_tamanm
|
|
|
3rd Inc. JDK 18
This JEP also clarifies that alignment with Project Valhalla is a critical part of completing the Vector API.
The implementation includes small set of bug fixes and performance enhancements.
4th Inc. JDK 19
1st Inc. JDK 16
2nd Inc. JDK 17
5th Inc. JDK 20
@_tamanm
|
|
|
@_tamanm
|
|
|
What will happen to the following at compile time?
Since JDK 20
short a = 30_000;
int b = 45_000;
a += b;
System.out.println("a = "+ a);
a = a + b;
System.out.println(a);
a = 9464
LossyConversions.java:6:
error: incompatible types:
possible lossy conversion from int to short
a = a + b;
^
Java 20 introduced a new compiler (javac) lint option called lossy-conversions
that generates warnings about type casts in compound assignments (such as +=, *=, etc.) that could result in data loss as the following:
[mtaman]:~ javac -Xlint:lossy-conversions LossyConversions.java
LossyConversions.java:4: warning: [lossy-conversions]
implicit cast from int to short in compound assignment is possibly lossy
a += b;
^
1 warning
@_tamanm
|
|
|
jmod --compress
' Command Line OptionThe jmod
tool in Java SE allows developers to create JMOD
archives, a new type of archive introduced in Java 9 that provides a modular way to package Java runtime components and their dependencies.
Since JDK 20
With Java 20, a new command line option --compress
has been added to the jmod
tool. This option specifies the compression level to use while creating the JMOD archive as the following:
The default value is
zip-6
.
The accepted values for the --compress
option is zip-[0-9]
, where zip-0
indicates no compression and zip-9
provides the best compression.
jmod
tool compresses the contents of the JMOD
archive using the ZIP format.@_tamanm
|
|
|
BreakIterator
> Whereas the new implementation, the string will be broken into two graphemes:
"🇺🇸", "👨👩👧👦"
Java's java.text.BreakIterator
follows the Extended Grapheme Clusters breaks defined in the Unicode Consortium's Standard Annex #29 for character boundary analysis.
This update may result in deliberate behavioral changes because the prior implementation primarily split at code point boundaries for most characters. For example, consider this string that includes the US flag and a grapheme for a 4-member family "🇺🇸👨👩👧👦":
> With the old implementation, the previous 4-member family String
will break at the code point boundaries:
"🇺", "🇸", "👨", "(zwj)", "👩", "(zwj)", "👧", "(zwj)", "👦"
Since JDK 20
Where (zwj
) denotes ZERO WIDTH JOINER (U 200D).
@_tamanm
|
|
|
The JavaDoc documentation now includes detailed information about the JEPs (Java Enhancement Proposals) related to the preview features on the Preview API page.
Since JDK 20
This gives developers a better understanding of the origin and purpose of these preview features.
Java 19 Documentation Preview List
Enhanced Java 20 documentation Preview List
@_tamanm
|
|
|
MatchException
for Exhaustive switch
over enum
Class without a labelThis release has a change related to the switch
expressions over an enum type.
Since JDK 20
This situation can only occur if a new enum constant is added to the enum class after the switch has been compiled. This change is necessary to treat exhaustive erroneous switches consistent with introducing pattern labels.
When you enable preview features with "--enable-preview
", if the selector expression produces an unexpected enum constant value, a MatchException
will be thrown instead of an IncompatibleClassChangeError
.
@_tamanm
|
|
|
@_tamanm
|
|
|
IdentityHashMap
's Remove and Replace Methods Use Object IdentityHowever, when the default remove(Object key, Object value)
and replace(K key, V oldValue, V newValue)
methods were added to the Map interface in Java 8, they were not overridden in IdentityHashMap
to use ==
instead of equals()
, leading to a bug.
IdentityHashMap
is a unique type of map that considers keys to be equal only if they are identical. The comparison using the ==
operator returns true rather than the equals()
method.
This bug has been corrected after over eight years, which may suggest that IdentityHashMap is not widely used and may contain other bugs.
Since JDK 20
@_tamanm
|
|
|
Thread
.suspend
/resume
/stop
Changed to Throw UnsupportedOperationException
JDK 20 removes the ability to suspend or resume a thread using the Thread.suspend()
and Thread.resume()
methods. Those methods have been deprecated since JDK 1.2 and are changed to throw UnsupportedOperationException
. These methods were prone to deadlock.
Since JDK 20
As part of this change, java.lang.ThreadDeath
has been deprecated for removal.
Instead of using these methods, the corresponding methods in ThreadGroup
should be used. However, in Java 19, these methods were also changed to throw UnsupportedOperationException
.
Similarly, the ability to stop a thread using the Thread.stop()
method has been removed due to its unsafe nature of causing java.lang.ThreadDeath
exception.
@_tamanm
|
|
|
java.net.URL
Constructors Are DeprecatedIn this release, the java.net.URL
constructors are deprecated. Instead, developers are encouraged to use java.net.URI
for parsing or constructing URL.
Since JDK 20
Suppose an instance of java.net.URL
is still needed to open a connection. In that case, java.net.URI
can construct or parse the URL
string by calling URI::create()
and then calling URI::toURL()
to create the URL
instance:
> Old code :
URL url = new URL("https://blogs.oracle.com/authors/mohamed-taman");
> New code :
URL url = URI.create("https://blogs.oracle.com/authors/mohamed-taman").toURL();
@_tamanm
|
|
|
To enhance security, the TLS_ECDH_*
cipher suites have been disabled by default. This has been done by adding "ECDH" to the jdk.tls.disabledAlgorithms
security property in the java.security
configuration file. These cipher suites lack forward-secrecy and are hardly ever used in practice.
Since JDK 20
TLS_ECDH_*
Cipher SuitesDTLS
1.0java.security
file fails to loadthe DTLS 1.0 protocol has been disabled by default due to its weakened security and lack of support for stronger cipher suites. To achieve this, the "DTLSv1.0
" has been added to the jdk.tls.disabledAlgorithms
security property in the java.security
configuration file. If any attempt is made to use DTLSv1.0, it will result in an SSLHandshakeException.
Previously, when the default security configuration file conf/security/java.security
failed to load, the JDK would fall back to using a hardcoded default configuration. However, in JDK 20, if the default configuration file fails to load, the JDK throws an InternalError
instead of the hardcoded default configuration.
@_tamanm
|
|
|
By Mohamed Taman
Java is innovative with each release, and to become a creative engineer for your company, you should know what is new in your language. This session will sweep the dust off Java SE's 20 hidden gems, including new cool language features, compiler changes, library additions, and critical bug fixes. They're handy in your day-to-day work.
Chief Solutions Architect, Owner/CEO of SiriusXI, a Java Champion, Oracle ACE, JCP member, Consultant, Speaker, and Author.