Java™ 21
What's new and noteworthy?

Piotr Przybył

http://www.utrechtjug.nl/
8 XI 2023

© 2023 Piotr Przybył. Licensed under CC BY-NC-SA 4.0
Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates. Other names may be trademarks of their respective owners.
Picture by Daniel Büscher from Pixabay

$ whoami

Piotr Przybył
piotrprz
@piotrprz@mstdn.social
Software Gardener
Testcontainers Community Champion
Trainer
SoftwareGarden.dev

$ who are you

CAVEAT AVDITORES!

A.K.A. Safe harbour statement: don't assume anything based on this presentation. Verify on your own. Errare humanum est.

Java 2️⃣1️⃣

Java 2️⃣1️⃣ JEPs:

  • 430: String Templates (Preview)
  • 431: Sequenced Collections
  • 439: Generational ZGC
  • 440: Record Patterns
  • 441: Pattern Matching for switch
  • 442: Foreign Function & Memory API (Third Preview)
  • 443: Unnamed Patterns and Variables (Preview)

Java 2️⃣1️⃣ JEPs:

  • 444: Virtual Threads
  • 445: Unnamed Classes and Instance Main Methods (Preview)
  • 446: Scoped Values (Preview)
  • 448: Vector API (Sixth Incubator)
  • 449: Deprecate the Windows 32-bit x86 Port for Removal
  • 451: Prepare to Disallow the Dynamic Loading of Agents
  • 452: Key Encapsulation Mechanism API
  • 453: Structured Concurrency (Preview)

Thank you

:troll:

Shall I abandon the latest LTS
and stick to the newest Java version?

I think so!

Unless you have some really* good excuse not to

*) like really, really good

because non-LTS doesn't mean "banana soft"

In short

  • New language syntax
    (as standard and preview features)
  • Concurrency
  • Processing data (especially records)
  • Native stuff (memory and calls)
  • ZGC, Sequenced Collections, sunsetting Windows 32 & dynamic agents

Preview features

JEP-12

--enable-preview

setting in Gradle


compileJava {
    options.compilerArgs += ["--enable-preview"]
}

test {
    jvmArgs '--enable-preview'
}

run.jvmArgs += ['--enable-preview']
            

setting in Maven


<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <release>21</release>
        <compilerArgs>
            --enable-preview
        </compilerArgs>
    </configuration>
</plugin>
<plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <argLine>--enable-preview</argLine>
    </configuration>
</plugin>

setting in Intellij IDEA

Setting language version in Intellij IDEA

More on preview features

Concurrency

platform / OS threads can be too slow to create

in limited number, hence no thread-per-request

reactive is not easy to debug and troubleshoot

lack of async / await

Virtual Threads / Loom

Biggest change since Lambda?

thinkspin

Virtual Threads

standard feature in 2️⃣1️⃣

JEP-444

Virtual Threads are great for:

  1. debugging
  2. switching
  3. waiting
  4. reducing memory consumption

Virtual Threads

  • not reinvent the wheel, keep threads model
  • carried by platform / OS threads under the hood
  • cheap to start
  • unmounting carrier threads when waiting, e.g. for IO
  • always daemons, with normal prio
  • allow thread-per-request nicely
  • (with debugging, troubleshooting etc.)
  • improve scalability for asynchronous style

There is no magic switch to turn the Threads you currently have into Virtual Threads

You need to alter your source code

  • Thread.ofVirtual(Runnable r).start()
  • Thread.startVirtualThread(Runnable r)
  • Executors.newVirtualThreadPerTaskExecutor()
  • Thread.ofVirtual().factory()

Virtual Threads won't magically squeeze more juice from your CPU

Virtual Threads

  • are not GC roots
  • more work for GC
  • don't guarantee fair CPU usage
  • because of FJP and user space, CPU cache misses are possible

What not to do with Virtual Threads

  • reuse
  • pool
  • pin

-Djdk.tracePinnedThreads=full
and JFR events

Testing (if there are no) pinned Virtual Threads

Structured Concurrency

🧪preview feature in 2️⃣1️⃣ (after incubating twice)

JEP-453

Structured concurrency

  • better "idioms" for multi-threaded code
  • thinking in synchronous way of subtasks
  • help eliminating thread leaks and cancellation delays
  • without touching current concurrency stuff
  • not replacing interruption with cancellation (for now)

