Java 26

Better Language, Better APIs, Better Runtime

Developer Advocate

Java Team at Oracle

Let’s get started!

JDK 26 was released today:
jdk.java.net/26

This session:

  • briefly summarizes Java 22 to 25

  • goes into more detail on Java 26

  • is a showcase, not a tutorial

Slides at slides.nipafx.dev/java-x (โ‡ hit "?")

Ask questions at any time.

Follow up in other sessions!

Java 26

Java 22 to 25
Language Features
New APIs
Runtime Improvements
Better Tooling
Java 26
Deprecations & Removals

Java 26

Java 22 to 25
Language Features
New APIs
Runtime Improvements
Better Tooling
Java 26
Deprecations & Removals

Flexible Constructor Bodies

class ThreePartName extends Name {
	private final String middle;

	ThreePartName(String full) {
		// split "first middle last" on space
		var names = full.split(" ");
		// assign fields before `super`
		this.middle = names[1];
		// call `Name(String first, String last)`
		super(names[0], names[2]);
	}
}

Unnamed patterns

Use _ to mark a (pattern) variable as unused, e.g.:

BiConsumer<String, Double> = (s, _) -> // use `s`

Object obj = // ...
if (obj instanceof User(var name, _))
	// use `name`

switch (obj) {
	case User _ -> userCount++;
	case Admin _ -> adminCount++;
}

Module Imports

import module $mod;
  • imports public API of $mod

  • your code does not need to be in a module

Simplified Main

Get started quicker:

// entire source file
// implicit `import module java.base`
void main() {
    var name = IO.readln("Please enter your name: ");
    IO.println("Nice to meet you, " + name);
}

(Execute with: java Main.java)

Java 26

Java 22 to 25
Language Features
New APIs
Runtime Improvements
Better Tooling
Java 26
Deprecations & Removals

Stream Gatherers

Create custom stream operations:

Stream.of("A", "C", "F", "B", "S")
	.gather(groups(2))
	.forEach(IO::println);

// [A, C]
// [F, B]
// [S]

Scoped Values

Simpler, more scalable, one-way alternative to TheadLocal:

static final ScopedValue<Integer> ANSWER =
	ScopedValue.newInstance();

void main() {
	ScopedValue //      โฌ VALUE
		.where(ANSWER, 42)
		//  |<---------- SCOPE ----------->|
		.run(() -> IO.println(ANSWER.get())); // "42"

	// OUT OF SCOPE
	ANSWER.get(); // โšก๏ธ NoSuchElementException
}

Foreign-memory API

Interact with off-heap memory:

// create `Arena` to manage off-heap memory lifetime
try (Arena offHeap = Arena.ofConfined()) {
	// [allocate off-heap memory to store pointers]
	// [do something with off-heap data]
	// [copy data back to heap]
} // off-heap memory is deallocated here

Foreign-function API

Interact with native libraries:

// find foreign function on the C library path
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle radixSort = linker
	.downcallHandle(stdlib.find("radixsort"), ...);

Class-file API

A modern, on-board bytecode manipulation API:

  • allows frameworks and libraries
    to drop dependency on ASM et al.

  • removes a big reason for:

Update all dependencies before updating the JDK.

Smaller API Additions

  • Console: Locale overloads for
    format, printf, readLine, readPassword

  • Console: isTerminal

  • Reader: readLines, readAllAsString

  • Math, StrictMath: more exact overloads,
    e.g. unsignedMultiplyExact(int, int)

  • Instant: until(Instant)

  • ForkJoinPool: schedule…​ and more

  • BodyHandlers, BodySubscribers: limiting

Java 26

Java 22 to 25
Language Features
New APIs
Runtime Improvements
Better Tooling
Java 26
Deprecations & Removals

Multi-File Execution

Small programs can expand:

MyFirstJava
 โ”œโ”€ Main.java
 โ”œโ”€ Helper.java
 โ””โ”€ Lib
     โ””โ”€ library.jar

Run with:

java -cp 'Lib/*' Main.java

AOT computation

Reduce your applicationss' startup and warmup times
with ahead-of-time computation:

# training run (โ‡ AOTCache)
$ java -XX:AOTCacheOutput=app.aot
       -cp app.jar com.example.App ...
# production run (AOTCache โ‡ performance)
$ java -XX:AOTCache=app.aot
       -cp app.jar com.example.App ...

Compact object headers

Reduce object headers from (usually) 12 bytes to 8 bytes:

-XX:+UseCompactObjectHeaders
  • reduces heap size by 5-30%

  • can reduce garbage collections

  • can improve or deteriorate
    overall performance

Report observations to hotspot-dev.

