Java 24

Better Language, Better APIs, Better Runtime

Developer Advocate

Java Team at Oracle

Let’s get started!

Lots to talk about!

Final features in Java 24:

  • AOT class loading & linking

  • less virtual thread pinning

  • stream gatherers

  • class-file API

Lots to talk about!

Final features in Java 23:

  • Markdown in JavaDoc

  • generational ZGC by default

Lots to talk about!

Final features in Java 22:

  • unnamed patterns

  • FFM API

  • multi-source-file programs

Lots to talk about!

Some preview features:

  • primitive patterns

  • module imports

  • flexible constructor bodies

  • string templates

Java 24

Final in Java 24
AOT Class Loading & Linking
Less Virtual Thread Pinning
Stream Gatherers
Class-File API
Final in Java 23
Final in Java 22
Preview in Java 24

Java Performance

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

  • slow startup time

  • slow warmup time

Startup & Warmup

Early work by the runtime:

  • class loading

  • callsite linkage

  • constant pool resolution

  • interpretation

  • profile gathering

  • JIT compilation (C1, C2)

Can we do this ahead of time?

Dynamic Java

But Java is highly dynamic:

  • class loading

  • class redefinition

  • linkage

  • access control

  • method dispatch

  • run-time typing (e.g. casting)

  • introspection

  • JIT compilation, decompilation

How to AOT everything?

Enter AOTCache

Project Leyden introduces AOTCache:

  • observe JVM

  • capture decisions in AOTCache
    (expansion of CDS Archive)

  • use as "initial state" during future run

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

AOT workflow

# training run (โ‡ profile)
$ java -XX:AOTMode=record
       -XX:AOTConfiguration=app.aotconf
       -cp app.jar com.example.App ...
# assembly phase (profile โ‡ AOTCache)
$ java -XX:AOTMode=create
       -XX:AOTConfiguration=app.aotconf
       -XX:AOTCache=app.aot
       -cp app.jar
# production run (AOTCache โ‡ performance)
$ java -XX:AOTCache=app.aot
       -cp app.jar com.example.App ...

(Open to improvements.)

AOT 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

AOT class loading & linking

Limitation:

  • same JDK release / hardware / OS

  • consistent class path for training and production

  • consistent module options

  • limited use of JVMTI agents

Otherwise, AOTCache is ignored.

More

Java 24

Final in Java 24
AOT Class Loading & Linking
Less Virtual Thread Pinning
Stream Gatherers
Class-File API
Final in Java 23
Final in Java 22
Preview in Java 24

Virtual Threads

A virtual thread:

  • is a regular Thread

  • low memory footprint (stack + bytes)

  • small switching cost

  • scheduled by the Java runtime

  • executes on platform thread

  • waits in memory
    (no platform thread blocked)

Exceptions

Pinning:
  • a pinned VT will block the PT

  • caused by object monitors,
    native calls, class initialization

Capture:
  • a captured VT blocks the PT

  • caused by file I/O

Object monitor pinning

Object monitor implementation:

  • was bound to OS threads

  • required deep refactoring
    to work with VTs

  • fix ships with JDK 24

โ‡ No more pinning for synchronized.

More

Java 24

Final in Java 24
AOT Class Loading & Linking
Less Virtual Thread Pinning
Stream Gatherers
Class-File API
Final in Java 23
Final in Java 22
Preview in Java 24

Missing Stream Ops

Streams are great, but some
intermediate operations are missing:

  • sliding windows

  • fixed groups

  • take-while-including

  • scanning

  • increasing sequences

  • etc.

Missing Terminal Ops

Streams also don’t have all possible terminal operations.

Instead:

  • generalization for terminal ops โ‡ collectors

  • a few implementations, e.g. Collectors.toSet()

  • extension point for them: Stream::collect

Let’s do the same for intermediate ops!

Introducing Gatherers

The gatherers API consists of:

  • generalization for intermediate ops โ‡ gatherers

  • a few implementations, e.g. Gatherers.scan(โ€ฆ)

  • extension point for them: Stream::gather

Stream.of("A", "C", "F", "B", "S")
	.gather(scan(...))
	.forEach(System.out::println);

Gatherer Building Blocks

One required building block:

Integrator
  • accepts (state, element, downstream)

  • has the task to combine state and element

    • to update the state

    • to emit 0+ result(s) to downstream

