Java 21 ๐Ÿ’ฃ๐Ÿ’ฅ

Developer Advocate

Java Team at Oracle

Let’s get started!

Lots to talk about!

Pattern Matching
Virtual Threads
String Templates
New & Updated APIs
Performance & Security
On-Ramp

Why upgrade?

Costs of running on old versions:

  • support contract for Java

  • waning support in libraries / frameworks

Why upgrade?

Costs of not running on new versions:

  • lower productivity

  • less observability and performance
    (more on that later)

  • less access to talent

  • bigger upgrade costs

Why upgrade?

Resistence is futile.

How to upgrade?

Preparations:

  • stick to supported APIs

  • stick to standardized behavior

  • stick to well-maintained projects

  • keep dependencies and tools up to date

  • stay ahead of removals (jdeprscan)

  • build on many JDK versions

How to upgrade?

Prepare by building on multiple JDK versions:

  • your baseline version

  • every supported version since then

  • latest version

  • EA build of next version

How to upgrade?

It’s not necessary to build โ€ฆ

  • โ€ฆ each commit on all versions

  • โ€ฆ the whole project on all versions

Build as much as feasible.

What about LTS?

Within OpenJDK, there is no LTS.

โ‡ has no impact on features, reliability, etc.

It’s a vendor-centric concept
to offer continuous fixes
(usually for money).

You’re paying not to get new features.

What about LTS?

inside java newscast 52

Java 21 ๐Ÿ’ฃ๐Ÿ’ฅ

Pattern Matching
A New Dynamic Dispatch
Data-Oriented Programming
Virtual Threads
String Templates
New & Updated APIs
Performance & Security
On-Ramp

A simple app

Features:

  • scrapes GitHub projects

  • creates Page instances:

    • GitHubIssuePage

    • GitHubPrPage

    • ExternalPage

    • ErrorPage

  • further processes pages

Crawling GitHub

The scraping is implemented in:

public class PageTreeFactory {

	public static Page loadPageTree(/*...*/) {
		// [...]
	}

}

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 ๐Ÿ˜ซ

  • type checks ๐Ÿ˜ฑ

Type checks ๐Ÿ˜ฑ

public void categorize(Page page) {
	if (page instanceof GitHubIssuePage) {
		GitHubIssuePage issue = (GitHubIssuePage) page;
		categorizeIssuePage(issue);
	} else if (page instanceof GitHubPrPage) {
		// ... etc. for all types
	}
}

Ignore the ๐Ÿ˜ฑ and let’s work on this.

Type patterns

[Finalized in Java 16 — JEP 394]

They combine:

  • type check

  • variable declaration

  • cast/assignment

โ‡ Standardizes and eases a common pattern.

Type patterns

public void categorize(Page page) {
	if (page instanceof GitHubIssuePage issue)
		categorizeIssuePage(issue);
	else if (page instanceof GitHubPrPage pr)
		// ... etc. for all types
}

Patterns

Generally, patterns consist of three parts:

  • a boolean check

  • variable declaration(s)

  • extraction(s)/assignment(s)

Records

[Finalized in Java 16 — JEP 395]

Transparent carriers for immutable data.

  • compiler understands internals

  • couples API to internals

  • reduces verbosity a lot

Records

Transparent carriers for immutable data.

record ExternalPage(URI url, String content) { }
  • ExternalPage is final

  • private final fields: URI url and String content

  • constructor: ExternalPage(URI url, String content)

  • accessors: URI url() and String content()

  • equals(), hashCode(), toString() that use the two fields

All method/constructor bodies can be customized.

Record Patterns

[Finalized in Java 21 — JEP 440]

  • check whether variable is of correct type

  • declare one variable per component

  • assign component values to variables

if (page instanceof
		ExternalPage(var url, var content)) {
	// use `url` and `content`
}

โ‡ Standardizes and eases a common pattern.

Patterns in switch

[Finalized in Java 21 — JEP 441]

public void categorize(Page page) {
	switch (page) {
		case GitHubIssuePage issue
			-> categorizeIssuePage(issue);
		case ExternalPage(var url, var content)
			-> categorizeExternalUrl(url);
		// ... etc. for all types
	}
}

But:

error: the switch expression does not cover
       all possible input values

Exhaustiveness

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

public void categorize(Page page) {
	switch (page) {
		case GitHubIssuePage issue ->
			categorizeIssuePage(issue);
		// ... etc. for all types
		default ->
			throw new IllegalArgumentException();
	}
}

That touches the ๐Ÿ˜ฑ nerve.

Sealed types

[Finalized in Java 17 — JEP 409]