Structured concurrency



        try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
          var namesSubtask = scope.fork(()-> nameService.getNames());
          var scoresSubtask = scope.fork(()-> scoreService.getScores());
          scope.join();
          scope.throwIfFailed();
          var names = namesSubtask.resultNow();
          var scores = scoresSubtask.resultNow();
          return combine(names, scores);
        }

        

free cancellation within the three, yay!

Structured concurrency


            StructuredTaskScope.ShutdownOnFailure()
            StructuredTaskScope.ShutdownOnSuccess<T>()
        

don't forget join/joinUntil

Scoped Values

🧪 preview feature in 2️⃣1️⃣ (after incubating)

JEP-446

Scoped Values

one-way immutable "ThreadLocals"
+
bounded lifetime (visible in code)

=
simplified reasoning
and improved performance

(ThreadLocal doesn't change and doesn't get deprecated,
only having them for millions of Virtual Threads might be problematic)

Scoped Values


        static final ScopedValue<Level> SECURITY_CLEARANCE_LEVEL =
                                        ScopedValue.newInstance();
        // ...
        ScopedValue.where(SECURITY_CLEARANCE_LEVEL, levelFromRequest())
          .run(() -> {
            // ...
            var level = SECURITY_CLEARANCE_LEVEL.orElse(guestLevel());
            if (level.permits()) {
                doSomeStuff();
                doSomeOtherStuff();
            } else {
                error();
            }
          });

        

Scoped Values

  • work nicely with Structured Concurrency
  • run(Runnable r) or call(Callable c)
  • where(...) can be nested with its own scope

Processing data

record → standard data carrier

current syntax might not be easy to handle data-first flows


        Object o = ...
        if (o instanceof Event e) {
            if (e.field().value() > X) {
                doSomething();
            } else {
                doSomethingElse();
            }
        }
        

Pattern Matching for switch

standard feature in 2️⃣1️⃣

JEP-441

Pattern Matching for switch

Good old switch remains the same

Pattern Matching for switch


private static String getGreeting(Object version) {
    return switch (version) {
        case null -> throw new IllegalArgumentException("Impossible!");
        case Integer i when (i >= 17) -> "Hello, this is Java™ "+ i + " ;-)";
        case Integer i -> i +"? But how?";
        default -> "oops...";
    };
}
        

Pattern Matching for switch

  • switch can be now more null friendly
  • switch handles now Objects
    (not just primitives, enum and String)
  • dominant cases aren't allowed by compiler
  • cases can contain guards
  • sealed classes don't need default
  • pattern matching doesn't work with primitives
  • but it does with enum labels since 21
  • both switch expression and instruction
  • PM doesn't allow fall-through

Record Patterns

standard feature in 2️⃣1️⃣

JEP-440

Record Patterns


            record Value(double value) {}
            record Complex(Value real, Value imaginary) {}
            // ...
            switch (something) {
                case Value(double v) ->
                    System.out.printf("I got the value %f%n", v);
                case Complex(Value(double r), Value(double i)) ->
                    System.out.printf("I got the Complex [%f, %f]%n", r, i);
                default ->
                    System.out.printf("Was ist das? %s%n", s);
            }
        

Record Patterns

  • allow de-constructing records (with nesting)
  • no other types (for now)
  • works with ifs and switches

Unnamed Patterns and Variables

🧪 1st preview feature in 2️⃣1️⃣

JEP-443

Unnamed Patterns and Variables

When part of the matched record or the whole variable don't matter

  • Rekord(_): unnamed pattern
  • Rekord(int _): unnamed pattern variable
  • var _ = Rekord(int a): unnamed variable

Unnamed Patterns and Variables

Useful for:

  • skipping irrelevant parts of matched records
  • for
  • catch

Native stuff

can be really (sun.misc.)Unsafe (time, range, threads)

tedious (all these stubs, header files, ...)

limited java.nio.ByteBuffer

and slow (we want performance >= JNI)

Foreign Function & Memory API

🧪 3rd preview feature in 2️⃣1️⃣

JEP-442

Building blocks of FMAA

  • MemorySegment: memory region (native, array, ...)
  • MemoryAddress: models an address
  • MemoryLayout: description of segment's contents
  • Arena boundary (time, thread), nice for try-with-resources
  • var handles can be used to store and fetch