Integrator Example

Behaves transparently:

static <T> Gatherer<T, ?, T> transparent() {
	Integrator<Void, T, T> integrator = (_, el, ds)
		-> ds.push(el);
	return Gatherer.of(integrator);
}

Integrator Example

Reimplements Stream::map:

static <T, R> Gatherer<T, ?, R> map(Function<T, R> f) {
	Integrator<Void, T, R> integrator = (_, el, ds) -> {
		R newEl = f.apply(el);
		return ds.push(newEl);
	};
	return Gatherer.of(integrator);
}

Gatherer Building Blocks

Three optional building blocks:

Initializer:

creates instance(s) of state

Finisher:
  • accepts (state, downstream)

  • emits 0+ element(s) to downstream

Combiner:

combines to states into one

Fixed-Sized Groups

Create groups of fixed size:

  • stream input: "A", "C", "F", "B", "S"

  • output of groups(2): ["A", "C"], ["F", "B"], ["S"]

We need:

  • an initializer to create empty group list

  • an integrator that emits when group is full

  • a finisher to emit incomplete group

Fixed-Sized Group Initializer

Supplier<List<T>> initializer = ArrayList::new;

Fixed-Sized Group Integrator

Integrator<List<T>, T, List<T>> integrator =
	(list, el, ds) -> {
		list.add(el);

		if (list.size() < size)
			return true;
		else {
			var group = List.copyOf(list);
			list.clear();
			return ds.push(group);
		}
	};

Fixed-Sized Group Finisher

BiConsumer<List<T>, Downstream<List<T>>> finisher =
	(list, ds) -> {
		var group = List.copyOf(list);
		ds.push(group);
	};

Fixed-Sized Group Gatherer

static <T> Gatherer<T, ?, List<T>> groups(int size) {
	Supplier<...> initializer = // ...
	Integrator<...> integrator = // ...
	BiConsumer<...> finisher = // ...

	return Gatherer.ofSequential(
		initializer, integrator, finisher);
}

Fixed-Sized Group Gatherer

Using our gatherer:

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

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

More

Java 24

Final in Java 24
AOT Class Loading & Linking
Less Virtual Thread Pinning
Stream Gatherers
Class-File API
Final in Java 23
Final in Java 22
Preview in Java 24

Bytecode Basics

Bytecode is instruction set for JVM:

  • creating objects and arrays

  • copying variable values or references
    between stack and registers

  • invoking methods

  • computing arithmetic operations

  • etc.

Bytecode Basics

Basic lifecycle:

  • generated by javac

  • stored in .class files

  • loaded, parsed, verified by class loader

  • executed by JVM

Bytecode Beyond Basics

In real life, much more happens:

  • generated by frameworks at build time

  • turned into machine code by JIT compiler
    or Graal native image

  • prefetched by AOTCache

  • analyzed by jdeps, SpotBugs, etc.

  • manipulated by agents and libraries

Bytecode Tools

Tooling:

  • libraries don’t manipulate bytecode themselves

  • they use a few tools

Big player is ASM
(direct or, e.g., via ByteBuddy or CGLIB).

Migration Pains

Updates:

  • bytecode has a level (e.g. 65 for Java 21)

  • tools can’t work with a higher level
    than they were built for

This can block updates!

E.g. when compiling your code with Java 25 (level 69)…​

Migration Pains

This is the reason for:

Before updating the JDK,
update all dependencies.

We want to move past that!

Class-File API

An API in Java that allows
analyzing and manipulating bytecode:

  • stable API in JDK

  • always up-to-date

When JDK is updated:

  • it may read new bytecodes

  • but that’s ok for most use cases

More

Deprecations

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

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

Cleaning house

Deprecations (for removal):

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

  • Linux 32-bit x86 port ใ‰” (JEP 501)

  • allowed-by-default JNI ใ‰” (JEP 472)

  • allowed-by-default dynamic agents ใ‰‘ (JEP 451)

  • finalization โ‘ฑ (JEP 421)

  • applet API โ‘ฐ (JEP 398, JDK-8345525 ๐Ÿ‘€)

  • primitive wrapper constructors โ‘ฏ (JEP 390)

Unsafe

Methods for memory access have been superseeded
(VarHandles via JEP 193; FFM via JEP 454).