Sealed types limit inheritance,
by only allowing specific subtypes.

  • communicates intention to developers

  • allows compiler to check exhaustiveness

Sealed types

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

โ‡ class MyPage implements Page doesn’t compile

Sealed types in switch

If all subtypes of a sealed types are covered,
the switch is exhaustive (without default) โ€ฆ

public void categorize(Page page) {
	switch (page) {
		case GitHubIssuePage issue -> // ...
		case GitHubPrPage pr -> // ...
		case ExternalPage external -> // ...
		case ErrorPage error -> // ...
	}
}

โ€ฆ and the compiler is happy!
(But still watching.)

Facing the ๐Ÿ˜ฑ

Why is switching over the type scary?

Because it may not be future proof!

But this one is!

Let’s add GitHubCommitPage implements Page.

โ‡ Follow the compile errors!

Follow the errors

Starting point:

record GitHubCommitPage(/*โ€ฆ*/) implements Page {

	// ...

}

Compile error because supertype is sealed.

โ‡ Go to the sealed supertype.

Follow the errors

Next stop: the sealed supertype

โ‡ Permit the new subtype!

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

Follow the errors

Next stop: all switches that are no longer exhaustive.

public void categorize(Page page) {
	switch (page) {
		case GitHubIssuePage issue -> // ...
		case GitHubPrPage pr -> // ...
		case ExternalPage external -> // ...
		case ErrorPage error -> // ...
		// missing case
	}
}

Bingo!

(But only works without default branch.)

Dynamic dispatch

Dynamic dispatch selects the invoked method by type.

As language feature:

  • via inheritance

  • makes method part of API

What if methods shouldn’t be part of the API?

Dynamic dispatch

Without methods becoming part of the API.

Via visitor pattern:

  • makes "visitation" part of API

  • cumbersome and indirect

Dynamic dispatch

Without methods becoming part of the API.

Via pattern matching (new):

  • makes "sealed" part of type

  • straight-forward

Patterns and language

Design patterns make up gaps in the language.

Good example is the strategy pattern:

  • used to be "a thing" in Java

  • you use it everytime you pass a lambda

But do you still think of it a design pattern?
(I don’t.)

Pattern matching does the same for the visitor pattern.

In practice

Applications for records, switch, and pattern matching:

  • ad-hoc data structures

  • complex return types

  • complex domains

Ad-hoc Data Structures

Often local, throw-away types used in one class or package:

record PageWithLinks(Page page, Set<URI> links) {

	PageWithLinks {
		requireNonNull(page);
		requireNonNull(links);
		links = new HashSet<>(links);
	}

}

Complex Return Types

Return values that are deconstructed immediately:

Match<User> findUser(String userName) { ... }

// types
sealed interface Match<T> { }

record None<T>() implements Match<T> { }

record Exact<T>(T entity) implements Match<T> { }

record Fuzzies<T>(Collection<T> entities)
	implements Match<T> { }

Complex Return Types

Return values that are deconstructed immediately:

// calling the method
switch (findUser("John Doe")) {
	case None<> none -> // ...
	case Exact<> exact -> // ...
	case Fuzzies<> fuzzies -> // ...
}

Complex Domains

Long-living objects that are part
of the program’s domain.

For example Page.

Pushing further

Pattern matching will probably see
further improvements, e.g.:

Unnamed patterns

[Preview in Java 21 — JEP 443]

Use _ to ignore components:

public void categorize(Page page) {
	switch (page) {
		case GitHubIssuePage(_, _, int issue, _) -> // ...
		case GitHubPrPage(_, _, int pr, _) -> // ...
		case ExternalPage(var url, _) -> // ...
		case ErrorPage(var url, _) -> // ...
	}
}

โ‡ Focus on what’s essential.

Unnamed patterns

Use _ to define default behavior:

public void categorizeGitHub(Page page) {
	switch (page) {
		case GitHubIssuePage(_, _, int issue, _) -> // ...
		case GitHubPrPage(_, _, int pr, _) -> // ...
		case ErrorPage _, ExternalPage _ -> { };
	};
}

โ‡ Default behavior without default branch.

Pattern matching guide

When keeping functionality separate from types:

  • seal the supertype

  • switch over sealed types

  • enumerate all subtypes

  • avoid default branches!

More

More on pattern matching:

Java 21 ๐Ÿ’ฃ๐Ÿ’ฅ

Pattern Matching
A New Dynamic Dispatch
Data-Oriented Programming
Virtual Threads
String Templates
New & Updated APIs
Performance & Security
On-Ramp

Programming paradigms

Paradigms often come with an
"Everything is a …​" sentence.