Foreign-Memory Access API


            var intArrayLayout = MemoryLayout.sequenceLayout(10, ValueLayout.JAVA_INT);
            try (var arena = Arena.openConfined()) {
                var segment = MemorySegment.allocateNative(intArrayLayout, arena.scope());
                for (int i = 0; i < intArrayLayout.elementCount(); i++) {
                    segment.setAtIndex(ValueLayout.JAVA_INT, i, i);
                    System.out.print(segment.getAtIndex(ValueLayout.JAVA_INT, i));
                }
            }
        

Arenas:

  • Global (many threads, can't be closed)
  • Automatic (many threads, closed by GC)
  • Confined (single thread, bounded lifetime)
  • Shared (many threads, bounded lifetime)
  • Custom

Foreign Linker API

🧪 is also a preview feature

Foreign Linker API

  • replace JNI with pure-Java development model
  • support for C (x64 & AArch64)
  • possibility to extend (C++, x86, ...)
  • performance >= JNI

Linker allows

  • downcalls - e.g. calls from Java to native code
  • upcalls - e.g. calls from native back to Java code

Foreign Linker API


            private static short getUid() throws Throwable {
                // calling https://man7.org/linux/man-pages/man2/geteuid.2.html
                var nativeLinker = Linker.nativeLinker();
                var stdlib = nativeLinker.defaultLookup();
                var getuid = stdlib.find("getuid")
                    .orElseThrow(() -> new IllegalStateException("Cannot find `getuid`"));
                var funcDesc = FunctionDescriptor.of(ValueLayout.JAVA_SHORT);
                var getuidHandle = nativeLinker.downcallHandle(getuid, funcDesc);
                return (short) getuidHandle.invoke();
            }
        

Generational ZGC

standard feature in 2️⃣1️⃣

JEP-439

Generational ZGC

  • still keep < 1ms pauses
  • while improving performance
  • low memory overhead
  • heaps from 100s MiB to many TiBs
  • heap mapped into young and old regions
  • minimal (maybe even no) manual configuration
  • no more 4 colour bits ahead ;-)
  • load and store barriers

Generational ZGC

java -XX:+UseZGC -XX:+ZGenerational -Xmx...

default behaviour is likely to flip in the future

Sequenced Collections

standard feature in 2️⃣1️⃣

JEP-431

Sequenced Collections

... and Maps

String Templates

🧪 1st preview feature in 2️⃣1️⃣

JEP-430

String Templates

  • "making " + strings + " with dynamic " + values() + "... does Java shine here?"
  • sb.append("Or maybe ")
    .append(direct?we:you)
    .append(" are just too ")
    .append(grumpy()).append("?")
`... but ${myLanguage} can already do that!!!11one`
ZU 0666',0,0); DROP DATABASE TABLICE; ---

String Templates

java.lang.StringTemplate.STR imported to every source file like public static final


            String intro = STR. """
            Full name: \{firstName} \{middleName} \{lastName}
            Born in: \{born.atZone(ZoneId.of("UTC")).getYear()}
            (Exactly \{Duration.between(born, Instant.now()).getSeconds()} seconds ago)""";
        

String Templates

Processors

java.lang.StringTemplate.STR

java.lang.StringTemplate.RAW

java.util.FormatProcessor.FMT

var MY_OWN = StringTemplate.Processor.of(
(StringTemplate st) -> {...})

Unnamed Classes and Instance Main Methods

🧪 1st preview feature in 2️⃣1️⃣

JEP-445

Unnamed Classes and Instance Main Methods

Before Java 21 (and still)


            public class HelloWorld {
                public static void main(String[] args) {
                    System.out.println("Hello, World!");
                }
            }
        

Since Java 21


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

Unnamed Classes and Instance Main Methods

  • String[] args can be omitted
  • main() doesn't have to be public or static
  • the order: String[] args over none,
    static over instance, public over default
  • work with interpreter and (to some degree) scripts

JFR.view

🧪 standard feature in 2️⃣1️⃣

JDK-8314229
  • no need to dump file or use JMC
  • jcmd [pid] JFR.view
  • jfr view

Varia

JDK 21 G1/Parallel/Serial GC changes

Fixed issues

JEP 448: Vector API (Sixth Incubator)

JEP 452: Key Encapsulation Mechanism API

Some deprecated stuff

Always

Everyone

Feedback

Always give feedback

How was it?

POLL-LINK

piotrprz

@piotrprz@mstdn.social

hire me
SLIDES-LINK

Java™ 21
What's new and noteworthy?


Thank you

Dziękuję bardzo!

Piotr Przybył
piotrprz
@piotrprz@mstdn.social
https://SoftwareGarden.dev
SLIDES-LINK
CODE-LINK