Modern Java

Past | Present | Future

HKA

Developer Advocate

Java Team at Oracle

Modern Java

Past
Present
Future

Our story begins in 1991

Then, in 1995

Flying high โ‡ 2004

Vale of tears โ‡ 2011

Which brings us to 2014

Java 8 gets released:

  • lambda expressions

  • streams, date/time, Optional

Begins Java’s revival.

Revival โ‡ 2023

Too many improvements in Java 9-21 for the time we have!

Language:

  • modules, var, text blocks, records, sealed types

  • pattern matching with instanceof and switch

APIs:

  • collection factories, sequenced collections

  • PRNG, HTTP/2, VarHandle, ProcessHandle

  • uncountable small additions

Runtime:

  • virtual threads, compact strings

  • big strides for G1 and ZGC

  • performance gains across the board

Tooling:

  • jshell, jlink, jpackage, jwebserver

Revival โ‡ 2023

Organizational changes:

  • move to Git & GitHub

  • mailing list update ๐Ÿ™ˆ

  • six-month release cadence

  • two-year LTS cadence

Which reminds me…​

java 21 no lts

More

Modern Java

Past
Language Features
New APIs
Runtime Improvements
Better Tooling
Present
Future

Modern Java

Past
Language Features
New APIs
Runtime Improvements
Better Tooling
Present
Future

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)

Modern Java

Past
Language Features
New APIs
Runtime Improvements
Better Tooling
Present
Future

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 ThreadLocal:

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
}

FFM API

Allows interaction with

  • native libraries (foreign functions)

  • off-heap memory (foreign memory)

Replaces JNI, ByteBuffer and parts of Unsafe
with a better, safer, and supported API.

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.

Modern Java

Past
Language Features
New APIs
Runtime Improvements
Better Tooling
Present
Future

Multi-File Execution

Small programs can expand:

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

Run with:

java -cp 'Lib/*' Main.java

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:

Modern Java