Generational ZGC

netflix genzgc gc pause

Less virtual thread pinning

Virtual threads:

  • execute on a platform thread

  • usually unmount from PT when waiting

  • pinning prevents that

  • caused by native calls, class initialization, and
    object monitors (e.g. synchronized)

Object monitors were reimplemented.

โ‡ No more pinning for synchronized.

Performance improvements

Every Java release improves performance, e.g.:

  • +10%/+5% critical/max jOPS in SPECjbb 2015

  • +70-75% requests/s on Helidon

(JDK 25 vs 21)

Security enhancements

Many enhancements between Java 21 and 25:

Java 26

Java 22 to 25
Language Features
New APIs
Runtime Improvements
Better Tooling
Java 26
Deprecations & Removals

JFR improvements

JDK Flight Recorder got better:

  • cooperative sampling (JEP 518)

  • method timing & tracing (JEP 520)

  • CPU-time profiling (JEP 509, experimental)

Markdown in Javadoc

/// Returns `true` if, and only if,
/// [#length()] is `0`.
///
/// @return `true` if [#length()] is `0`,
//          otherwse `false`
/// @since 1.6
@Override
public boolean isEmpty() { /* ... */ }

More

Specifically:

Generally:

Java 26

Java 22 to 25
Java 26
Primitive Patterns
Lazy Constants
HTTP/3
Structured Concurrency
PEM Encoding
Deprecations & Removals

Patterns so far…​

In instanceof and switch, patterns can:

  • match against reference types

  • deconstruct records

  • nest patterns

  • ignore parts of a pattern

In switch:

  • refine the selection with guarded patterns

Patterns so far…​

That (plus sealed types) are
the pattern matching basics.

This will be:

  • built up with more features

  • built out to re-balance the language

Case in Point

The x instanceof Y operation:

  • meant: "is x of type Y?"

  • now means: "does x match the pattern Y?"

For primitives:

  • old semantics made no sense
    โ‡ no x instanceof $primitive

  • new semantics can make sense

Bound Checks

Example: int number = 0;

Can number be an instance of byte?

No, it’s an int.

But can its value be a byte?

Yes!

Bound Checks

int x = 0;
if (x instanceof byte b)
	IO.println(b + " in [-128, 127]");
else
	IO.println(x + " not in [-128, 127]");

More Checks

What’s special about 16_777_217?

Smallest positive int that float can’t represent.

Precision Checks

int x = 16_777_217;
if (x instanceof float f)
	// use `f`

Tri-state Boolean

Boolean bool = // ...
var emoji = switch (bool) {
	case null -> "";
	case true -> "โœ…";
	case false -> "โŒ";
}

More

Java 26

Java 22 to 25
Java 26
Primitive Patterns
Lazy Constants
HTTP/3
Structured Concurrency
PEM Encoding
Deprecations & Removals

Laziness

Deferring computation is interesting…​

  • if it takes long

  • if later execution creates better results

  • if we may not need to do it

But has tradeoffs:

  • more complexity

  • increased tail-end latency

Java and our programs are lazy in many regards.

Laziness in Java

Program infrastructure is built on fields.

Lazy field initialization:

  • is harder to get right

  • prevents final:

    • less maintainable

    • no constant folding

Double-checked Locking

private volatile LoginService login;

private LoginService getLogin() {
	var login = this.login;
	if (login == null) {
		synchronized (this) {
			login = this.login;
			if (login == null)
				this.login = login =
					LoginService.initialize();
		}
	}
	return login;
}

Lazy constants

public class UserController {

	private final LazyConstant<LoginService> login;

	public UserController() {
		this.login = LazyConstant
			.of(LoginService::initialize);
		// using `login` later...
		IO.println(login.get());
	}

}

Properties

Lazy constants:

  • are easy to use

  • are truly final
    โ‡ allow constant folding

Best of both worlds.

Behavior

Details:

  • initializer executes at most once (successfully)

  • equals compares LazyConstant identity

Error cases:

  • rejects null

  • IllegalStateException on cycles

  • if initializer fails:

    • get throws its exception

    • next get will try again

  • no cancellation/timeout

Lazy collections

var list = List.ofLazy(2, index -> "" + index);
var map = Map.ofLazy(Set.of(0, 1), key -> "" + key);