The goal of any programming paradigm is to manage complexity.

  • complexity comes in many forms

  • not all paradigms handle all forms equally well

โ‡ "It depends"

Object-oriented programming

Everything is an object

  • combines state and behavior

  • hinges on encapsulation

  • polymorphism through inheritance

Works best when defining/upholding boundaries.

Mixed programming

Great use cases for OOP:

  • boundaries between libraries and clients

  • in large programs to enable modular reasoning

Consider a data-oriented approach for:

  • smaller (sub)systems

  • focused on data

Data-oriented programming

Guiding principles:

  • model the data, the whole data,
    and nothing but the data

  • data is immutable

  • validate at the boundary

  • make illegal states unrepresentable

From Brian Goetz' seminal article:
Data Oriented Programming in Java

Crawling GitHub

The app we just looked at:

  • is small

  • focusses on data (Page)

  • separates operations from types

โ‡ Perfect for data-oriented programming!

Applying DOP

Model the data, the whole data,
and nothing but the data.

There are four kinds of pages:

  • error page

  • external page

  • GitHub issue page

  • GitHub PR page

โ‡ Use four records to model them!

Modeling the data

public record ErrorPage(
	URI url, Exception ex) { }

public record ExternalPage(
	URI url, String content) { }

public record GitHubIssuePage(
	URI url, String content,
	int issueNumber, Set<Page> links) { }

public record GitHubPrPage(
	URI url, String content,
	int prNumber, Set<Page> links) { }

Applying DOP

Model the data, the whole data,
and nothing but the data.

There are additional relations between them:

  • a page (load) is either successful or not

  • a successful page is either external or GitHub

  • a GitHub page is either for a PR or an issue

โ‡ Use sealed types to model the alternatives!

Modeling alternatives

public sealed interface Page
		permits ErrorPage, SuccessfulPage {
	URI url();
}

public sealed interface SuccessfulPage
		extends Page permits ExternalPage, GitHubPage {
	String content();
}

public sealed interface GitHubPage
		extends SuccessfulPage
		permits GitHubIssuePage, GitHubPrPage {
	Set<Page> links();
	default Stream<Page> subtree() { ... }
}

Algebraic data types

  • records are product types

  • sealed types are sum types

This simple combination of mechanisms — aggregation and choice — is deceptively powerful

Applying DOP

Make illegal states unrepresentable.

Many are already, e.g.:

  • with error and with content

  • with issueNumber and prNumber

  • with isseNumber or prNumber but no links

Validation

Validate at the boundary.

โ‡ Reject other illegal states in constructors.

record ExternalPage(URI url, String content) {
	// compact constructor
	ExternalPage {
		Objects.requireNonNull(url);
		Objects.requireNonNull(content);
		if (content.isBlank())
			throw new IllegalArgumentException();
	}
}

Applying DOP

Data is immutable.

Records are shallowly immutable,
but field types may not be.

โ‡ Fix that during construction.

// compact constructor
GitHubPrPage {
	// [...]
	links = Set.copyOf(links);
}

Where are we?

  • page "type" is explicit in Java’s type

  • only legal combination of data are possible

  • API is self-documenting

  • code is trivial to test

But where did the operations go?

Operations on data

Model the data, the whole data,
and nothing but the data.

โ‡ Methods should be limited to derived quantities.

public record GitHubIssuePage(
		URI url, String content,
		int issueNumber, Set<Page> links) {

	public String toPrettyString() {
		return "๐Ÿˆ ISSUE #" + issueNumber;
	}

}

Operations on data

Other operations must be defined elsewhere:

  • methods in other subsystems

  • use pattern matching over sealed types
    for polymorphic operations

  • avoid default branch

  • use record patterns to access data

โ‡ This is just pattern matching.

Operations on data

If toPrettyString were defined outside of Page:

private static String toPrettyString(Page page) {
	return switch (page) {
		case ErrorPage(var url, _)
			-> "๐Ÿ’ฅ ERROR: " + url.getHost();
		case ExternalPage(var url, _)
			-> "๐Ÿ’ค EXTERNAL: " + url.getHost();
		case GitHubIssuePage(_, _, int issueNumber, _)
			-> "๐Ÿˆ ISSUE #" + issueNumber;
		case GitHubPrPage(_, _, int prNumber, _)
			-> "๐Ÿ™ PR #" + prNumber;
	};
}

Functional programming?!

  • immutable data structures

  • methods (functions?) that operate on them

Isn’t this just functional programming?!

Kind of.

DOP vs FP

Functional programming:

Everything is a function

โ‡ Focus on creating and composing functions.


Data-oriented programming:

Model data as data.

โ‡ Focus on correctly modeling the data.