Will be phased out:

  • deprecation and compile-time warning ใ‰“

  • run-time warning ใ‰”

  • exception on invocation ~ใ‰–

  • remove methods

Unsafe

You’re probably not using it directly,
but your dependencies may.

Find them with --sun-misc-unsafe-memory-access:

  • warn: to get run-time warnings

  • debug: same with more info

  • deny: throws exception

Report and help fix!

JNI

Native code can undermine Java’s integrity.

App owner should opt in knowingly:

  • 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

  • warn (default on JDK 24)

  • deny

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

Prepare now by setting --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:

  • all mechanisms for agents remain intact

  • nothing changed yet

  • in the future, 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

Finalization

What it is:

  • finalize() methods

  • a JLS/GC machinery for them

Finalization

What you need to know:

  • you can disable 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)

Primitive constructors

What it is:

  • new Integer(42)

  • new Double(42)

  • etc.

Primitive constructors

What you need to know:

  • Valhalla wants to turn them into value types

  • those have no identity

  • identity-based operations need to be removed

Primitive constructors

What you need to do:

  • Integer.valueOf(42)

  • Double.valueOf(42)

  • etc.

More

What’s next?

  • Markdown in JavaDoc ใ‰“

  • generational ZGC by default ใ‰“

  • unnamed patterns ใ‰’

  • FFM API ใ‰’

  • multi-source-file programs ใ‰’

  • primitive patterns โŠ•

  • module imports โŠ•

  • flexible constructor bodies โŠ•

  • string templates โŠ•

Java 24

Final in Java 24
Final in Java 23
Markdown in JavaDoc
GenZGC by Default
Final in Java 22
Preview in Java 24

Writing JavaDoc

Writing simple JavaDoc is great!

Writing more complex documentation…​

  • where does <p> go?

  • do we need </p>?

  • code snippets/blocks are cumbersome

  • lists are verbose

  • tables are terrible

  • …​

I blame HTML!

Markdown

Markdown is more pleasant to write:

  • neither <p> nor </p>

  • code snippts/blocks are simple

  • lists are simple

  • tables are less terrible

  • embedding HTML is straightforward

Markdown is widely used and known.

Markdown in JavaDoc

Java now allows Markdown JavaDoc:

  • each line starts with ///

  • CommonMark 0.30

  • links to program elements use extended
    reference link syntax: [text][element]

  • JavaDoc tags work as usual

[Introduced in Java 23 — JEP 467]

Why /// ?

Wouldn’t this be nice:

/**md
 *
 * Markdown here...
 *
 */

Why /// ?

No - reason #1:

/**md
 *
 * Here's a list:
 *
    * item #1
    * item #1
 *
 */

(The leading * in JavaDoc is optional.)

Why /// ?

No - reason #2:

/**md
 *
 * ```java
 * /* a Java inline comment */
 * ```
 *
 */

(/** can’t contain */.)

Why /// ?

///:

  • no such issues

  • doesn’t require new Java syntax
    (// already "escapes" parsing)

Code

Inline code with `backticks`.

Code blocks with fences, e.g.:

```java
public void example() { }
```

A language tag is set as a CSS class
for JS-based highlighting in the frontend.

(Add a library with javadoc --add-script …​.)

Tables

Markdown tables:

  • better than HTML tables

  • still uncomfortable to create manually

  • use something like tablesgenerator.com

Advanced tables:

  • for features unsupported in Markdown,
    create HTML tables

Tags

JavaDoc tags work as expected:

  • can be used in Markdown comments

  • if they contain text, Markdown syntax works

/// For more information on comments,
/// see {@jls 3.7 Comments}.
///
/// @implSpec this implementation does _nothing_
public void doSomething() { }

More

Java 24

Final in Java 24
Final in Java 23
Markdown in JavaDoc
GenZGC by Default
Final in Java 22
Preview in Java 24

Generational ZGC

Compared to other GCs, ZGC:

  • optimizes for ultra-low pause times

  • can have higher memory footprint or higher CPU usage

In JDK 21, ZGC became generational.

Generational Hypothesis

  • most objects die young

  • those who don’t, grow (very) old

GCs can make use of this by tracking
young and old generations.

ZGC didn’t do this, but can do it now.

Reports

Netflix published a blog post on their adoption.

Out of context graphs (vs. G1):

netflix genzgc memory

Reports

netflix genzgc gc pause

Default

Generational mode is:

  • the default on JDK 23

  • the only mode on JDK 24+

-XX:+UseZGC

(Default GC is still G1.)

More

Java 24

Final in Java 24
Final in Java 23
Final in Java 22
Unnamed Patterns
FFM API
Launch Multi-File Programs
Preview in Java 24

Underscore

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++;
}

That last one is very important!

A simple app

Features:

  • scrapes GitHub projects

  • creates Page instances:

    • GitHubIssuePage

    • GitHubPrPage

    • ExternalPage

    • ErrorPage

  • further processes pages

A simple app

Features:

  • display as interactive graph

  • compute graph properties

  • categorize pages by topic

  • analyze mood of interactions

  • process payment for analyses

  • etc.

A simple architecture?

How to implement features?

  • methods on Page ๐Ÿ˜ง

  • visitor pattern ๐Ÿ˜ซ

  • pattern matching ๐Ÿฅณ

Pattern Matching

Ingredients:

  • switch expressions โ‘ญ (JEP 361)

  • type pattern matching โ‘ฏ (JEP 394)

  • sealed types โ‘ฐ (JEP 409)

  • patterns in switch ใ‰‘ (JEP 441)

  • unnamed patterns ใ‰’ (JEP 456)

Pattern Matching

Approach:

  • make Page sealed

  • implement features as methods outside of Page

  • accept Page parameters and switch over it

  • avoid default branch for maintainability

Sealed Page

Sealed types limit inheritance,
by only allowing specific subtypes.

public sealed interface Page
	permits GitHubIssuePage, GitHubPrPage,
			ExternalPage, ErrorPage {
	// ...
}

Switch over Page

public Category categorize(Page page) {
	return switch (page) {
		case GitHubIssuePage is -> categorizeIssue(is);
		case GitHubPrPage pr -> categorizePr(pr);
		case ExternalPage ext -> categorizeExternal(ext);
		case ErrorPage err -> categorizeError(err);
	}
}

Default Behavior

Sometimes you have "defaulty" behavior:

public Category categorize(Page page) {
	return switch (page) {
		// categorize only GitHub pages
		case GitHubIssuePage is -> categorizeIssue(is);
		case GitHubPrPage pr -> categorizePr(pr);
		// return Category.NONE for other page types
	}
}

How to handle remaining cases?

Maintainability

Unlike an if-else-if-chain,
a pattern switch needs to be exhaustive.

Fulfilled by:

  1. a default branch

  2. explicit branches:

    • switching over a sealed types

    • a case per subtype

Default Branches

Option 1:

public Category categorize(Page page) {
	return switch (page) {
		case GitHubIssuePage is -> categorizeIssue(is);
		case GitHubPrPage pr -> categorizePr(pr);
		default -> Category.NONE;
	}
}

Default Branches

If GitHubCommitPage is added:

public Category categorize(Page page) {
	return switch (page) {
		case GitHubIssuePage is -> categorizeIssue(is);
		case GitHubPrPage pr -> categorizePr(pr);
		// `GitHubCommitPage` gets no category!
		default -> Category.NONE;
	}
}

โ‡ Adding a new subtype causes no compile error! โŒ

Explicit Branches In Java 21

Option 2 in Java 21
(without preview features):

public Category categorize(Page page) {
	return switch (page) {
		case GitHubIssuePage is -> categorizeIssue(is);
		case GitHubPrPage pr -> categorizePr(pr);
		// duplication ๐Ÿ˜ข
		case ErrorPage err -> Category.NONE;
		case ExternalPage ext -> Category.NONE;
	};
}

Explicit Branches In Java 21

If GitHubCommitPage is added:

public Category categorize(Page page) {
	// error:
	//     "the switch expression does not cover
	//      all possible input values"
	return switch (page) {
		case GitHubIssuePage is -> categorizeIssue(is);
		case GitHubPrPage pr -> categorizePr(pr);
		case ErrorPage err -> Category.NONE;
		case ExternalPage ext -> Category.NONE;
	}
}

โ‡ Adding a new subtype causes a compile error! โœ…

Explicit Branches

Would be nice to combine branches:

public Category categorize(Page page) {
	return switch (page) {
		case GitHubIssuePage is -> categorizeIssue(is);
		case GitHubPrPage pr -> categorizePr(pr);
		case ErrorPage err, ExternalPage ext
			-> Category.NONE;
	};
}

Doesn’t make sense.
(Neither err nor ext would be in scope.)

Explicit Branches in Java 22

Use _ to combine "default branches":

public Category categorize(Page page) {
	return switch (page) {
		case GitHubIssuePage is -> categorizeIssue(is);
		case GitHubPrPage pr -> categorizePr(pr);
		case ErrorPage _, ExternalPage _
			-> Category.NONE;
	};
}

โ‡ Default behavior without default branch. ๐Ÿฅณ

More

Java 24

Final in Java 24
Final in Java 23
Final in Java 22
Unnamed Patterns
FFM API
Launch Multi-File Programs
Preview in Java 24

Foreign memory

Storing data off-heap is tough:

  • ByteBuffer is limited (2GB) and inefficient

  • Unsafe is…​ unsafe and not supported

Foreign-memory API

Panama introduces safe and performant API:

  • control (de)allocation:
    Arena, MemorySegment, SegmentAllocator

  • to access/manipulate: MemoryLayout, VarHandle

Foreign-memory API

// 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-memory API

Allocate off-heap memory to store pointers:

String[] javaStrings = { "mouse", "cat", "dog" };
// Arena offHeap = ...

MemorySegment pointers = offHeap.allocateArray(
	ValueLayout.ADDRESS, javaStrings.length);
for (int i = 0; i < javaStrings.length; i++) {
	// allocate off-heap & store a pointer
	MemorySegment cString = offHeap
		.allocateUtf8String(javaStrings[i]);
	pointers
		.setAtIndex(ValueLayout.ADDRESS, i, cString);
}

Foreign-memory API

Copy data back to heap:

// String[] javaStrings = ...
// MemorySegment pointers =

for (int i = 0; i < javaStrings.length; i++) {
	MemorySegment cString = pointers
		.getAtIndex(ValueLayout.ADDRESS, i);
	javaStrings[i] = cString.getUtf8String(0);
}

Foreign functions

JNI isn’t ideal:

  • involves several tedious artifacts (header file, impl, …​)

  • can only interoperate with languages that align
    with OS/architecture the JVM was built for

  • doesn’t reconcile Java/C type systems

Foreign-function API

Panama introduces streamlined tooling/API
based on method handles:

  • jextract: generates method handles from header file

  • classes to call foreign functions
    Linker, FunctionDescriptor, SymbolLookup

Foreign-function API

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

String[] javaStrings = { "mouse", "cat", "dog" };
try (Arena offHeap = Arena.ofConfined()) {
	// [move Java strings off heap]
	// invoke foreign function
	radixSort.invoke(
		pointers, javaStrings.length,
		MemorySegment.NULL, '\0');
	// [copy data back to heap]
}

Finally final!

Java 22 finalizes the FFM API, but there’s more to do:

  • user-friendly and performant mapping from
    native memory to Java records/interfaces

  • improving jextract and surrounding tooling

And more.

More

Java 24

Final in Java 24
Final in Java 23
Final in Java 22
Unnamed Patterns
FFM API
Launch Multi-File Programs
Preview in Java 24

A Mature Ecosystem

Java is very mature:

  • refined programming model

  • detailed toolchain

  • rich ecosystem

But this can make it hard to learn for new (Java) developers.

Approachable Java

Java needs to be approachable:

  • for kids

  • for students

  • for the frontend dev

  • for ML/AI folks

  • etc.

Java needs an on-ramp for new (Java) developers!

On-Ramp to Java

On-ramp:

  • simplified main method and class

  • single-source-file execution

  • multi-source-file execution

Simpler Code

Remove requirement of:

  • String[] args parameter

  • main being static

  • main being public

  • the class itself

// smallest viable Main.java
void main() {
	// ...
}

[Preview in Java 24 — JEP 495]

Simpler Code

Implicitly declared classes, implicitly import:

  • java.io.IO 's methods print, println, and readln

  • public top-level classes in packages exported by java.base

// complete Main.java
void main() {
	var letters = List.of("A", "B", "C");
	println(letters);
}

Single-File Execution

It’s easy to execute that file:

java Main.java

[Introduced in Java 11 — JEP 330]

Multi-File Execution

The program can expand:

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

Run with:

java -cp 'Lib/*' Main.java

[Introduced in Java 22 — JEP 458]

Progression

Natural progression:

  • start with main()

  • need arguments? โ‡ add String[] args

  • need to organize code? โ‡ add methods

  • need shared state? โ‡ add fields

  • need more functionality? โ‡ explore JDK APIs

  • even more? โ‡ explore simple libraries

  • need more structure? โ‡ split into multiple files

  • even more โ‡ use visibility & packages & modules

Doesn’t even have to be that order!

More

Simplified main:

Single-source-file execution:

Multi-source-file execution:

Java 24

Final in Java 24
Final in Java 23
Final in Java 22
Preview in Java 24
Primitive Patterns
Module Imports
Flexible Constructor Bodies
String Templates

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 รฌnt.

But can its value be a byte?

Yes!

Bound Checks

int x = 0;
if (x instanceof byte b)
	System.out.println(b + " in [-128, 127]");
else
	System.out.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_216;
if (x instanceof float f)
	// use `f`

Tri-state Boolean

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

(Bugged in 23; fixed in 23.0.1 and 24-EA.)

More

Java 24

Final in Java 24
Final in Java 23
Final in Java 22
Preview in Java 24
Primitive Patterns
Module Imports
Flexible Constructor Bodies
String Templates

Imports

Which one do you prefer?

// option A
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

// option B
import java.util.*;

Star Imports

Upsides:

  • more succinct

  • easier to manage manually

Downsides:

  • less clear

  • chance of conflicts

  • arbitrary slice of API

Module Imports

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

  • your code does not need to be in a module

[Preview in Java 23 — JEP 476]

Module Import Details

import module $mod;

Imports all public top-level types in:

  • packages exported by $mod

  • packages exported by $mod to your module
    (qualified exports)

  • packages exported by modules that $mod
    requires transitively (implied readability)

Module Imports

Upsides:

  • much more succinct

  • trivial to manage manually

Downsides:

  • less detailed

  • conflicts are more likely

Conflicts

import module java.base;
import module java.desktop;
import module java.sql;

public class Example {

	public void example() {
		// error: reference to Date is ambiguous
		var outdated = new Date(1997, 1, 18);
		// error: reference to List is ambiguous
		var letters = List.of("I", "J", "N");
	}

}

Resolving Conflicts

import module java.base;
import module java.desktop;
import module java.sql;

import java.util.Date;
import java.util.List;

public class Example {

	public void example() {
		var outdated = new Date(1997, 1, 18);
		var letters = List.of("I", "J", "N");
	}

}

Use Cases

Consider using module imports, when:

  • you’re already using star imports

  • you’re writing scripts, experiments, demos, etc.

Default Import

Implicitly declared classes, implicitly import java.base:

// complete Main.java - no explicit imports!
void main() {
	List<?> dates = Stream
		.of(1, 2, 23, 29)
		.map(BigDecimal::new)
		.map(day -> LocalDate.of(
			2024,
			RandomGenerator.getDefault()
				.nextInt(11) + 1,
			day.intValue()))
		.toList();

	System.out.println(dates);
}

More

Java 24

Final in Java 24
Final in Java 23
Final in Java 22
Preview in Java 24
Primitive Patterns
Module Imports
Flexible Constructor Bodies
String Templates

Constructor Chaining

With multiple constructors, it’s good practice
to have one constructor that:

  • checks all arguments

  • assigns all fields

Other constructors just forward (if possible).

Constructor Chaining

class Name {

	private final String first;
	private final String last;

	Name(String first, String last) {
		// [... checks for null, etc. ...]
		this.first = first;
		this.last = last;
	}

	Name(String last) {
		this("", last);
	}

}

Inheritance

With superclasses, chaining is enforced:

class ThreePartName extends Name {

	private final String middle;

	ThreePartName(
			String first, String middle, String last) {
		// doesn't compile without this call
		super(first, last);
		this.middle = middle;
	}

}

Limitations

But:

Java allows no statements before
super(…​) / this(…​)!

Why?

No statements before super(…​) / this(…​):

  • superclass should be initialized
    before subclass runs any code
    โ‡ no code before super(…​)

  • code before this(…​) would
    run before super(…​)
    โ‡ no code before this(…​)

Limitations

This is inconvenient when you want to:

  • check arguments

  • prepare arguments

  • split/share arguments

Splitting Arguments

class Name {

	// fields and constructor as before

	Name(String full) {
		// does the same work twice
		this(
			full.split(" ")[0],
			full.split(" ")[1]);
	}

}

Splitting Arguments

class Name {

	// fields and constructor as before

	// avoids two splits but "costs"
	// duplicated argument validation
	Name(String full) {
		String[] names = full.split(" ");
		// [... checks for null, etc. ...]
		this.first = names[0];
		this.last = names[1];
	}

}

Splitting Arguments

class Name {

	// fields and constructor as before

	// avoids two splits but "costs"
	// an additional constructor
	Name(String full) {
		this(full.split(" "));
	}

	private Name(String[] names) {
		this(names[0], names[1]);
	}

}

Splitting Arguments

class Name {

	// fields and constructor as before

	// avoids two splits but "costs"
	// an additional construction protocol
	static Name fromFullName(String full) {
		String[] names = full.split(" ");
		return new Name(names[0], names[1]);
	}

}

Limitations - Record Edition

To enforce a uniform construction protocol:

Records require all custom constructors
to (eventually) call the canonical constructor.

Limitations - Record Edition

record Name(String first, String last) {

	// nope
	Name(String full) {
		String[] names = full.split(" ");
		// [... checks for null, etc. ...]
		this.first = names[0];
		this.last = names[1];
	}

}

Splitting Arguments

What we want to write:

record Name {

	Name(String full) {
		String[] names = full.split(" ");
		this(names[0], names[1]);
	}

}

(Analogous for classes.)

Flexible Constructor Bodies

Java 23 previews statements
before super(…​) and this(…​).

Great to…​

Check Arguments

class ThreePartName extends Name {

	private final String middle;

	ThreePartName(
			String first, String middle, String last) {
		// can't have a middle name without a first name
		requireNonNullNonEmpty(first);
		super(first, last);
		this.middle = middle;
	}

}

Prepare Arguments

class ThreePartName extends Name {

	private final String middle;

	ThreePartName(
			String first, String middle, String last) {
		// shorten first if middle is given
		var short1st = middle.length() == 1
				? first.substring(0, 1)
				: first;
		super(short1st, last);
		this.middle = middle;
	}

}

Split Arguments

class ThreePartName extends Name {

	private final String middle;

	ThreePartName(String full) {
		// split "first middle last" on space (once ๐Ÿ™Œ๐Ÿพ)
		var names = full.split(" ");
		super(names[0], names[2]);
		this.middle = names[1];
	}

}

Surprising Cameo

  • Project Valhalla ponders null-restricted types

  • has to work with super()

  • subclass sets non-null fields before super()

More

Java 24

Final in Java 24
Final in Java 23
Final in Java 22
Preview in Java 24
Primitive Patterns
Module Imports
Flexible Constructor Bodies
String Templates

String composition

Composing strings in Java is cumbersome:

String property = "last_name";
String value = "Doe";

// concatenation
String query =
	"SELECT * FROM Person p WHERE p."
		+ property + " = '" + value + "'";
// formatting
String query =
	"SELECT * FROM Person p WHERE p.%s = '%s'"
		.formatted(property, value);

Comes with free SQL injection! ๐Ÿ˜ณ

String interpolation

Why not?

// (fictional syntax!)
String query =
	"SELECT * FROM Person p "
		+ "WHERE p.\{property} = '\{value}'";

Also comes with free SQL injection! ๐Ÿ˜ณ

String interpolation

SQL injections aren’t the only concern.

These also need validation and sanitization:

  • HTML/XML

  • JSON

  • YAML

  • …​

All follow format-specific rules.

String templates

[Preview in Java 21 and 22 — JEP 430, JEP 459]

Statement query = SQL."""
	SELECT * FROM Person p
	WHERE p.\{property} = '\{value}'
	""";

Special SQL."…​" syntax was too much.

Quo Vadis?

  • string templates were removed from JDK 23
    (not even a preview)

  • the feature needs a redesign

  • timing is unknown

๐Ÿ˜ž

More

Java 22-24

In a few slides…​

Java 22-24 Introduce

Java 24 Previews

Java 22-24

Not explosive like Java 21,
but no slouches either.

Continue Java’s evolution.

Get JDK 24 EA at jdk.java.net/24.

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