Coming in JDK 27: Set::ofLazy.`

Lazy collections

Details:

  • initializer executes at most once
    per element (successfully)

  • collections are unmodifiable

  • equals evaluates all elements
    (Collection requires this)

Error cases like before.

More

Second preview in JDK 26.

Java 26

Java 22 to 25
Java 26
Primitive Patterns
Lazy Constants
HTTP/3
Structured Concurrency
PEM Encoding
Deprecations & Removals

HTTP client

Java’s HTTP API:

HttpClient client = HttpClient.newBuilder()
	.version(HTTP_2)
	.build();

HttpRequest request = HttpRequest.newBuilder()
	.uri(URI.create("https://dev.java"))
	.build();

var response = client
	.send(request, BodyHandlers.ofString());

HTTP/3

It now supports HTTP/3:

HttpClient client = HttpClient.newBuilder()
	.version(HTTP_3)
	.build();

HttpRequest request = HttpRequest.newBuilder()
	.uri(URI.create("https://dev.java"))
	.version(HTTP_3)
	.build();

Connection upgrades

Network stacks:

  • HTTP/1.1 and 2 use TCP

  • HTTP/3 uses QUIC over UDP

โ‡ Can’t upgrade a connection to /3.

Version selection

Four options:

  1. start with /2
    โ‡ use /3 for next request if supported

  2. start with /2 and /3
    โ‡ keep using what replies first

  3. start with /3
    โ‡ on timeout, request with /2

  4. insist on /3
    โ‡ otherwise fail

Check JEP or documentation for details.

More

Final in JDK 26.

Java 26

Java 22 to 25
Java 26
Primitive Patterns
Lazy Constants
HTTP/3
Structured Concurrency
PEM Encoding
Deprecations & Removals

Unstructured concurrency

Common way to organize concurrency:

  • have long-running ExecutorService instances

  • submit new tasks wherever necessary

  • enqueue follow-up computations wherever convenient

  • wait for results wherever needed

Concurrency is "all over the place".

Unstructured concurrency

Makes it more difficult to:

  • express/identify relationships between threads

  • organize overarching result/error handling

  • analyze control flow

  • debug issues that span multiple threads

Structured programming

We have seen this before with GOTOs.

Structured programming was the solution:

  • prescribes single entry point
    and clearly defined exit points

  • influenced languages and runtimes

Structured concurrency

When the flow of execution splits into multiple concurrent flows, they rejoin in the same code block.

Term coined/refined by:

Structured concurrency

// implicitly short-circuits on error
try (var scope = StructuredTaskScope.open()) {
	// spawns new (virtual) threads
	Subtask<String> a = scope.fork(this::taskA);
	Subtast<String> b = scope.fork(this::taskB);

	// waits explicitly for success
	// (throws error if there was one)
	scope.join();

	return a.get() + b.get();
} catch (FailedException ex) {
	// TODO: handle task errors
} // waits until all tasks/threads complete

Properties

Threads are short-lived:

  • start when task begins

  • end on completion

โ‡ Establishes parent-child/sibling relationships
and logical grouping of tasks/threads.

Benefits

Structured concurrency:

  • defines a scope for concurrency

  • simplifies control flow

  • simplifies grouped result/error handling

  • makes thread relationships visible
    (in thread dumps and soon๐Ÿคž debuggers)

Completion

Use Joiner to configure result/error handling:

  • how are results collected?

  • when are subtasks cancelled?

  • what does join return?

  • when does join throw?

Pass to StructuredTaskScope.open(Joiner).

Joiners

Existing joiners for heterogeneous results:

  • awaitAllSuccessfulOrThrow():

    • cancels/throws on first error

    • default behavior of open()

  • awaitAll():

    • never cancels/throws

Await All

try (var scope = StructuredTaskScope.open(
		Joiner.awaitAll())) {
	var subtask = scope.fork(this::task);
	// never throws:
	scope.join();
	switch (subtask.state()) {
		case SUCCESS -> // ...
		case FAILED -> // ...
		case UNAVAILABLE -> // ...
	}
} catch (FailedException ex) {
	// TODO: handle task errors
}

Joiners

Existing joiners for homogeneous results:

  • allSuccessfulOrThrow():

    • cancels/throws on first error

    • returns List<RESULT>

  • anySuccessfulOrThrow()

    • cancels/throws if all fail

    • returns RESULT

Any Successful

try (var scope = StructuredTaskScope.open(
		Joiner.<String> anySuccessfulOrThrow())) {
	// no need to grab the `Subtask` instances
	scope.fork(this::taskA);
	scope.fork(this::taskB);

	// returns the first successful result
	return scope.join();
} catch (FailedException ex) {
	// TODO: handle task errors
}

Custom joiners

Implement and pass to StructuredTaskScope::open:

interface Joiner<T, R> {

	boolean onFork(Subtask<? extends T> subtask);

	boolean onComplete(Subtask<? extends T> subtask);

	void onTimeout();

	R result() throws Throwable;

}

Configuration

Further configuration options:

  • the scope’s name

  • a timeout

  • a custom thread factory

Pass UnaryOperator<Configuration> to StructuredTaskScope::open.

Timeouts

var timeout = Duration.ofSeconds(3);
try (var scope = StructuredTaskScope.open(
		Joiner.<String> anySuccessfulOrThrow(),
		cfg -> cfg.withTimeout(timeout))) {
	// [... forks ...]

	// throws when timeout expires
	return scope.join();
} catch (TimeoutException ex) {
	// TODO: handle timeout
} catch (FailedException ex) {
	// TODO: handle task errors
}

Error propagation

If joiner cancels scope:

  • all child threads get interrupted

  • join returns or throws

But StructuredTaskScope.close() waits
until all child threds complete.

โ‡ Handle InterruptedException properly!

Sharing data

Structured concurrency being scoped allows
inheriting scoped values to child threads.

Scoped values

ScopedValue.where(ANSWER, 42).run(() -> {// scope โ”€โ”€โ”
	try (var scope = StructuredTaskScope    //      โ”‚
			.open() {                       //      โ”‚
		               // child threads' scope โ”€โ”€โ”  โ”‚
		var subA = scope.fork(ANSWER::get); //   โ”‚  โ”‚
		var subB = scope.fork(ANSWER::get); //   โ”‚  โ”‚
		                                    //   โ”‚  โ”‚
		scope.join();                       //   โ”‚  โ”‚
		                                    //   โ”‚  โ”‚
		var result = subA.get()+subB.get(); //   โ”‚  โ”‚
		IO.println(result);  // "84"        //   โ”‚  โ”‚
	}           // all child threads completed โ”€โ”€โ”˜  โ”‚
	IO.println(ANSWER.get()) // "42"        //      โ”‚
}                                           // โ”€โ”€โ”€โ”€โ”€โ”˜

More

Sixth preview in JDK 26.

Java 26

Java 22 to 25
Java 26
Primitive Patterns
Lazy Constants
HTTP/3
Structured Concurrency
PEM Encoding
Deprecations & Removals

PEM texts

Representations of cryptographic objects
(keys, certificates, certificate revocation lists):

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj
0DAQcDQgAEi/kRGOL7wCPTN4KJ
2ppeSt5UYB6ucPjjuKDtFTXbgu
OIFDdZ65O/8HTUqS/sVzRF+dg7
H3/tkQ/36KdtuADbwQ==
-----END PUBLIC KEY-----

Privacy-enhanced…​ mail?!

Was introduced for exchange via email
but moved way beyond:

  • development platforms (e.g. GitHub)

  • certificate authorities

  • cryptographic libraries (e.g. OpenSSL)

  • security-sensitive applications (e.g. OpenSSL)

  • hardware authentication devices (e.g. YubiKeys)

  • your applications

โ‡ Very helpful if Java handled them.

Manual En-/Decoding

To encode:

  • create binary representation of object

  • Base64-encode it

  • surround string with BEGIN/END lines

To decode: reverse that.

โ‡ Java has all the building blocks, but
combining them is cumbersome.

PEM API

New PEM API is straightforward to use:

X509Certificate cert = // ...

// encode
PEMEncoder encoder = PEMEncoder.of();
String pem = encoder.encodeToString(cert);

// decode
PEMDecoder decoder = PEMDecoder.of();
DEREncodable cert2 = decoder.decode(pem);

assert cert.equals(cert2);

Endcoder/Decoder

Instances of PEMEncoder and PEMDecoder are:

  • immutable

  • thread-safe

  • reusable

Encodable

All implementations of DEREncodable can be encoded:

  • AsymmetricKey
    (DH, DSA, EC, RSA, etc.)

  • KeyPair

  • PKCS8EncodedKeySpec

  • X509EncodedKeySpec

  • X509Certificate

  • X509CRL

  • EncryptedPrivateKeyInfo

  • PEM

Decoding

For decoding:

  • if the object type is known, call
    decode(String, Class<T extends DEREncodable>):

    KeyPair kp = decoder.decode(pem, KeyPair.class);
  • otherwise, switch/instanceof over
    return value of decode(String)

(Overloads for InputStream exist.)

Unknown objects

Unknown cryptographic objects are decoded to PEM:

  • String type (header text)

  • String content (Base64-encoded body)

  • byte[] leadingData (data preceding the header)

Encryption

PrivateKey, KeyPair, PKCS8EncodedKeySpec
instances can be encrypted/decrypted:

PrivateKey key = // ...
char[] pass = // ...

String pem = encoder
	.withEncryption(pass)
	.encodeToString(key);
DEREncodable der = decoder
	.withDecryption(pass)
	.decode(pem);

(Custom parameters/providers can be configured.)

More

Second preview in JDK 26.

Java 26

Java 22 to 25
Java 26
Deprecations & Removals

Cleaning house

Already removed:

Security Manager

What it was:

  • a system of checks and permissions

  • intended to safeguard security-relevant
    code sections

  • embodied by SecurityManager

Security Manager

What you need to know:

  • barely used but maintenance-intensive

  • disallowed by default since JDK 18

  • removed in JDK 24

    • command-line flags cause errors

    • SecurityManager still exists

    • calls are "no-ops" (where possible)
      or "no" (otherwise)

Security Manager

What you need to do:

  • observe your app with security manager disabled
    (java.security.manager=disallow on JDK 12+)

  • if used, move away from it

Cleaning house

Deprecated (for removal)

Finalization

What it is:

  • finalize() methods

  • a JLS/GC machinery for them

Finalization

What you need to know:

  • causes GC overhead and better alternatives exist

  • you can disable it with --finalization=disabled

  • in a future release, disabled will be the default

  • in a later release, finalization will be removed

Finalization

What you need to do:

  • search for finalize() in your code and
    replace with try-with-resources or Cleaner API

  • search for finalize() in your dependencies and
    help remove them

  • run your app with --finalization=disabled and
    closely monitor resource behavior (e.g. file handles)

Integrity by Default

Deprecated (for removal):

  • memory access via Unsafe ใ‰” (JEP 498)

Disallow by default:

Unsafe Memory Access

What it is:

  • methods on sun.misc.Unsafe

  • allow direct memory access

Unsafe Memory Access

What you need to know:

  • superseeded by
    VarHandles (JEP 193) and FFM (JEP 454)

  • will be phased out:

    1. deprecation and compile-time warning ใ‰“

    2. run-time warning ใ‰”

    3. exception on invocation

    4. methods removed

Unsafe Memory Access

What you need to do:

  • find with --sun-misc-unsafe-memory-access:

    • warn: issue run-time warnings ใ‰”

    • debug: same with more info

    • deny: throw exceptions

  • report and help fix!

Final Field Mutation

What it is:

  • parts of the reflecion API (โ‡ setAccessible)

  • can change values of most final fields

Final Field Mutation

What you need to know:

  • causes issues for:

    • correctness (any code can change any field)

    • performance (e.g. constant folding)

  • remains intact but app owner should opt in

  • Java 26 issues warnings on final field muation,
    except where explicitly enabled

  • a future version will disallow it where not enabled

Final Field Mutation

What you need to do:

  • use --enable-final-field-mutation to allow
    specified modules to mutate final fields

  • use --illegal-final-field-mutation for other code

Final Field Mutation

Three options for illegal mutation:

  • allow: allow without warning

  • warn: issue run-time warnings ใ‰–

  • debug: same with more info

  • deny: throw exceptions

In a future release, deny will become the only mode.

Prepare now by enabling mutation where necessary
plus --illegal-final-field-mutation=deny.

JNI

What it is:

  • machinery that allows Java programs
    to interact with native libraries

JNI

What you need to know:

  • native code can undermine Java’s integrity

  • remains intact but app owner should opt in

JNI

What you need to do:

  • use --enable-native-access to allow
    access to restricted JNI/FFM methods

  • use --illegal-native-access for other code

JNI

Three options for illegal native access:

  • allow: allow without warning

  • warn: issue run-time warnings ใ‰”

  • deny: throw exceptions

In a future release, deny will become the only mode.

Prepare now by enabling native access where necessary
plus --illegal-native-access=deny.

Agents

What it is:

  • a component that transforms byte code

  • uses java.lang.instrument or JVM TI

  • launches with JVM or attaches later ("dynamic")

Dynamic agents

What you need to know:

  • remains intact but app owner should opt in

  • nothing changed yet

  • in a future release, dynamic attach will be
    disabled by default

  • enable with -XX:+EnableDynamicAgentLoading

Dynamic agents

What you need to do:

  • run your app with -XX:-EnableDynamicAgentLoading

  • observe closely

  • investigate necessity of dynamic agents

More

Java 22-26

Major achievements:

  • pave the on-ramp

  • add essential lower-level APIs

  • improve tooling and performance

  • introduce AOT workflow

Get JDK 26 at jdk.java.net/26.

So long…​

37% off with
code fccparlog

bit.ly/the-jms

More

Slides at slides.nipafx.dev
โ‡œ Get my book!

Follow Nicolai

nipafx.dev
/nipafx

Follow Java

inside.java // dev.java
/java    //    /openjdk

Image Credits