DOP vs OOP

OOP is not dead (again):

  • valuable for complex entities or rich libraries

  • use whenever encapsulation is needed

  • still a good default on high level

DOP —  consider when:

  • mainly handling outside data

  • working with simple or ad-hoc data

  • data and behavior should be separated

Data-oriented programming

Use Java’s strong typing to model data as data:

  • use classes to represent data, particularly:

    • data as data with records

    • alternatives with sealed classes

  • use methods (separately) to model behavior, particularly:

    • exhaustive switch without default

    • pattern matching to destructure polymorphic data

Guiding principles

  • model the data, the whole data,
    and nothing but the data

  • data is immutable

  • validate at the boundary

  • make illegal states unrepresentable

More

More on data-oriented programming:

Java 21 ๐Ÿ’ฃ๐Ÿ’ฅ

Pattern Matching
Virtual Threads
Unlimited Threads
Under The Hood
String Templates
New & Updated APIs
Performance & Security
On-Ramp

A simple web request

Imagine a hypothetical HTTP request:

  1. interpret request

  2. query database (blocks)

  3. process data for response

Resource utilization:

  • good for 1. and 3.

  • really bad for 2.

How to implement that request?

Synchronous

Align application’s unit of concurrency (request)
with Java’s unit of concurrency (thread):

  • use thread per request

  • simple to write, debug, profile

  • blocks threads on certain calls

  • limited number of platform threads
    โ‡ bad resource utilization
    โ‡ low throughput

Asynchronous

Only use threads for actual computations:

  • use non-blocking APIs (futures / reactive streams)

  • harder to write, challenging to debug/profile

  • incompatible with synchronous code

  • shares platform threads
    โ‡ great resource utilization
    โ‡ high throughput

Conflict!

There’s a conflict between:

  • simplicity

  • throughput

Nota bene

There are other conflicts:

  • design vs performance (โ‡ Valhalla)

  • explicitness vs succinctness (โ‡ Amber)

  • flexibility vs safety (โ‡ Panama)

  • optimization vs specification (โ‡ Leyden)

Enter virtual threads!

A virtual thread:

  • is a regular Thread

  • low memory footprint ([k]bytes)

  • small switching cost

  • scheduled by the Java runtime

  • requires no OS thread when waiting

Virtual things

Virtual memory:

  • maps large virtual address space
    to limited physical memory

  • gives illusion of plentiful memory

Virtual threads:

  • map large number of virtual threads
    to a small number of OS threads

  • give the illusion of plentiful threads

Virtual things

Programs rarely care about virtual vs physical memory.

Programs need rarely care about virtual vs platform thread.

Instead:

  • write straightforward (blocking) code

  • runtime shares available OS threads

  • reduces the cost of blocking to near zero

Example

try (var executor = Executors
		.newVirtualThreadPerTaskExecutor()) {
	for (int i = 0; i < 1_000_000; i++) {
		var number = i;
		executor.submit(() -> {
			Thread.sleep(Duration.ofSeconds(1));
			return number;
		});
	}
} // executor.close() is called implicitly, and waits

Effects

Virtual threads:

  • remove "number of threads" as bottleneck

  • match app’s unit of concurrency to Java’s

โ‡ simplicity && throughput

Performance

Virtual threads aren’t "faster threads":

  • same number of CPU cycles

  • each task takes the same time (same latency)

So why bother?

Parallelism vs concurrency

ParallelismConcurrency

Task origin

solution

problem

Control

developer

environment

Resource use

coordinated

competitive

Metric

latency

throughput

Abstraction

CPU cores

tasks

# of threads

# of cores

# of tasks

Performance

When workload is not CPU-bound:

  • start waiting as early as possible

  • for as many tasks as possible

โ‡ Virtual threads increase throughput:

  • when workload is not CPU-bound

  • when number of concurrent tasks is high

Server how-to

For servers:

  • request handling threads are started by web framework

  • frameworks will offer (easy) configuration options

We’re getting there.

Spring Boot

Set property on 3.2 (Nov 2023):

spring.threads.virtual.enabled=true

Quarkus

Annotate request handling method on 3.?:

@GET
@Path("api")
@RunOnVirtualThread
public String handle() {
	// ...
}

Helidon

Just works on 4.0 (currently RC1).

More

Virtual threads

Go forth and multiply (your threads)

Java 21 ๐Ÿ’ฃ๐Ÿ’ฅ

Pattern Matching
Virtual Threads
Unlimited Threads
Under The Hood
String Templates
New & Updated APIs
Performance & Security
On-Ramp

Preparing your code

Virtual threads:

  • always work correctly

  • may not scale perfectly