Past
Language Features
New APIs
Runtime Improvements
Better Tooling
Present
Future

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`,
//          otherwise `false`
/// @since 1.6
@Override
public boolean isEmpty() { /* ... */ }

More

Going from Java 21 to 25:

Generally:

Modern Java

Past
Present
HTTP/3
PQC
Performance
PEM
Structured Concurrency
Deprecations & Removals
Future

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.

Check documentation for version selection.

More

HTTP/3 is final in JDK 26.

Modern Java

Past
Present
HTTP/3
PQC
Performance
PEM
Structured Concurrency
Deprecations & Removals
Future

Quantum computers

The fusion reactors of computer science

Today:

Can barely factor 35 into 5*7 (source).

Soonโ„ข:

Can factor numbers in polynomial time and space
(and thus break many encryption algorithms).

In the meantime:

Harvest now, decrypt later.

Post-quantum encryption

NIST standardized module-latice based algorithms:

  • ML-KEM to secure symmetric keys (FIPS 203)

  • ML-DSA for digital signatures (FIPS 204)

IETF developed a framework for
hybrid key exchange schemes for TLS 1.3 (RFC 9954).

PQC in Java

Java implements:

Oracle announced backports to
Oracle JDKs 25, 21, 17, 11, 8.

More

Modern Java

Past
Present
HTTP/3
PQC
Performance
PEM
Structured Concurrency
Deprecations & Removals
Future

Performance

Continuous performance improvements, e.g.:

  • reduced virtual thread pinning (JDK-8369238) ใ‰–

  • G1 across the board (particularly JEP 522) ใ‰– ใ‰—

  • G1 becomes true default GC (JEP 523) ใ‰—

  • compact object headers by default (JEP 534) ใ‰—

And many, many more: JDK 26, JDK 27.

characteristics

Java has really good peak performance,
but also tends to have:

  • slow startup time

  • slow warmup time

This is due to a lot of early work
(e.g. class loading, profiling, JIT).

AOTCache

Project Leyden introduces the AOTCache:

  • observe the JVM

  • capture decisions in an AOTCache

  • use as "initial state" during future run

  • fall back to live observation/optimization
    if necessary and possible

AOT computation

Creating and using the AOTCache:

# 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 ...

Class loading & linking

Improve startup time by making the classes of an application instantly available, in a loaded and linked state, when the HotSpot JVM starts.

Spring PetClinic benchmarks:

  • up to ~40% startup time reduction

  • AOT cache size of ~130 MB

Method profiling

Improve warmup time by making method-execution profiles from a previous run of an application instantly available, when the HotSpot Java Virtual Machine starts.

Benchmark of a 100_000x loop over a simple stream:

  • ~20% run time reduction

  • AOT cache size increased by ~2.5%

Streaming cached objects

Making it possible to load cached Java objects sequentially into memory from a neutral, GC-agnostic format.

  • allows use of any garbage collector

  • does not block when initializing heap

  • takes longer for warm starts and
    requires a CPU core

(Note: The cache contains no application instances.)

Code Compilation

Improve startup and warmup time by making optimized native code for an application instantly available when the HotSpot JVM starts.

Benchmarks with javac:

  • ~15% startup time reduction

  • ~75% fewer warmup iterations

AOT limitations

  • for code compilation: AArch64 or x64

  • training vs production:

    • same JDK release / architecture / OS

    • for code compilation: same CPU features & GC

    • consistent class path

    • consistent module options

  • limited use of JVMTI agents

Otherwise, unsuitable portions are ignored.

More

  • ๐Ÿ“ JEP 483: AOT Class Loading and Linking ใ‰”

  • ๐Ÿ“ JEP 515: AOT Method Profiling ใ‰•

  • ๐Ÿ“ JEP 514: AOT CLI Ergonomics ใ‰•

  • ๐Ÿ“ JEP 516: AOT Object Caching with Any GC ใ‰–

  • ๐Ÿ“ JEP 544: AOT Code Compilation ใ‰˜

Modern Java

Past
Present
HTTP/3
PQC
Performance
PEM
Structured Concurrency
Deprecations & Removals
Future

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-----

PEM API

PEM API encodes and decodes cryptographic objects:

X509Certificate cert = // ...

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

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

assert cert.equals(cert2);

Encodable

All implementations of BinaryEncodable 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 BinaryEncodable>):

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

More

The PEM API is final in JDK 28.

Modern Java

Past
Present
HTTP/3
PQC
Performance
PEM
Structured Concurrency
Deprecations & Removals
Future

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

String executeTasks() throws InterruptedException {
	// implicitly short-circuits on error
	try (var scope = StructuredTaskScope.open()) {
		Subtask<String> taskA = scope.fork(this::doA);
		Subtask<String> taskB = scope.fork(this::doB);

		// wait explicitly for success
		// (throws errors if there were any)
		scope.join();

		// all tasks succeeded
		return taskA.get() + taskB.get();
	} catch (FailedException ex) {
		return ex.getMessage();
	}
}

Properties

Threads are short-lived:

  • start when task begins

  • end on completion

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

Completion

Use Joiner to configure success/failure policy:

  • how are results collected?

  • when are subtasks cancelled?

  • when does join throw?

Pass to StructuredTaskScope.open(Joiner).

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
}

Structured concurrency

Benefits:

  • defines a scope for concurrency

  • simplifies control flow

  • simplifies grouped result/error handling

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

More

The structured concurrency API will probably finalize in JDK 28.

Modern Java

Past
Present
HTTP/3
PQC
Performance
PEM
Structured Concurrency
Deprecations & Removals
Future

Cleaning house

Already removed:

Cleaning house

Deprecated (for removal)

  • finalization โ‘ฑ (JEP 421)

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

Integrity by default

Disallow by default:

More

Modern Java

Past
Present
Future
Amber’s Arc
Leyden’s Arc
JSON (no Arc)
Valhalla’s Arc
Babylon’s Arc

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 A: int x = 0;

  • x can’t literally be an instance of byte

  • but its value can be a byte

Example B: int y = 16_777_217;

  • y can’t literally be an instance of float

  • it can be cast to a float but not losslessly

Conversion checks

Primitive patterns for simpler conversion checks:

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

int y = 16_777_217;
if (y instanceof float f)
	IO.println(f + " is a float (lossless)");

(Probably sixth preview in JDK 28.)

Strawman syntax

Everything coming next is speculative,
particularly the syntax!

โš ๏ธ

Constant checks

Constant patterns for simpler primitive checks:

record Point(int x, int y) { }

var point = // ...
switch (point) {
	// primitive pattern
	case Point(var x, _) when x == 0 -> // ...
	// constant pattern (probably)
	case Point(0, _) -> // ...
}

Currently, only records can be deconstructed.

Deconstructible interfaces

Expand deconstruction to other types:

// โ†™ INTERFACE   โ†™ state description
interface Point(int x, int y) {
	// state description implicitly requires:
	// int x();
	// int y();
}

// would allow
var point = // ...
switch (point) {
	case Point(var x, var y) -> // ...
}

Deconstruction on assignment

Often, there’s no need for the conditional.

Deconstruction on assignment is unconditional:

// `Point` is a type with state description
Point nextPoint() { /* ... */ }

// if you only need `x`
Point(int x, _) = nextPoint();

Reconstruction

If the type has a symmetrical construction protocol
(like records):

record Point(int x, int y) { }

var p0 = new Point(0, 0);
var p1 = p0 with { x = 1; };

More

Patterns and deconstruction:

Other endeavors:

  • string templates (yes, still)

  • ๐ŸŽฅ talks #1, #2 on serialization 2.0

  • ๐Ÿ“ JEP draft: Concise Method Bodies

Modern Java

Past
Present
Future
Amber’s Arc
Leyden’s Arc
JSON (no Arc)
Valhalla’s Arc
Babylon’s Arc

Project Leyden

Introduced the AOTCache in JDK 24
and has expanded it ever since.

But is not done yet!

Project Leyden

Specific explorations:

  • allow tradeoff between
    portability and peak performance

  • iterative training

  • better inspectability of training data

General goals:

  • more startup/warmup improvements

  • better usability

More

Modern Java

Past
Present
Future
Amber’s Arc
Leyden’s Arc
JSON (no Arc)
Valhalla’s Arc
Babylon’s Arc

JSON API

A simple API to parse, traverse, and format JSON:

var jsonDoc = """
	{
		"users": [
			{ "name": "John Doe" },
			{ "name": "Jane Doe" }
		]
	}
	""";

JsonValue json = Json.parse(jsonDoc);

JsonValue

public sealed interface JsonValue permits
		JsonObject, JsonArray, JsonNull
		JsonString, JsonNumber, JsonBoolean {

	// ...

}

JsonValue API

String asString();
double asDouble();
long asLong();
int asInt();
boolean asBoolean();

List<JsonValue> asList();

Map<String, JsonValue> asMap();
JsonValue get(String name);
JsonValue get(int index);

Optional<JsonValue> tryGet(String name);
Optional<JsonValue> tryValue();

Using JsonValue

JsonValue json = Json.parse(jsonDoc);
// if structure is known
var name = json
	.get("users")
	.get(0)
	.get("name");
var names = json
	.get("users")
	.asList().stream()
	.map((JsonValue user) -> user.get("name"))
	.toList();

Using JsonValue

JsonValue json = Json.parse(jsonDoc);
// if structure is unknown
switch (json) {
	case JsonString string -> // ...
	case JsonNumber number -> // ...
	// ...
}

Advanced features

This API is a "JSON starter set":

  • no parsing configuration

  • no streaming

  • no data binding

Advanced features are left to ecosystem projects.

More

The JSON API is incubating for the first time in JDK 28.

  • ๐Ÿ“ JEP 540: Simple JSON API (Incubator)

Modern Java

Past
Present
Future
Amber’s Arc
Leyden’s Arc
JSON (no Arc)
Valhalla’s Arc
Babylon’s Arc

Valhalla reset

You’ve probably heard a lot about Project Valhalla:

  • indirection and flattening

  • object headers and density

  • identity and custom primitives

  • universal and specialized generics

  • performance benefits

Forget all that for now!

Identity

Current state:

  • all reference types have identity

  • identity distinguishes objects

  • many equal immutable objects are interchangeable
    (e.g. two LocalDate.of(2026, 7, 31))

For many immutable classes:

  • identity is meaningless

  • identity can cause confusion

  • identity comes with a run-time cost

Wouldn’t it be nice to be able to opt out of identity?

Value classes

Enter JEP 401 in JDK 28 with --enable-preview:

We introduce value objects to model simple immutable data. A value object is an instance of a value class, declared with the value modifier. Classes without the value modifier are identity classes, and their instances are identity objects.

Platform value classes

With previews enabled, 30 JDK classes are value classes:

  • primitive wrappers

  • Optional types

  • many date/time API types

Custom value classes

You can create your own by adding value:

value class ComplexNumber {
	// ...
}

value record Point(int x, int y) {
	// ...
}

(Fun fact: The only new syntax is value.)

Business as usual

In most respects, value objects work the way that objects have always worked in the language: They have fields and methods, they are handled by reference, and their references can be null.

Declaration differences

Declaration site restrictions:

  • fields and class are final

  • can’t extends identity classes
    (except java.lang.Object)

  • by default, construction code runs
    during early construction

Behavioral differences

Behavioral differences vs identity classes:

  • new may not allocate a fresh object

  • == does a field-wise comparison

Behavioral restrictions:

  • no deep reflection

  • no object monitor interaction

  • no finalization

  • limited serialization (records or proxies)

  • no GC interaction

What other benefits can we get?

Optimizations

Optimization of value objects:

  • JVM can flatten and scalarize some references

  • flattening is limited to small values

  • scalarization is more universally applicable

But, as (almost) always in Java:

  • write maintainable code

  • use constructs semantically

  • rely on JVM for optimization

Future work

Project Valhalla has a long way to go:

  • manage nullity and atomicity to increase flattening

  • introduce type classes for limited operator overloading

  • add universal and specialized generics
    for a better language and more flattening

More

Value classes have their first preview in JDK 28.

Modern Java

Past
Present
Future
Amber’s Arc
Leyden’s Arc
JSON (no Arc)
Valhalla’s Arc
Babylon’s Arc

Motivation

Java is adjacent to other programmable systems:

  • GPUs and FPGAs

  • SQL databases

  • differentiable functions

Allow programming them with Java code.

Project Babylon

Don’t adapt to each realm in a separate project.

Instead:

  • make Java code accessible

  • provide API to read and transform it

  • let ecosystem provide adaptions

Code Reflection

Babylons’s central mechanism is code reflection:

  • enhancement of "regular" reflection

  • reaches down into methods/lambdas

  • symbolic representation of (Java) code

These are called code models.

NIH?

Abstract syntax tree:

  • constructed during compilation

  • closely aligned with Java grammar

  • too much syntactic info

Bytecode:

  • created by compiler

  • specified by JVM Specification

  • too little important info

Code Models

The code model design is heavily influenced by the design of data structures used by many modern compilers to represent code. These data structures are commonly referred to as Intermediate Representations (IRs). The design is further influenced by Multi-Level Intermediate Representation (MLIR), a sub-project of the LLVM Compiler Infrastructure project.

Code Models

Identify code (e.g. with annotation):

@CodeReflection
static double sub(double a, double b) {
   return a - b;
}

Then:

  • compiler creates code model

  • stored in class files

  • accessible via reflection API

  • can be transformed by Java code

More

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