Code changes can improve scalability
(and maintainability, debuggability, observability).

Avoid thread pools

Only pool expensive resources
but virtual threads are cheap.

โ‡ Replace thread pools (for concurrency),
with virtual threads plus, e.g., semaphores.

With thread pools

// limits concurrent queries but pools ๐Ÿ‘Ž๐Ÿพ
private static final ExecutorService DB_POOL =
	Executors.newFixedThreadPool(16);

public <T> Future<T> queryDatabase(Callable<T> query) {
	return DB_POOL.submit(query);
}

With semaphore

// limits concurrent queries without pool ๐Ÿ‘๐Ÿพ
private static final Semaphore DB_SEMAPHORE =
	new Semaphore(16);

public <T> T queryDatabase(Callable<T> query)
		throws Exception {
	DB_SEMAPHORE.acquire();
	try {
		return query.call();
	} finally {
		DB_SEMAPHORE.release();
	}
}

Caveats

To understand virtual thread caveats
we need to understand how they work.

(Also, it’s very interesting.)

Under the hood

The Java runtime manages virtual threads:

  • runs them on a pool of carrier threads

  • on blocking call:

    • internally calls non-blocking operation

    • unmounts from carrier thread!

  • when call returns:

    • mounts to (other) carrier thread

    • continues

The simple web request

Remember the hypothetical request:

  1. interpret request

  2. query database (blocks)

  3. process data for response

In a virtual thread:

  • runtime submits task to carrier thread pool

  • when 2. blocks, virtual thread unmounts

  • runtime hands carrier thread back to pool

  • when 2. unblocks, runtime resubmits task

  • virtual thread mounts and continues with 3.

Compatibility

Virtual threads work correctly with everything:

  • all blocking operations

  • synchronized

  • Thread, currentThread, etc.

  • thread interruption

  • thread-locals

  • native code

But not all scale perfectly.

Caveat #1: capture

Despite lots of internal rework (e.g. JEPs 353, 373)
not all blocking operations unmount.

Some capture platform thread:

  • Object::wait

  • file I/O (โ‡ io_uring)

โ‡ Compensated by temporarily growing carrier pool.

โš ๏ธ Problematic when capturing operations dominate.

Caveat #2: pinning

Some operations pin (operations don’t unmount):

  • native method call (JNI)

  • foreign function call (FFM)

  • synchronized block (for now)

โ‡ No compensation

โš ๏ธ Problematic when:

  • pinning is frequent

  • contains blocking operations

Avoid long-running pins

If possible:

  • avoid pinning operations

  • remove blocking operations
    from pinning code sections.

With synchronization

// guarantees sequential access, but pins (for now) ๐Ÿ‘Ž๐Ÿพ
public synchronized String accessResource() {
	return access();
}

With lock

// guarantees sequential access without pinning ๐Ÿ‘๐Ÿพ
private static final ReentrantLock LOCK =
	new ReentrantLock();

public String accessResource() {
	// lock guarantees sequential access
	LOCK.lock();
	try {
		return access();
	} finally {
		LOCK.unlock();
	}
}

Caveat #3: thread-locals

Thread-locals can hinder scalability:

  • can be inherited

  • to keep them thread-local,
    values are copied

  • can occupy lots of memory

(There are also API shortcomings.)

โ‡ Refactor to scoped values (JEP 446).

With thread-local

// copies value for each inheriting thread ๐Ÿ‘Ž๐Ÿพ
static final ThreadLocal<Principal> PRINCIPAL =
	new ThreadLocal<>();

public void serve(Request request, Response response) {
	var level = request.isAdmin() ? ADMIN : GUEST;
	var principal = new Principal(level);
	PRINCIPAL.set(principal);
	Application.handle(request, response);
}

With scoped value

// immutable, so no copies needed ๐Ÿ‘๐Ÿพ
static final ScopedValue<Principal> PRINCIPAL =
	new ScopedValue<>();

public void serve(Request request, Response response) {
	var level = request.isAdmin() ? ADMIN : GUEST;
	var principal = new Principal(level);
	ScopedValue
		.where(PRINCIPAL, principal)
		.run(() -> Application
			.handle(request, response));
}

Preparing your code

Most importantly:

  1. replace thread pools with semaphores

Also helpful:

  1. remove long-running I/O from pinned sections

  2. replace thread-locals with scoped values

  3. replace synchronized with locks

Deprecations

Cleaning house

Already removed:

Cleaning house

Deprecations (for removal):

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

  • Windows 32-bit x86 port ใ‰‘ (JEP 449)

  • finalization โ‘ฑ (JEP 421)

  • security manager โ‘ฐ (JEP 411)

  • applet API โ‘ฐ (JEP 398)

  • primitive wrapper constructors โ‘ฏ (JEP 390)

  • biased locking โ‘ฎ (JEP 374)

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)

Security Manager

What it is:

  • 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

  • already disallowed by default

  • enable with java.security.manager=allow

  • in a future release, it will be removed

Security Manager

What you need to do:

  • observe your app with default settings
    (โ‡ security manager disallowed)

  • if used, move away from security manager

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

Java 21 ๐Ÿ’ฃ๐Ÿ’ฅ

Pattern Matching
Virtual Threads
String Templates
New & Updated APIs
Performance & Security
On-Ramp

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 — JEP 430]

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

Template expression ingredients:

  • template with embedded expressions
    ~> StringTemplate

  • template processor (e.g. STR):
    transforms StringTemplate into String*

Template procesor STR

String form = STR."""
	Desc     Unit   Qty   Amount
	\{desc} $\{price} \{qty} $\{price * qty}

	Subtotal  $\{price * qty}
	Tax       $\{price * qty * tax}
	Total     $\{price * qty * (1.0 + tax)}
	""";
Desc     Unit   Qty   Amount
hammer   $7.88  3     $23.64

Subtotal  $23.64
Tax       $3.546
Total     $27.186

Template processor FMT

String form = FMT."""
	Desc        Unit      Qty   Amount
	%-10s\{desc} $%5.2f\{price} %5d\{qty} $%5.2f\{price * qty}

	Subtotal  $%5.2f\{price * qty}
	Tax       $%5.2f\{price * qty * tax}
	Total     $%5.2f\{price * qty * (1.0 + tax)}
	""";
Desc        Unit      Qty   Amount
hammer      $ 7.88      3   $23.64

Subtotal  $23.64
Tax       $ 3.55
Total     $27.19

Why strings?

Often, strings are just exchange format, e.g.:

  • start with: String + values

  • validate / sanitize (i.e. parse)

  • dumb down to: String ๐Ÿค”

  • parse to: JSONObject, Statement, โ€ฆ

Why the detour?

Custom templating

STR is a singleton instance of
a Processor implementation:

public interface Processor<RESULT, EX> {
	RESULT process(StringTemplate s) throws EX;
}

RESULT can be of any type!

Custom templating

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

// validates & escapes JSON
JSONObject doc = JSON."""
	{
		"name": "\{name}",
		"year": "\{bday.getYear()}"
	}
	""";

Summary

String templates:

  • simplify string concatenation

  • enable domain-specific processing

  • incentivize the "right way"

Java 21 ๐Ÿ’ฃ๐Ÿ’ฅ

Pattern Matching
Virtual Threads
String Templates
New & Updated APIs
Sequenced Collections
Address Resolution SPI
Misc. Improvements
Performance & Security
On-Ramp

Order

Collections with order and indexed access:

  • List

Collections with order without indexed access:

  • SortedSet (sort order)

  • Deque (insertion order)

  • LinkedHashSet (insertion order)

  • and more

Sequence

New interfaces capture the concept of order:

  • SequencedCollection

  • SequencedSet

  • SequencedMap

Use as parameter or return type
and enjoy new methods.

Get first

Getting the first element:

  • list.get(0)

  • sortedSet.first()

  • deque.getFirst()

  • linkedHashSet.iterator().next()

Now for all:

sequencedCollection.getFirst()

Remove last

Removing the last element:

  • list.remove(list.size() - 1)

  • sortedSet.remove(sortedSet.last())

  • deque.removeLast()

  • linkedHashSet.๐Ÿคท๐Ÿพโ€โ™‚๏ธ()

Now for all:

sequencedCollection.removeLast()

Reverse order

Reversing order:

  • list.listIterator() โ‡ ListIterator

  • navigableSet.descendingSet() โ‡ NavigableSet (view)

  • deque.descendingIterator() โ‡ Iterator

  • linkedHashSet.๐Ÿคท๐Ÿพโ€โ™‚๏ธ()

Now for all:

sequencedCollection.reversed()

Reversed collection

sequencedCollection.reversed() returns
a SequencedCollection view:

for (E element : list.reversed())
	// ...

sortedSet
	.reversed().stream()
	//...

deque.reversed().toArray();

Reversed view

sequencedCollection.reversed() returns
a SequencedCollection view:

var letters = new ArrayList<>(List.of("A", "B", "C"));
	// โ‡ letters = ["A", "B", "C"]
letters.reversed().removeFirst();
	// โ‡ letters = ["A", "B"]

New operations

void addFirst(E);
void addLast(E);
E getFirst();
E getLast();
E removeFirst();
E removeLast();

SequencedCollection<E> reversed();

(Analoguous for maps.)

New ops vs sort order

What happens when addFirst|Last is used
on a sorted data structure?

SortedSet<String> letters = new TreeSet<>(
	List.of("B", "A", "C"));
	// โ‡ letters = ["A", "B", "C"]
letters.addLast("D");

Options:

  • works always โ‡ breaks SortedSet contract

  • works if value fits โ‡ hard to predict

  • works never โ‡ UnsupportedOperationException

Using types

Use the most general type that:

  • has the API you need/support

  • plays the role you need/support

For collections, that’s often: Collection
(less often: List, Set).

โ‡ Consider new types!

More

Java 21 ๐Ÿ’ฃ๐Ÿ’ฅ

Pattern Matching
Virtual Threads
String Templates
New & Updated APIs
Sequenced Collections
Address Resolution SPI
Misc. Improvements
Performance & Security
On-Ramp

Built-in resolver

The JDK has a built-in resolver for host names:

  • relies on OS’s native resolver

  • typically uses hosts file and DNS

Desirable improvements:

  • better interaction with virtual threads

  • support for alternative protocols

  • support for testing/mocking

(While being backwards compatible.)

Pluggable resolver

Solution: Allow plugging in a custom resolver.

Two new types:

  • InetAddressResolverProvider

  • InetAddressResolver

Old resolver implements these,
and acts as default.

Pluggable resolver

// registered as a service
public abstract class InetAddressResolverProvider {
	InetAddressResolver get(Configuration config);
	String name();
}

public interface InetAddressResolver {
	Stream<InetAddress> lookupByName(
		String host, LookupPolicy lookupPolicy);
	String lookupByAddress(byte[] addr);
}

A transparent custom resolver

// in module declaration
provides InetAddressResolverProvider
	with TransparentInetAddressResolverProvider;

// class
public class TransparentInetAddressResolverProvider
		extends InetAddressResolverProvider {

	@Override
	public InetAddressResolver get(
			Configuration config) {
		return new TransparentInetAddressResolver(
			config.builtinResolver());
	}

}

A transparent custom resolver

public class TransparentInetAddressResolver
		implements InetAddressResolver {

	private InetAddressResolver builtinResolver;

	public TransparentInetAddressResolver(
			InetAddressResolver builtinResolver) {
		this.builtinResolver = builtinResolver;
	}

	// ...

}

A transparent custom resolver

@Override
public Stream<InetAddress> lookupByName(
		String host, LookupPolicy lookupPolicy) {
	return builtinResolver
		.lookupByName(host, lookupPolicy);
}

@Override
public String lookupByAddress(byte[] addr) {
	return builtinResolver.lookupByAddress(addr);
}

Custom resolvers

Possible resolvers:

  • support for QUIC, TLS, HTTPS, etc.

  • redirect host names to local IPs for testing

  • more ideas?

More

๐Ÿ“ JEP 418

Java 21 ๐Ÿ’ฃ๐Ÿ’ฅ

Pattern Matching
Virtual Threads
String Templates
New & Updated APIs
Sequenced Collections
Address Resolution SPI
Misc. Improvements
Performance & Security
On-Ramp

Collections

How do you create an ArrayList that
can store 50 elements without resizing?

new ArrayList<>(50);

How do you create a HashMap that
can store 50 pairs without resizing?

new HashMap<>(64, 0.8f);
new HashMap<>(128);

๐Ÿค”

Collections

Right-sizing hashing data structures:

  • HashMap.newHashMap(int numMappings)

  • HashSet.newHashSet(int numElements)

  • LinkedHashMap.newLinkedHashMap(int numMappings)

  • LinkedHashSet.newLinkedHashSet(int numElements)

Math

Lots of new methods on Math:

  • for int/long division with different modes for:

    • rounding

    • overflow handling

  • for ceiling modulus (5 โŒˆ%โŒ‰ 3 = -1)

  • for clamping

Strings and builders

"String".indexOf(
	String str, int beginIndex, int endIndex)

On StringBuilder and StringBuffer:

  • repeat(int codePoint, int count)

  • repeat(CharSequence cs, int count)

๐Ÿ’ฃ๐Ÿ’ฅ

On Character (all static):

  • isEmoji(int codePoint)

  • isEmojiPresentation(int codePoint)

  • isEmojiModifier(int codePoint)

  • isEmojiModifierBase(int codePoint)

  • isEmojiComponent(int codePoint)

Localizing DateTimes

Options for formatting dates/times with DateTimeFormatter:

  • with a fixed pattern: ofPattern

  • with a localized style: ofLocalizedDate
    (FULL, LONG, MEDIUM, SHORT)

What about a localized result with custom elements?

โ‡ DateTimeFormatter.ofLocalizedPattern

Localized patterns

DateTimeFormatter.ofLocalizedPattern:

  • you include what you want to show up
    (e.g. year + month with "yMM")

  • result will depend on locale
    (e.g. "10/2023" in USA)

Pattern comparison:

var now = ZonedDateTime.now();
for (var locale : List.of(
		Locale.of("en", "US"),
		Locale.of("be", "BE"),
		Locale.of("vi", "VN"))) {
	Locale.setDefault(locale);

	var custom = DateTimeFormatter
		.ofPattern("y-MM-dd");
	var local = DateTimeFormatter
		.ofLocalizedDate(FormatStyle.SHORT);
	var customLocal = DateTimeFormatter
		.ofLocalizedPattern("yMM");

	// pretty print
}

Pattern comparison

localecustomlocalboth

en_US

2023-10-02

10/2/23

10/2023

be_BE

2023-10-02

2.10.23

10.2023

vi_VN

2023-10-02

02/10/2023

thรกng 10, 2023

Localized patterns

Analogue methods were added to DateTimeFormatterBuilder:

DateTimeFormatterBuilder appendLocalized(
	String requestedTemplate);

static String getLocalizedDateTimePattern(
	String requestedTemplate,
	Chronology chrono, Locale locale)

More AutoCloseable

These types now implemnet AutoCloseable:

  • HttpClient

  • ExecutorService

  • ForkJoinPool

A better future

Additions to Future<T>:

  • T resultNow()

  • Throwable exceptionNow

  • State state()

New:

enum State {
	RUNNING, SUCCESS, FAILED, CANCELLED
}

More

There are many more additions like this.

Find a few more in
๐ŸŽฅ Java 21 API New Features (Sep 2023)

Java 21 ๐Ÿ’ฃ๐Ÿ’ฅ

Pattern Matching
Virtual Threads
String Templates
New & Updated APIs
Performance & Security
On-Ramp

Generational ZGC

Compared to other GCs, ZGC:

  • optimizes for ultra-low pause times

  • can have higher memory footprint or higher CPU usage

In Java 21, ZGC becomes 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 with:
-XX:+UseZGC -XX:+ZGenerational

Some Benchmarks

A Cassandra 4 benchmark of ZGC vs GenZGC showed:

  • 4x throughput with a fixed heap or

  • 1/4x heap size with stable throughput

(Probably not representative but very promising.)

More

Java 21 ๐Ÿ’ฃ๐Ÿ’ฅ

Pattern Matching
Virtual Threads
String Templates
New & Updated APIs
Performance & Security
On-Ramp

Starting (with) Java

Java 21 makes life easier
for new (Java) developers.

Why do we care?

We all know Java, IDEs, build tools, etc.

  • do we all?

  • what about your kids?

  • what about students?

  • what about the frontend dev?

  • what about ML/AI folks?

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

Running Java

To write and run a simple Java program, you need:

  • a JDK

  • an editor (IDE?)

  • javac (build tool? IDE?)

  • java (IDE?)

  • some Java code

Writing Java

Minimal Java code:

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

  • classes & methods

  • static vs instance

  • returns & parameters

  • statements & arguments

Approachability

That’s a lot of tools and concepts!

Java is great for large-scale development:

  • detailed toolchain

  • refined programming model

This make it less approachable.

Let’s change that!

jshell

Java 9 added jshell:

  • all you need:

    • tools: JDK, jshell

    • concepts: statements & arguments

  • but:

    • not great for beginners (IMO)

    • no progression

More is needed.

Single-file execution

Java 11 added single-file execution (JEP 330):

java Prog.java
  • removed: javac

  • but: no progression

Much better for beginners,
but just a section of an on-ramp.

On-Ramp

Expand single-file execution in two directions:

  • simplify code: reduce required Java concepts

  • ease progression: run multiple files with java

Simpler code

Remove requirement of:

  • String[] args parameter

  • main being static

  • main being public

  • the class itself

// all the code in Prog.java
void main() {
	System.out.println("Hello, World!");
}

[Preview in Java 21 — JEP 445]

Running multiple files

Say you have a folder:

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

Run with:

java -cp 'Lib/*' Prog.java

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

Doesn’t even have to be that order!

Summary

Java’s strengths for large-scale development
make it less approachable:

  • detailed toolchain

  • refined programming model

There are new features that:

  • make it easier to start

  • allow gradual progression

  • entice the future dev generation

More

Don’t miss Brian Goetz' keynote: Wed, 10:20!

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